[ci skip] Merge branch 'master' into 38587-pipelines-empty-state
* master: (52 commits) Projects and groups badges API Keep a commit around if its sha is present #43691: DiffNotes not counted by ContributionsCalendar Cleanup after adding MR diff's commit_count Fix MR merge commit cross-references to the MR itself Fix n+1 issue by not reloading fully loaded blobs Add "added" type on changelog Use limited count queries also for scoped searches Update changelog Rename quick actions handler Adds updated_at filter to issues and merge_requests API Update API: add search param to branches Add changelog entry LabelsSelect DropdownValueCollapsed Component LabelsSelect DropdownValue Component LabelsSelect DropdownTitle Component LabelsSelect DropdownSearchInput Component LabelsSelect DropdownHiddenInput Component LabelsSelect DropdownHeader Component LabelsSelect DropdownFooter Component ...
This commit is contained in:
commit
8ec0fd0a42
186 changed files with 4911 additions and 1314 deletions
|
@ -619,9 +619,10 @@ codequality:
|
|||
cache: {}
|
||||
dependencies: []
|
||||
script:
|
||||
- apk update && apk add jq
|
||||
- ./scripts/codequality analyze -f json > raw_codeclimate.json || true
|
||||
# The following line keeps only the fields used in the MR widget, reducing the JSON artifact size
|
||||
- cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,description,fingerprint,location})' > codeclimate.json
|
||||
- jq -c 'map({check_name,description,fingerprint,location})' raw_codeclimate.json > codeclimate.json
|
||||
artifacts:
|
||||
paths: [codeclimate.json]
|
||||
expire_in: 1 week
|
||||
|
|
|
@ -196,6 +196,17 @@ release. There are two levels of priority labels:
|
|||
milestone. If these issues are not done in the current release, they will
|
||||
strongly be considered for the next release.
|
||||
|
||||
### Severity labels (~S1, ~S2, etc.)
|
||||
|
||||
Severity labels help us clearly communicate the impact of a ~bug on users.
|
||||
|
||||
| Label | Meaning | Example |
|
||||
|-------|------------------------------------------|---------|
|
||||
| ~S1 | Feature broken, no workaround | Unable to create an issue |
|
||||
| ~S2 | Feature broken, workaround unacceptable | Can push commits, but only via the command line |
|
||||
| ~S3 | Feature broken, workaround acceptable | Can create merge requests only from the Merge Requests page, not through the Issue |
|
||||
| ~S4 | Cosmetic issue | Label colors are incorrect / not being displayed |
|
||||
|
||||
### Label for community contributors (~"Accepting Merge Requests")
|
||||
|
||||
Issues that are beneficial to our users, 'nice to haves', that we currently do
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.87.0
|
||||
0.88.0
|
||||
|
|
|
@ -1 +1 @@
|
|||
3.6.0
|
||||
3.8.0
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -411,7 +411,7 @@ group :ed25519 do
|
|||
end
|
||||
|
||||
# Gitaly GRPC client
|
||||
gem 'gitaly-proto', '~> 0.87.0', require: 'gitaly'
|
||||
gem 'gitaly-proto', '~> 0.88.0', require: 'gitaly'
|
||||
# Locked until https://github.com/google/protobuf/issues/4210 is closed
|
||||
gem 'google-protobuf', '= 3.5.1'
|
||||
|
||||
|
|
|
@ -285,7 +285,7 @@ GEM
|
|||
po_to_json (>= 1.0.0)
|
||||
rails (>= 3.2.0)
|
||||
gherkin-ruby (0.3.2)
|
||||
gitaly-proto (0.87.0)
|
||||
gitaly-proto (0.88.0)
|
||||
google-protobuf (~> 3.1)
|
||||
grpc (~> 1.0)
|
||||
github-linguist (5.3.3)
|
||||
|
@ -601,7 +601,7 @@ GEM
|
|||
atomic (>= 1.0.0)
|
||||
mysql2
|
||||
peek
|
||||
peek-performance_bar (1.3.0)
|
||||
peek-performance_bar (1.3.1)
|
||||
peek (>= 0.1.0)
|
||||
peek-pg (1.3.0)
|
||||
concurrent-ruby
|
||||
|
@ -1057,7 +1057,7 @@ DEPENDENCIES
|
|||
gettext (~> 3.2.2)
|
||||
gettext_i18n_rails (~> 1.8.0)
|
||||
gettext_i18n_rails_js (~> 1.2.0)
|
||||
gitaly-proto (~> 0.87.0)
|
||||
gitaly-proto (~> 0.88.0)
|
||||
github-linguist (~> 5.3.3)
|
||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||
gitlab-markup (~> 1.6.2)
|
||||
|
|
|
@ -5,12 +5,12 @@ import Vue from 'vue';
|
|||
|
||||
import Flash from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
import '~/vue_shared/models/label';
|
||||
|
||||
import FilteredSearchBoards from './filtered_search_boards';
|
||||
import eventHub from './eventhub';
|
||||
import sidebarEventHub from '~/sidebar/event_hub'; // eslint-disable-line import/first
|
||||
import './models/issue';
|
||||
import './models/label';
|
||||
import './models/list';
|
||||
import './models/milestone';
|
||||
import './models/assignee';
|
||||
|
|
|
@ -216,6 +216,9 @@ export default class MilestoneSelect {
|
|||
$value.html(milestoneLinkNoneTemplate);
|
||||
return $sidebarCollapsedValue.find('span').text('No');
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
$loading.fadeOut();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
<script>
|
||||
import LabelsSelect from '~/labels_select';
|
||||
import LoadingIcon from '../../loading_icon.vue';
|
||||
|
||||
import DropdownTitle from './dropdown_title.vue';
|
||||
import DropdownValue from './dropdown_value.vue';
|
||||
import DropdownValueCollapsed from './dropdown_value_collapsed.vue';
|
||||
import DropdownButton from './dropdown_button.vue';
|
||||
import DropdownHiddenInput from './dropdown_hidden_input.vue';
|
||||
import DropdownHeader from './dropdown_header.vue';
|
||||
import DropdownSearchInput from './dropdown_search_input.vue';
|
||||
import DropdownFooter from './dropdown_footer.vue';
|
||||
import DropdownCreateLabel from './dropdown_create_label.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LoadingIcon,
|
||||
DropdownTitle,
|
||||
DropdownValue,
|
||||
DropdownValueCollapsed,
|
||||
DropdownButton,
|
||||
DropdownHiddenInput,
|
||||
DropdownHeader,
|
||||
DropdownSearchInput,
|
||||
DropdownFooter,
|
||||
DropdownCreateLabel,
|
||||
},
|
||||
props: {
|
||||
showCreate: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
abilityName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
context: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
namespace: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
updatePath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
labelsPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
labelsWebUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
labelFilterBasePath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
canEdit: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
hiddenInputName() {
|
||||
return this.showCreate ? `${this.abilityName}[label_names][]` : 'label_id[]';
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.labelsDropdown = new LabelsSelect(this.$refs.dropdownButton, {
|
||||
handleClick: this.handleClick,
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
handleClick(label) {
|
||||
this.$emit('onLabelClick', label);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="block labels">
|
||||
<dropdown-value-collapsed
|
||||
v-if="showCreate"
|
||||
:labels="context.labels"
|
||||
/>
|
||||
<dropdown-title
|
||||
:can-edit="canEdit"
|
||||
/>
|
||||
<dropdown-value
|
||||
:labels="context.labels"
|
||||
:label-filter-base-path="labelFilterBasePath"
|
||||
>
|
||||
<slot></slot>
|
||||
</dropdown-value>
|
||||
<div
|
||||
v-if="canEdit"
|
||||
class="selectbox"
|
||||
style="display: none;"
|
||||
>
|
||||
<dropdown-hidden-input
|
||||
v-for="label in context.labels"
|
||||
:key="label.id"
|
||||
:name="hiddenInputName"
|
||||
:label="label"
|
||||
/>
|
||||
<div class="dropdown">
|
||||
<dropdown-button
|
||||
:ability-name="abilityName"
|
||||
:field-name="hiddenInputName"
|
||||
:update-path="updatePath"
|
||||
:labels-path="labelsPath"
|
||||
:namespace="namespace"
|
||||
:labels="context.labels"
|
||||
:show-extra-options="!showCreate"
|
||||
/>
|
||||
<div
|
||||
class="dropdown-menu dropdown-select dropdown-menu-paging
|
||||
dropdown-menu-labels dropdown-menu-selectable"
|
||||
>
|
||||
<div class="dropdown-page-one">
|
||||
<dropdown-header v-if="showCreate" />
|
||||
<dropdown-search-input/>
|
||||
<div class="dropdown-content"></div>
|
||||
<div class="dropdown-loading">
|
||||
<loading-icon />
|
||||
</div>
|
||||
<dropdown-footer
|
||||
v-if="showCreate"
|
||||
:labels-web-url="labelsWebUrl"
|
||||
/>
|
||||
</div>
|
||||
<dropdown-create-label
|
||||
v-if="showCreate"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,78 @@
|
|||
<script>
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
abilityName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
fieldName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
updatePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
labelsPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
namespace: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
labels: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
showExtraOptions: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
dropdownToggleText() {
|
||||
if (this.labels.length === 0) {
|
||||
return __('Label');
|
||||
}
|
||||
|
||||
if (this.labels.length > 1) {
|
||||
return sprintf(s__('LabelSelect|%{firstLabelName} +%{remainingLabelCount} more'), {
|
||||
firstLabelName: this.labels[0].title,
|
||||
remainingLabelCount: this.labels.length - 1,
|
||||
});
|
||||
}
|
||||
|
||||
return this.labels[0].title;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
type="button"
|
||||
ref="dropdownButton"
|
||||
class="dropdown-menu-toggle wide js-label-select js-multiselect js-context-config-modal"
|
||||
data-toggle="dropdown"
|
||||
:class="{ 'js-extra-options': showExtraOptions }"
|
||||
:data-ability-name="abilityName"
|
||||
:data-field-name="fieldName"
|
||||
:data-issue-update="updatePath"
|
||||
:data-labels="labelsPath"
|
||||
:data-namespace-path="namespace"
|
||||
:data-show-any="showExtraOptions"
|
||||
>
|
||||
<span class="dropdown-toggle-text">
|
||||
{{ dropdownToggleText }}
|
||||
</span>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="fa fa-chevron-down"
|
||||
data-hidden="true"
|
||||
>
|
||||
</i>
|
||||
</button>
|
||||
</template>
|
|
@ -0,0 +1,84 @@
|
|||
<script>
|
||||
export default {
|
||||
created() {
|
||||
this.suggestedColors = gon.suggested_label_colors;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dropdown-page-two dropdown-new-label">
|
||||
<div class="dropdown-title">
|
||||
<button
|
||||
type="button"
|
||||
class="dropdown-title-button dropdown-menu-back"
|
||||
:aria-label="__('Go back')"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="fa fa-arrow-left"
|
||||
data-hidden="true"
|
||||
>
|
||||
</i>
|
||||
</button>
|
||||
{{ __('Create new label') }}
|
||||
<button
|
||||
type="button"
|
||||
class="dropdown-title-button dropdown-menu-close"
|
||||
:aria-label="__('Close')"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="fa fa-times dropdown-menu-close-icon"
|
||||
data-hidden="true"
|
||||
>
|
||||
</i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-content">
|
||||
<div class="dropdown-labels-error js-label-error"></div>
|
||||
<input
|
||||
id="new_label_name"
|
||||
type="text"
|
||||
class="default-dropdown-input"
|
||||
:placeholder="__('Name new label')"
|
||||
/>
|
||||
<div class="suggest-colors suggest-colors-dropdown">
|
||||
<a
|
||||
v-for="(color, index) in suggestedColors"
|
||||
href="#"
|
||||
:key="index"
|
||||
:data-color="color"
|
||||
:style="{
|
||||
backgroundColor: color,
|
||||
}"
|
||||
>
|
||||
|
||||
</a>
|
||||
</div>
|
||||
<div class="dropdown-label-color-input">
|
||||
<div class="dropdown-label-color-preview js-dropdown-label-color-preview"></div>
|
||||
<input
|
||||
id="new_label_color"
|
||||
type="text"
|
||||
class="default-dropdown-input"
|
||||
:placeholder="__('Assign custom color like #FF0000')"
|
||||
/>
|
||||
</div>
|
||||
<div class="clearfix">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary pull-left js-new-label-btn disabled"
|
||||
>
|
||||
{{ __('Create') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default pull-right js-cancel-label-btn"
|
||||
>
|
||||
{{ __('Cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,34 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
labelsWebUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dropdown-footer">
|
||||
<ul class="dropdown-footer-list">
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
class="dropdown-toggle-page"
|
||||
>
|
||||
{{ __('Create new label') }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
data-is-link="true"
|
||||
class="dropdown-external-link"
|
||||
:href="labelsWebUrl"
|
||||
>
|
||||
{{ __('Manage labels') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,21 @@
|
|||
<script>
|
||||
export default {};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dropdown-title">
|
||||
<span>{{ __('Assign labels') }}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="dropdown-title-button dropdown-menu-close"
|
||||
:aria-label="__('Close')"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="fa fa-times dropdown-menu-close-icon"
|
||||
data-hidden="true"
|
||||
>
|
||||
</i>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,22 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
label: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input
|
||||
type="hidden"
|
||||
:name="name"
|
||||
:value="label.id"
|
||||
/>
|
||||
</template>
|
|
@ -0,0 +1,27 @@
|
|||
<script>
|
||||
export default {};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dropdown-input">
|
||||
<input
|
||||
autocomplete="off"
|
||||
class="dropdown-input-field"
|
||||
type="search"
|
||||
:placeholder="__('Search')"
|
||||
/>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="fa fa-search dropdown-input-search"
|
||||
data-hidden="true"
|
||||
>
|
||||
</i>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="fa fa-times dropdown-input-clear js-dropdown-input-clear"
|
||||
data-hidden="true"
|
||||
role="button"
|
||||
>
|
||||
</i>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,30 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
canEdit: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="title hide-collapsed append-bottom-10">
|
||||
{{ __('Labels') }}
|
||||
<template v-if="canEdit">
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="fa fa-spinner fa-spin block-loading"
|
||||
data-hidden="true"
|
||||
>
|
||||
</i>
|
||||
<button
|
||||
type="button"
|
||||
class="edit-link btn btn-blank pull-right js-sidebar-dropdown-toggle"
|
||||
>
|
||||
{{ __('Edit') }}
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,63 @@
|
|||
<script>
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
props: {
|
||||
labels: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
labelFilterBasePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isEmpty() {
|
||||
return this.labels.length === 0;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
labelFilterUrl(label) {
|
||||
return `${this.labelFilterBasePath}?label_name[]=${encodeURIComponent(label.title)}`;
|
||||
},
|
||||
labelStyle(label) {
|
||||
return {
|
||||
color: label.textColor,
|
||||
backgroundColor: label.color,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="hide-collapsed value issuable-show-labels">
|
||||
<span
|
||||
v-if="isEmpty"
|
||||
class="text-secondary"
|
||||
>
|
||||
<slot>{{ __('None') }}</slot>
|
||||
</span>
|
||||
<a
|
||||
v-else
|
||||
v-for="label in labels"
|
||||
:key="label.id"
|
||||
:href="labelFilterUrl(label)"
|
||||
>
|
||||
<span
|
||||
v-tooltip
|
||||
class="label color-label"
|
||||
data-placement="bottom"
|
||||
data-container="body"
|
||||
:style="labelStyle(label)"
|
||||
:title="label.description"
|
||||
>
|
||||
{{ label.title }}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,48 @@
|
|||
<script>
|
||||
import { s__, sprintf } from '~/locale';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
props: {
|
||||
labels: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
labelsList() {
|
||||
const labelsString = this.labels.slice(0, 5).map(label => label.title).join(', ');
|
||||
|
||||
if (this.labels.length > 5) {
|
||||
return sprintf(s__('LabelSelect|%{labelsString}, and %{remainingLabelCount} more'), {
|
||||
labelsString,
|
||||
remainingLabelCount: this.labels.length - 5,
|
||||
});
|
||||
}
|
||||
|
||||
return labelsString;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-tooltip
|
||||
class="sidebar-collapsed-icon"
|
||||
data-placement="left"
|
||||
data-container="body"
|
||||
:title="labelsList"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
data-hidden="true"
|
||||
class="fa fa-tags"
|
||||
>
|
||||
</i>
|
||||
<span>{{ labels.length }}</span>
|
||||
</div>
|
||||
</template>
|
|
@ -1,7 +1,5 @@
|
|||
/* eslint-disable no-unused-vars, space-before-function-paren */
|
||||
|
||||
class ListLabel {
|
||||
constructor (obj) {
|
||||
constructor(obj) {
|
||||
this.id = obj.id;
|
||||
this.title = obj.title;
|
||||
this.type = obj.type;
|
|
@ -9,7 +9,6 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController
|
|||
@impersonation_token = finder.build(impersonation_token_params)
|
||||
|
||||
if @impersonation_token.save
|
||||
flash[:impersonation_token] = @impersonation_token.token
|
||||
redirect_to admin_user_impersonation_tokens_path, notice: "A new impersonation token has been created."
|
||||
else
|
||||
set_index_vars
|
||||
|
|
|
@ -17,10 +17,8 @@ class Projects::CompareController < Projects::ApplicationController
|
|||
|
||||
def show
|
||||
apply_diff_view_cookie!
|
||||
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37430
|
||||
Gitlab::GitalyClient.allow_n_plus_1_calls do
|
||||
render
|
||||
end
|
||||
|
||||
render
|
||||
end
|
||||
|
||||
def diff_for_path
|
||||
|
|
|
@ -19,6 +19,10 @@
|
|||
# non_archived: boolean
|
||||
# iids: integer[]
|
||||
# my_reaction_emoji: string
|
||||
# created_after: datetime
|
||||
# created_before: datetime
|
||||
# updated_after: datetime
|
||||
# updated_before: datetime
|
||||
#
|
||||
class IssuableFinder
|
||||
prepend FinderWithCrossProjectAccess
|
||||
|
@ -79,6 +83,7 @@ class IssuableFinder
|
|||
def filter_items(items)
|
||||
items = by_scope(items)
|
||||
items = by_created_at(items)
|
||||
items = by_updated_at(items)
|
||||
items = by_state(items)
|
||||
items = by_group(items)
|
||||
items = by_search(items)
|
||||
|
@ -283,6 +288,13 @@ class IssuableFinder
|
|||
end
|
||||
end
|
||||
|
||||
def by_updated_at(items)
|
||||
items = items.updated_after(params[:updated_after]) if params[:updated_after].present?
|
||||
items = items.updated_before(params[:updated_before]) if params[:updated_before].present?
|
||||
|
||||
items
|
||||
end
|
||||
|
||||
def by_state(items)
|
||||
case params[:state].to_s
|
||||
when 'closed'
|
||||
|
|
|
@ -17,6 +17,10 @@
|
|||
# my_reaction_emoji: string
|
||||
# public_only: boolean
|
||||
# due_date: date or '0', '', 'overdue', 'week', or 'month'
|
||||
# created_after: datetime
|
||||
# created_before: datetime
|
||||
# updated_after: datetime
|
||||
# updated_before: datetime
|
||||
#
|
||||
class IssuesFinder < IssuableFinder
|
||||
CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER
|
||||
|
|
|
@ -19,6 +19,10 @@
|
|||
# my_reaction_emoji: string
|
||||
# source_branch: string
|
||||
# target_branch: string
|
||||
# created_after: datetime
|
||||
# created_before: datetime
|
||||
# updated_after: datetime
|
||||
# updated_before: datetime
|
||||
#
|
||||
class MergeRequestsFinder < IssuableFinder
|
||||
def klass
|
||||
|
|
|
@ -48,11 +48,23 @@ class NotesFinder
|
|||
def init_collection
|
||||
if target
|
||||
notes_on_target
|
||||
elsif target_type
|
||||
notes_of_target_type
|
||||
else
|
||||
notes_of_any_type
|
||||
end
|
||||
end
|
||||
|
||||
def notes_of_target_type
|
||||
notes = notes_for_type(target_type)
|
||||
|
||||
search(notes)
|
||||
end
|
||||
|
||||
def target_type
|
||||
@params[:target_type]
|
||||
end
|
||||
|
||||
def notes_of_any_type
|
||||
types = %w(commit issue merge_request snippet)
|
||||
note_relations = types.map { |t| notes_for_type(t) }
|
||||
|
|
|
@ -110,10 +110,6 @@ class TodosFinder
|
|||
ids
|
||||
end
|
||||
|
||||
def projects(items)
|
||||
ProjectsFinder.new(current_user: current_user, project_ids_relation: project_ids(items)).execute
|
||||
end
|
||||
|
||||
def type?
|
||||
type.present? && %w(Issue MergeRequest).include?(type)
|
||||
end
|
||||
|
@ -152,13 +148,14 @@ class TodosFinder
|
|||
|
||||
def by_project(items)
|
||||
if project?
|
||||
items = items.where(project: project)
|
||||
items.where(project: project)
|
||||
else
|
||||
item_projects = projects(items)
|
||||
items = items.merge(item_projects).joins(:project)
|
||||
end
|
||||
projects = Project
|
||||
.public_or_visible_to_user(current_user)
|
||||
.order_id_desc
|
||||
|
||||
items
|
||||
items.joins(:project).merge(projects)
|
||||
end
|
||||
end
|
||||
|
||||
def by_state(items)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
module LabelsHelper
|
||||
extend self
|
||||
include ActionView::Helpers::TagHelper
|
||||
|
||||
def show_label_issuables_link?(label, issuables_type, current_user: nil, project: nil)
|
||||
|
|
|
@ -11,7 +11,7 @@ module NotesHelper
|
|||
end
|
||||
|
||||
def note_supports_quick_actions?(note)
|
||||
Notes::QuickActionsService.supported?(note, current_user)
|
||||
Notes::QuickActionsService.supported?(note)
|
||||
end
|
||||
|
||||
def noteable_json(noteable)
|
||||
|
|
51
app/models/badge.rb
Normal file
51
app/models/badge.rb
Normal file
|
@ -0,0 +1,51 @@
|
|||
class Badge < ActiveRecord::Base
|
||||
# This structure sets the placeholders that the urls
|
||||
# can have. This hash also sets which action to ask when
|
||||
# the placeholder is found.
|
||||
PLACEHOLDERS = {
|
||||
'project_path' => :full_path,
|
||||
'project_id' => :id,
|
||||
'default_branch' => :default_branch,
|
||||
'commit_sha' => ->(project) { project.commit&.sha }
|
||||
}.freeze
|
||||
|
||||
# This regex is built dynamically using the keys from the PLACEHOLDER struct.
|
||||
# So, we can easily add new placeholder just by modifying the PLACEHOLDER hash.
|
||||
# This regex will build the new PLACEHOLDER_REGEX with the new information
|
||||
PLACEHOLDERS_REGEX = /(#{PLACEHOLDERS.keys.join('|')})/.freeze
|
||||
|
||||
default_scope { order_created_at_asc }
|
||||
|
||||
scope :order_created_at_asc, -> { reorder(created_at: :asc) }
|
||||
|
||||
validates :link_url, :image_url, url_placeholder: { protocols: %w(http https), placeholder_regex: PLACEHOLDERS_REGEX }
|
||||
validates :type, presence: true
|
||||
|
||||
def rendered_link_url(project = nil)
|
||||
build_rendered_url(link_url, project)
|
||||
end
|
||||
|
||||
def rendered_image_url(project = nil)
|
||||
build_rendered_url(image_url, project)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_rendered_url(url, project = nil)
|
||||
return url unless valid? && project
|
||||
|
||||
Gitlab::StringPlaceholderReplacer.replace_string_placeholders(url, PLACEHOLDERS_REGEX) do |arg|
|
||||
replace_placeholder_action(PLACEHOLDERS[arg], project)
|
||||
end
|
||||
end
|
||||
|
||||
# The action param represents the :symbol or Proc to call in order
|
||||
# to retrieve the return value from the project.
|
||||
# This method checks if it is a Proc and use the call method, and if it is
|
||||
# a symbol just send the action
|
||||
def replace_placeholder_action(action, project)
|
||||
return unless project
|
||||
|
||||
action.is_a?(Proc) ? action.call(project) : project.public_send(action) # rubocop:disable GitlabSecurity/PublicSend
|
||||
end
|
||||
end
|
5
app/models/badges/group_badge.rb
Normal file
5
app/models/badges/group_badge.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
class GroupBadge < Badge
|
||||
belongs_to :group
|
||||
|
||||
validates :group, presence: true
|
||||
end
|
15
app/models/badges/project_badge.rb
Normal file
15
app/models/badges/project_badge.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
class ProjectBadge < Badge
|
||||
belongs_to :project
|
||||
|
||||
validates :project, presence: true
|
||||
|
||||
def rendered_link_url(project = nil)
|
||||
project ||= self.project
|
||||
super
|
||||
end
|
||||
|
||||
def rendered_image_url(project = nil)
|
||||
project ||= self.project
|
||||
super
|
||||
end
|
||||
end
|
|
@ -19,6 +19,7 @@ module Issuable
|
|||
include AfterCommitQueue
|
||||
include Sortable
|
||||
include CreatedAtFilterable
|
||||
include UpdatedAtFilterable
|
||||
|
||||
# This object is used to gather issuable meta data for displaying
|
||||
# upvotes, downvotes, notes and closing merge requests count for issues and merge requests
|
||||
|
|
12
app/models/concerns/updated_at_filterable.rb
Normal file
12
app/models/concerns/updated_at_filterable.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
module UpdatedAtFilterable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
scope :updated_before, ->(date) { where(scoped_table[:updated_at].lteq(date)) }
|
||||
scope :updated_after, ->(date) { where(scoped_table[:updated_at].gteq(date)) }
|
||||
|
||||
def self.scoped_table
|
||||
arel_table.alias(table_name)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -31,6 +31,8 @@ class Group < Namespace
|
|||
|
||||
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
||||
has_many :badges, class_name: 'GroupBadge'
|
||||
|
||||
accepts_nested_attributes_for :variables, allow_destroy: true
|
||||
|
||||
validate :visibility_level_allowed_by_projects
|
||||
|
|
|
@ -15,4 +15,8 @@ class LfsObject < ActiveRecord::Base
|
|||
.where(lfs_objects_projects: { id: nil })
|
||||
.destroy_all
|
||||
end
|
||||
|
||||
def self.calculate_oid(path)
|
||||
Digest::SHA256.file(path).hexdigest
|
||||
end
|
||||
end
|
||||
|
|
|
@ -197,10 +197,6 @@ class MergeRequestDiff < ActiveRecord::Base
|
|||
CompareService.new(project, head_commit_sha).execute(project, sha, straight: true)
|
||||
end
|
||||
|
||||
def commits_count
|
||||
super || merge_request_diff_commits.size
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_merge_request_diff_files(diffs)
|
||||
|
|
|
@ -221,6 +221,8 @@ class Project < ActiveRecord::Base
|
|||
has_one :auto_devops, class_name: 'ProjectAutoDevops'
|
||||
has_many :custom_attributes, class_name: 'ProjectCustomAttribute'
|
||||
|
||||
has_many :project_badges, class_name: 'ProjectBadge'
|
||||
|
||||
accepts_nested_attributes_for :variables, allow_destroy: true
|
||||
accepts_nested_attributes_for :project_feature, update_only: true
|
||||
accepts_nested_attributes_for :import_data
|
||||
|
@ -1766,6 +1768,17 @@ class Project < ActiveRecord::Base
|
|||
.set(import_jid, StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION)
|
||||
end
|
||||
|
||||
def badges
|
||||
return project_badges unless group
|
||||
|
||||
group_badges_rel = GroupBadge.where(group: group.self_and_ancestors)
|
||||
|
||||
union = Gitlab::SQL::Union.new([project_badges.select(:id),
|
||||
group_badges_rel.select(:id)])
|
||||
|
||||
Badge.where("id IN (#{union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def storage
|
||||
|
|
|
@ -253,7 +253,7 @@ class Repository
|
|||
# branches or tags, but we want to keep some of these commits around, for
|
||||
# example if they have comments or CI builds.
|
||||
def keep_around(sha)
|
||||
return unless sha && commit_by(oid: sha)
|
||||
return unless sha.present? && commit_by(oid: sha)
|
||||
|
||||
return if kept_around?(sha)
|
||||
|
||||
|
|
11
app/services/badges/base_service.rb
Normal file
11
app/services/badges/base_service.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
module Badges
|
||||
class BaseService
|
||||
protected
|
||||
|
||||
attr_accessor :params
|
||||
|
||||
def initialize(params = {})
|
||||
@params = params.dup
|
||||
end
|
||||
end
|
||||
end
|
12
app/services/badges/build_service.rb
Normal file
12
app/services/badges/build_service.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
module Badges
|
||||
class BuildService < Badges::BaseService
|
||||
# returns the created badge
|
||||
def execute(source)
|
||||
if source.is_a?(Group)
|
||||
GroupBadge.new(params.merge(group: source))
|
||||
else
|
||||
ProjectBadge.new(params.merge(project: source))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
10
app/services/badges/create_service.rb
Normal file
10
app/services/badges/create_service.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
module Badges
|
||||
class CreateService < Badges::BaseService
|
||||
# returns the created badge
|
||||
def execute(source)
|
||||
badge = Badges::BuildService.new(params).execute(source)
|
||||
|
||||
badge.tap { |b| b.save }
|
||||
end
|
||||
end
|
||||
end
|
12
app/services/badges/update_service.rb
Normal file
12
app/services/badges/update_service.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
module Badges
|
||||
class UpdateService < Badges::BaseService
|
||||
# returns the updated badge
|
||||
def execute(badge)
|
||||
if params.present?
|
||||
badge.update_attributes(params)
|
||||
end
|
||||
|
||||
badge
|
||||
end
|
||||
end
|
||||
end
|
|
@ -109,6 +109,10 @@ class IssuableBaseService < BaseService
|
|||
@available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute
|
||||
end
|
||||
|
||||
def handle_quick_actions_on_create(issuable)
|
||||
merge_quick_actions_into_params!(issuable)
|
||||
end
|
||||
|
||||
def merge_quick_actions_into_params!(issuable)
|
||||
original_description = params.fetch(:description, issuable.description)
|
||||
|
||||
|
@ -131,7 +135,7 @@ class IssuableBaseService < BaseService
|
|||
end
|
||||
|
||||
def create(issuable)
|
||||
merge_quick_actions_into_params!(issuable)
|
||||
handle_quick_actions_on_create(issuable)
|
||||
filter_params(issuable)
|
||||
|
||||
params.delete(:state_event)
|
||||
|
|
|
@ -24,6 +24,17 @@ module MergeRequests
|
|||
|
||||
private
|
||||
|
||||
def handle_wip_event(merge_request)
|
||||
if wip_event = params.delete(:wip_event)
|
||||
# We update the title that is provided in the params or we use the mr title
|
||||
title = params[:title] || merge_request.title
|
||||
params[:title] = case wip_event
|
||||
when 'wip' then MergeRequest.wip_title(title)
|
||||
when 'unwip' then MergeRequest.wipless_title(title)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def merge_request_metrics_service(merge_request)
|
||||
MergeRequestMetricsService.new(merge_request.metrics)
|
||||
end
|
||||
|
|
|
@ -34,6 +34,12 @@ module MergeRequests
|
|||
super
|
||||
end
|
||||
|
||||
# Override from IssuableBaseService
|
||||
def handle_quick_actions_on_create(merge_request)
|
||||
super
|
||||
handle_wip_event(merge_request)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_merge_requests_head_pipeline(merge_request)
|
||||
|
|
|
@ -98,17 +98,6 @@ module MergeRequests
|
|||
|
||||
private
|
||||
|
||||
def handle_wip_event(merge_request)
|
||||
if wip_event = params.delete(:wip_event)
|
||||
# We update the title that is provided in the params or we use the mr title
|
||||
title = params[:title] || merge_request.title
|
||||
params[:title] = case wip_event
|
||||
when 'wip' then MergeRequest.wip_title(title)
|
||||
when 'unwip' then MergeRequest.wipless_title(title)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create_branch_change_note(issuable, branch_type, old_branch, new_branch)
|
||||
SystemNoteService.change_branch(
|
||||
issuable, issuable.project, current_user, branch_type,
|
||||
|
|
|
@ -9,14 +9,12 @@ module Notes
|
|||
UPDATE_SERVICES[note.noteable_type]
|
||||
end
|
||||
|
||||
def self.supported?(note, current_user)
|
||||
noteable_update_service(note) &&
|
||||
current_user &&
|
||||
current_user.can?(:"update_#{note.to_ability_name}", note.noteable)
|
||||
def self.supported?(note)
|
||||
!!noteable_update_service(note)
|
||||
end
|
||||
|
||||
def supported?(note)
|
||||
self.class.supported?(note, current_user)
|
||||
self.class.supported?(note)
|
||||
end
|
||||
|
||||
def extract_commands(note, options = {})
|
||||
|
|
|
@ -347,9 +347,9 @@ module QuickActions
|
|||
"#{verb} this #{noun} as Work In Progress."
|
||||
end
|
||||
condition do
|
||||
issuable.persisted? &&
|
||||
issuable.respond_to?(:work_in_progress?) &&
|
||||
current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
|
||||
issuable.respond_to?(:work_in_progress?) &&
|
||||
# Allow it to mark as WIP on MR creation page _or_ through MR notes.
|
||||
(issuable.new_record? || current_user.can?(:"update_#{issuable.to_ability_name}", issuable))
|
||||
end
|
||||
command :wip do
|
||||
@updates[:wip_event] = issuable.work_in_progress? ? 'unwip' : 'wip'
|
||||
|
|
32
app/validators/url_placeholder_validator.rb
Normal file
32
app/validators/url_placeholder_validator.rb
Normal file
|
@ -0,0 +1,32 @@
|
|||
# UrlValidator
|
||||
#
|
||||
# Custom validator for URLs.
|
||||
#
|
||||
# By default, only URLs for the HTTP(S) protocols will be considered valid.
|
||||
# Provide a `:protocols` option to configure accepted protocols.
|
||||
#
|
||||
# Also, this validator can help you validate urls with placeholders inside.
|
||||
# Usually, if you have a url like 'http://www.example.com/%{project_path}' the
|
||||
# URI parser will reject that URL format. Provide a `:placeholder_regex` option
|
||||
# to configure accepted placeholders.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# class User < ActiveRecord::Base
|
||||
# validates :personal_url, url: true
|
||||
#
|
||||
# validates :ftp_url, url: { protocols: %w(ftp) }
|
||||
#
|
||||
# validates :git_url, url: { protocols: %w(http https ssh git) }
|
||||
#
|
||||
# validates :placeholder_url, url: { placeholder_regex: /(project_path|project_id|default_branch)/ }
|
||||
# end
|
||||
#
|
||||
class UrlPlaceholderValidator < UrlValidator
|
||||
def validate_each(record, attribute, value)
|
||||
placeholder_regex = self.options[:placeholder_regex]
|
||||
value = value.gsub(/%{#{placeholder_regex}}/, 'foo') if placeholder_regex && value
|
||||
|
||||
super(record, attribute, value)
|
||||
end
|
||||
end
|
|
@ -23,6 +23,12 @@
|
|||
- deleted_message = s_('ForkedFromProjectPath|Forked from %{project_name} (deleted)')
|
||||
= deleted_message % { project_name: fork_source_name(@project) }
|
||||
|
||||
.project-badges
|
||||
- @project.badges.each do |badge|
|
||||
- badge_link_url = badge.rendered_link_url(@project)
|
||||
%a{ href: badge_link_url, target: '_blank', rel: 'noopener noreferrer' }
|
||||
%img{ src: badge.rendered_image_url(@project), alt: badge_link_url }
|
||||
|
||||
.project-repo-buttons
|
||||
.count-buttons
|
||||
= render 'projects/buttons/star'
|
||||
|
|
|
@ -14,25 +14,25 @@
|
|||
= link_to search_filter_path(scope: 'issues') do
|
||||
Issues
|
||||
%span.badge
|
||||
= @search_results.issues_count
|
||||
= limited_count(@search_results.limited_issues_count)
|
||||
- if project_search_tabs?(:merge_requests)
|
||||
%li{ class: active_when(@scope == 'merge_requests') }
|
||||
= link_to search_filter_path(scope: 'merge_requests') do
|
||||
Merge requests
|
||||
%span.badge
|
||||
= @search_results.merge_requests_count
|
||||
= limited_count(@search_results.limited_merge_requests_count)
|
||||
- if project_search_tabs?(:milestones)
|
||||
%li{ class: active_when(@scope == 'milestones') }
|
||||
= link_to search_filter_path(scope: 'milestones') do
|
||||
Milestones
|
||||
%span.badge
|
||||
= @search_results.milestones_count
|
||||
= limited_count(@search_results.limited_milestones_count)
|
||||
- if project_search_tabs?(:notes)
|
||||
%li{ class: active_when(@scope == 'notes') }
|
||||
= link_to search_filter_path(scope: 'notes') do
|
||||
Comments
|
||||
%span.badge
|
||||
= @search_results.notes_count
|
||||
= limited_count(@search_results.limited_notes_count)
|
||||
- if project_search_tabs?(:wiki)
|
||||
%li{ class: active_when(@scope == 'wiki_blobs') }
|
||||
= link_to search_filter_path(scope: 'wiki_blobs') do
|
||||
|
|
|
@ -30,10 +30,9 @@ class ProcessCommitWorker
|
|||
end
|
||||
|
||||
def process_commit_message(project, commit, user, author, default = false)
|
||||
# this is a GitLab generated commit message, ignore it.
|
||||
return if commit.merged_merge_request?(user)
|
||||
|
||||
closed_issues = default ? commit.closes_issues(user) : []
|
||||
# Ignore closing references from GitLab-generated commit messages.
|
||||
find_closing_issues = default && !commit.merged_merge_request?(user)
|
||||
closed_issues = find_closing_issues ? commit.closes_issues(user) : []
|
||||
|
||||
close_issues(project, user, author, commit, closed_issues) if closed_issues.any?
|
||||
commit.create_cross_references!(author, closed_issues)
|
||||
|
|
5
changelogs/unreleased/41616-api-issues-between-date.yml
Normal file
5
changelogs/unreleased/41616-api-issues-between-date.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Adds updated_at filter to issues and merge_requests API
|
||||
merge_request: 17417
|
||||
author: Jacopo Beschi @jacopo-beschi
|
||||
type: added
|
5
changelogs/unreleased/41719-mr-title-fix.yml
Normal file
5
changelogs/unreleased/41719-mr-title-fix.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Render htmlentities correctly for links not supported by Rinku
|
||||
merge_request:
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add search param to Branches API
|
||||
merge_request: 17005
|
||||
author: bunufi
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix quick actions for users who cannot update issues and merge requests
|
||||
merge_request: 17482
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Stop loading spinner on error of milestone update on issue
|
||||
merge_request: 17507
|
||||
author: Takuya Noguchi
|
||||
type: fixed
|
5
changelogs/unreleased/an-workhorse-3-8-0.yml
Normal file
5
changelogs/unreleased/an-workhorse-3-8-0.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Upgrade Workhorse to version 3.8.0 to support structured logging
|
||||
merge_request:
|
||||
author:
|
||||
type: other
|
5
changelogs/unreleased/ee-4862-verify-file-checksums.yml
Normal file
5
changelogs/unreleased/ee-4862-verify-file-checksums.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Foreground verification of uploads and LFS objects
|
||||
merge_request: 17402
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Count comments on diffs as contributions for the contributions calendar
|
||||
merge_request: 17418
|
||||
author: Riccardo Padovani
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Implemented badge API endpoints
|
||||
merge_request: 17082
|
||||
author:
|
||||
type: added
|
6
changelogs/unreleased/jprovazn-scoped-limit.yml
Normal file
6
changelogs/unreleased/jprovazn-scoped-limit.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: Optimize search queries on the search page by setting a limit for matching
|
||||
records in project scope
|
||||
merge_request:
|
||||
author:
|
||||
type: performance
|
5
changelogs/unreleased/kp-label-select-vue.yml
Normal file
5
changelogs/unreleased/kp-label-select-vue.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Port Labels Select dropdown to Vue
|
||||
merge_request: 17411
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Don't use ProjectsFinder in TodosFinder
|
||||
merge_request:
|
||||
author:
|
||||
type: performance
|
4
changelogs/unreleased/wip-new-mr-cmd.yml
Normal file
4
changelogs/unreleased/wip-new-mr-cmd.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
title: Port /wip quick action command to Merge Request creation (on description)
|
||||
merge_request: 17463
|
||||
author: Adam Pahlevi
|
||||
type: added
|
|
@ -23,5 +23,6 @@ warmup do |app|
|
|||
end
|
||||
|
||||
map ENV['RAILS_RELATIVE_URL_ROOT'] || "/" do
|
||||
use Gitlab::Middleware::ReleaseEnv
|
||||
run Gitlab::Application
|
||||
end
|
||||
|
|
|
@ -26,6 +26,7 @@ module Gitlab
|
|||
# This is a nice reference article on autoloading/eager loading:
|
||||
# http://blog.arkency.com/2014/11/dont-forget-about-eager-load-when-extending-autoload
|
||||
config.eager_load_paths.push(*%W[#{config.root}/lib
|
||||
#{config.root}/app/models/badges
|
||||
#{config.root}/app/models/hooks
|
||||
#{config.root}/app/models/members
|
||||
#{config.root}/app/models/project_services
|
||||
|
|
|
@ -18,13 +18,26 @@ module Sidekiq
|
|||
%i(perform_async perform_at perform_in).each do |name|
|
||||
define_method(name) do |*args|
|
||||
if !Sidekiq::Worker.skip_transaction_check && AfterCommitQueue.inside_transaction?
|
||||
raise Sidekiq::Worker::EnqueueFromTransactionError, <<~MSG
|
||||
begin
|
||||
raise Sidekiq::Worker::EnqueueFromTransactionError, <<~MSG
|
||||
`#{self}.#{name}` cannot be called inside a transaction as this can lead to
|
||||
race conditions when the worker runs before the transaction is committed and
|
||||
tries to access a model that has not been saved yet.
|
||||
|
||||
Use an `after_commit` hook, or include `AfterCommitQueue` and use a `run_after_commit` block instead.
|
||||
MSG
|
||||
MSG
|
||||
rescue Sidekiq::Worker::EnqueueFromTransactionError => e
|
||||
if Rails.env.production?
|
||||
Rails.logger.error(e.message)
|
||||
|
||||
if Gitlab::Sentry.enabled?
|
||||
Gitlab::Sentry.context
|
||||
Raven.capture_exception(e)
|
||||
end
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
super(*args)
|
||||
|
|
17
db/migrate/20180214093516_create_badges.rb
Normal file
17
db/migrate/20180214093516_create_badges.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
class CreateBadges < ActiveRecord::Migration
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
create_table :badges do |t|
|
||||
t.string :link_url, null: false
|
||||
t.string :image_url, null: false
|
||||
t.references :project, index: true, foreign_key: { on_delete: :cascade }, null: true
|
||||
t.integer :group_id, index: true, null: true
|
||||
t.string :type, null: false
|
||||
|
||||
t.timestamps_with_timezone null: false
|
||||
end
|
||||
|
||||
add_foreign_key :badges, :namespaces, column: :group_id, on_delete: :cascade
|
||||
end
|
||||
end
|
14
db/migrate/20180304204842_clean_commits_count_migration.rb
Normal file
14
db/migrate/20180304204842_clean_commits_count_migration.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
class CleanCommitsCountMigration < ActiveRecord::Migration
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
Gitlab::BackgroundMigration.steal('AddMergeRequestDiffCommitsCount')
|
||||
end
|
||||
|
||||
def down
|
||||
end
|
||||
end
|
17
db/schema.rb
17
db/schema.rb
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20180301084653) do
|
||||
ActiveRecord::Schema.define(version: 20180304204842) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -183,6 +183,19 @@ ActiveRecord::Schema.define(version: 20180301084653) do
|
|||
add_index "award_emoji", ["awardable_type", "awardable_id"], name: "index_award_emoji_on_awardable_type_and_awardable_id", using: :btree
|
||||
add_index "award_emoji", ["user_id", "name"], name: "index_award_emoji_on_user_id_and_name", using: :btree
|
||||
|
||||
create_table "badges", force: :cascade do |t|
|
||||
t.string "link_url", null: false
|
||||
t.string "image_url", null: false
|
||||
t.integer "project_id"
|
||||
t.integer "group_id"
|
||||
t.string "type", null: false
|
||||
t.datetime_with_timezone "created_at", null: false
|
||||
t.datetime_with_timezone "updated_at", null: false
|
||||
end
|
||||
|
||||
add_index "badges", ["group_id"], name: "index_badges_on_group_id", using: :btree
|
||||
add_index "badges", ["project_id"], name: "index_badges_on_project_id", using: :btree
|
||||
|
||||
create_table "boards", force: :cascade do |t|
|
||||
t.integer "project_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
|
@ -1969,6 +1982,8 @@ ActiveRecord::Schema.define(version: 20180301084653) do
|
|||
add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
|
||||
add_index "web_hooks", ["type"], name: "index_web_hooks_on_type", using: :btree
|
||||
|
||||
add_foreign_key "badges", "namespaces", column: "group_id", on_delete: :cascade
|
||||
add_foreign_key "badges", "projects", on_delete: :cascade
|
||||
add_foreign_key "boards", "projects", name: "fk_f15266b5f9", on_delete: :cascade
|
||||
add_foreign_key "chat_teams", "namespaces", on_delete: :cascade
|
||||
add_foreign_key "ci_build_trace_section_names", "projects", on_delete: :cascade
|
||||
|
|
|
@ -78,34 +78,41 @@ Example output:
|
|||
|
||||
## Uploaded Files Integrity
|
||||
|
||||
The uploads check Rake task will loop through all uploads in the database
|
||||
and run two checks to determine the integrity of each file:
|
||||
Various types of file can be uploaded to a GitLab installation by users.
|
||||
Checksums are generated and stored in the database upon upload, and integrity
|
||||
checks using those checksums can be run. These checks also detect missing files.
|
||||
|
||||
1. Check if the file exist on the file system.
|
||||
1. Check if the checksum of the file on the file system matches the checksum in the database.
|
||||
Currently, integrity checks are supported for the following types of file:
|
||||
|
||||
* LFS objects
|
||||
* User uploads
|
||||
|
||||
**Omnibus Installation**
|
||||
|
||||
```
|
||||
sudo gitlab-rake gitlab:lfs:check
|
||||
sudo gitlab-rake gitlab:uploads:check
|
||||
```
|
||||
|
||||
**Source Installation**
|
||||
|
||||
```bash
|
||||
sudo -u git -H bundle exec rake gitlab:lfs:check RAILS_ENV=production
|
||||
sudo -u git -H bundle exec rake gitlab:uploads:check RAILS_ENV=production
|
||||
```
|
||||
|
||||
This task also accepts some environment variables which you can use to override
|
||||
These tasks also accept some environment variables which you can use to override
|
||||
certain values:
|
||||
|
||||
Variable | Type | Description
|
||||
-------- | ---- | -----------
|
||||
`BATCH` | integer | Specifies the size of the batch. Defaults to 200.
|
||||
`ID_FROM` | integer | Specifies the ID to start from, inclusive of the value.
|
||||
`ID_TO` | integer | Specifies the ID value to end at, inclusive of the value.
|
||||
Variable | Type | Description
|
||||
--------- | ------- | -----------
|
||||
`BATCH` | integer | Specifies the size of the batch. Defaults to 200.
|
||||
`ID_FROM` | integer | Specifies the ID to start from, inclusive of the value.
|
||||
`ID_TO` | integer | Specifies the ID value to end at, inclusive of the value.
|
||||
`VERBOSE` | boolean | Causes failures to be listed individually, rather than being summarized.
|
||||
|
||||
```bash
|
||||
sudo gitlab-rake gitlab:lfs:check BATCH=100 ID_FROM=50 ID_TO=250
|
||||
sudo gitlab-rake gitlab:uploads:check BATCH=100 ID_FROM=50 ID_TO=250
|
||||
```
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ following locations:
|
|||
- [GitLab CI Config templates](templates/gitlab_ci_ymls.md)
|
||||
- [Groups](groups.md)
|
||||
- [Group Access Requests](access_requests.md)
|
||||
- [Group Badges](group_badges.md)
|
||||
- [Group Members](members.md)
|
||||
- [Issues](issues.md)
|
||||
- [Issue Boards](boards.md)
|
||||
|
@ -43,6 +44,7 @@ following locations:
|
|||
- [Pipeline Schedules](pipeline_schedules.md)
|
||||
- [Projects](projects.md) including setting Webhooks
|
||||
- [Project Access Requests](access_requests.md)
|
||||
- [Project Badges](project_badges.md)
|
||||
- [Project import/export](project_import_export.md)
|
||||
- [Project Members](members.md)
|
||||
- [Project Snippets](project_snippets.md)
|
||||
|
|
|
@ -13,6 +13,7 @@ GET /projects/:id/repository/branches
|
|||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `search` | string | no | Return list of branches matching the search criteria. |
|
||||
|
||||
```bash
|
||||
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/repository/branches
|
||||
|
|
191
doc/api/group_badges.md
Normal file
191
doc/api/group_badges.md
Normal file
|
@ -0,0 +1,191 @@
|
|||
# Group badges API
|
||||
|
||||
## Placeholder tokens
|
||||
|
||||
Badges support placeholders that will be replaced in real time in both the link and image URL. The allowed placeholders are:
|
||||
|
||||
- **%{project_path}**: will be replaced by the project path.
|
||||
- **%{project_id}**: will be replaced by the project id.
|
||||
- **%{default_branch}**: will be replaced by the project default branch.
|
||||
- **%{commit_sha}**: will be replaced by the last project's commit sha.
|
||||
|
||||
Because these enpoints aren't inside a project's context, the information used to replace the placeholders will be
|
||||
from the first group's project by creation date. If the group hasn't got any project the original URL with the placeholders will be returned.
|
||||
|
||||
## List all badges of a group
|
||||
|
||||
Gets a list of a group's badges.
|
||||
|
||||
```
|
||||
GET /groups/:id/badges
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
|
||||
```bash
|
||||
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/badges
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}",
|
||||
"image_url": "https://shields.io/my/badge",
|
||||
"rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master",
|
||||
"rendered_image_url": "https://shields.io/my/badge",
|
||||
"kind": "group"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}",
|
||||
"image_url": "https://shields.io/my/badge",
|
||||
"rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master",
|
||||
"rendered_image_url": "https://shields.io/my/badge",
|
||||
"kind": "group"
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
## Get a badge of a group
|
||||
|
||||
Gets a badge of a group.
|
||||
|
||||
```
|
||||
GET /groups/:id/badges/:badge_id
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `badge_id` | integer | yes | The badge ID |
|
||||
|
||||
```bash
|
||||
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/badges/:badge_id
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}",
|
||||
"image_url": "https://shields.io/my/badge",
|
||||
"rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master",
|
||||
"rendered_image_url": "https://shields.io/my/badge",
|
||||
"kind": "group"
|
||||
}
|
||||
```
|
||||
|
||||
## Add a badge to a group
|
||||
|
||||
Adds a badge to a group.
|
||||
|
||||
```
|
||||
POST /groups/:id/badges
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `link_url` | string | yes | URL of the badge link |
|
||||
| `image_url` | string | yes | URL of the badge image |
|
||||
|
||||
```bash
|
||||
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "link_url=https://gitlab.com/gitlab-org/gitlab-ce/commits/master&image_url=https://shields.io/my/badge1&position=0" https://gitlab.example.com/api/v4/groups/:id/badges
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master",
|
||||
"image_url": "https://shields.io/my/badge1",
|
||||
"rendered_link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master",
|
||||
"rendered_image_url": "https://shields.io/my/badge1",
|
||||
"kind": "group"
|
||||
}
|
||||
```
|
||||
|
||||
## Edit a badge of a group
|
||||
|
||||
Updates a badge of a group.
|
||||
|
||||
```
|
||||
PUT /groups/:id/badges/:badge_id
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `badge_id` | integer | yes | The badge ID |
|
||||
| `link_url` | string | no | URL of the badge link |
|
||||
| `image_url` | string | no | URL of the badge image |
|
||||
|
||||
```bash
|
||||
curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/badges/:badge_id
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master",
|
||||
"image_url": "https://shields.io/my/badge",
|
||||
"rendered_link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master",
|
||||
"rendered_image_url": "https://shields.io/my/badge",
|
||||
"kind": "group"
|
||||
}
|
||||
```
|
||||
|
||||
## Remove a badge from a group
|
||||
|
||||
Removes a badge from a group.
|
||||
|
||||
```
|
||||
DELETE /groups/:id/badges/:badge_id
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `badge_id` | integer | yes | The badge ID |
|
||||
|
||||
```bash
|
||||
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/badges/:badge_id
|
||||
```
|
||||
|
||||
## Preview a badge from a group
|
||||
|
||||
Returns how the `link_url` and `image_url` final URLs would be after resolving the placeholder interpolation.
|
||||
|
||||
```
|
||||
GET /groups/:id/badges/render
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `link_url` | string | yes | URL of the badge link|
|
||||
| `image_url` | string | yes | URL of the badge image |
|
||||
|
||||
```bash
|
||||
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/badges/render?link_url=http%3A%2F%2Fexample.com%2Fci_status.svg%3Fproject%3D%25%7Bproject_path%7D%26ref%3D%25%7Bdefault_branch%7D&image_url=https%3A%2F%2Fshields.io%2Fmy%2Fbadge
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}",
|
||||
"image_url": "https://shields.io/my/badge",
|
||||
"rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master",
|
||||
"rendered_image_url": "https://shields.io/my/badge",
|
||||
}
|
||||
```
|
|
@ -525,3 +525,7 @@ And to switch pages add:
|
|||
```
|
||||
|
||||
[ce-15142]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15142
|
||||
|
||||
## Group badges
|
||||
|
||||
Read more in the [Group Badges](group_badges.md) documentation.
|
||||
|
|
|
@ -46,6 +46,10 @@ GET /issues?my_reaction_emoji=star
|
|||
| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
|
||||
| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
|
||||
| `search` | string | no | Search issues against their `title` and `description` |
|
||||
| `created_after` | datetime | no | Return issues created on or after the given time |
|
||||
| `created_before` | datetime | no | Return issues created on or before the given time |
|
||||
| `updated_after` | datetime | no | Return issues updated on or after the given time |
|
||||
| `updated_before` | datetime | no | Return issues updated on or before the given time |
|
||||
|
||||
```bash
|
||||
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/issues
|
||||
|
@ -152,6 +156,10 @@ GET /groups/:id/issues?my_reaction_emoji=star
|
|||
| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
|
||||
| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
|
||||
| `search` | string | no | Search group issues against their `title` and `description` |
|
||||
| `created_after` | datetime | no | Return issues created on or after the given time |
|
||||
| `created_before` | datetime | no | Return issues created on or before the given time |
|
||||
| `updated_after` | datetime | no | Return issues updated on or after the given time |
|
||||
| `updated_before` | datetime | no | Return issues updated on or before the given time |
|
||||
|
||||
|
||||
```bash
|
||||
|
@ -259,8 +267,10 @@ GET /projects/:id/issues?my_reaction_emoji=star
|
|||
| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
|
||||
| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
|
||||
| `search` | string | no | Search project issues against their `title` and `description` |
|
||||
| `created_after` | datetime | no | Return issues created after the given time (inclusive) |
|
||||
| `created_before` | datetime | no | Return issues created before the given time (inclusive) |
|
||||
| `created_after` | datetime | no | Return issues created on or after the given time |
|
||||
| `created_before` | datetime | no | Return issues created on or before the given time |
|
||||
| `updated_after` | datetime | no | Return issues updated on or after the given time |
|
||||
| `updated_before` | datetime | no | Return issues updated on or before the given time |
|
||||
|
||||
```bash
|
||||
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues
|
||||
|
|
|
@ -41,8 +41,10 @@ Parameters:
|
|||
| `milestone` | string | no | Return merge requests for a specific milestone |
|
||||
| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request |
|
||||
| `labels` | string | no | Return merge requests matching a comma separated list of labels |
|
||||
| `created_after` | datetime | no | Return merge requests created after the given time (inclusive) |
|
||||
| `created_before` | datetime | no | Return merge requests created before the given time (inclusive) |
|
||||
| `created_after` | datetime | no | Return merge requests created on or after the given time |
|
||||
| `created_before` | datetime | no | Return merge requests created on or before the given time |
|
||||
| `updated_after` | datetime | no | Return merge requests updated on or after the given time |
|
||||
| `updated_before` | datetime | no | Return merge requests updated on or before the given time |
|
||||
| `scope` | string | no | Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`. Defaults to `created-by-me` |
|
||||
| `author_id` | integer | no | Returns merge requests created by the given user `id`. Combine with `scope=all` or `scope=assigned-to-me` |
|
||||
| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` |
|
||||
|
@ -158,8 +160,10 @@ Parameters:
|
|||
| `milestone` | string | no | Return merge requests for a specific milestone |
|
||||
| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request |
|
||||
| `labels` | string | no | Return merge requests matching a comma separated list of labels |
|
||||
| `created_after` | datetime | no | Return merge requests created after the given time (inclusive) |
|
||||
| `created_before` | datetime | no | Return merge requests created before the given time (inclusive) |
|
||||
| `created_after` | datetime | no | Return merge requests created on or after the given time |
|
||||
| `created_before` | datetime | no | Return merge requests created on or before the given time |
|
||||
| `updated_after` | datetime | no | Return merge requests updated on or after the given time |
|
||||
| `updated_before` | datetime | no | Return merge requests updated on or before the given time |
|
||||
| `scope` | string | no | Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all` _([Introduced][ce-13060] in GitLab 9.5)_ |
|
||||
| `author_id` | integer | no | Returns merge requests created by the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ |
|
||||
| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ |
|
||||
|
@ -494,6 +498,8 @@ Parameters:
|
|||
|
||||
## List MR pipelines
|
||||
|
||||
> [Introduced][ce-15454] in GitLab 10.5.0.
|
||||
|
||||
Get a list of merge request pipelines.
|
||||
|
||||
```
|
||||
|
@ -1449,3 +1455,4 @@ Example response:
|
|||
|
||||
[ce-13060]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13060
|
||||
[ce-14016]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14016
|
||||
[ce-15454]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15454
|
||||
|
|
188
doc/api/project_badges.md
Normal file
188
doc/api/project_badges.md
Normal file
|
@ -0,0 +1,188 @@
|
|||
# Project badges API
|
||||
|
||||
## Placeholder tokens
|
||||
|
||||
Badges support placeholders that will be replaced in real time in both the link and image URL. The allowed placeholders are:
|
||||
|
||||
- **%{project_path}**: will be replaced by the project path.
|
||||
- **%{project_id}**: will be replaced by the project id.
|
||||
- **%{default_branch}**: will be replaced by the project default branch.
|
||||
- **%{commit_sha}**: will be replaced by the last project's commit sha.
|
||||
|
||||
## List all badges of a project
|
||||
|
||||
Gets a list of a project's badges and its group badges.
|
||||
|
||||
```
|
||||
GET /projects/:id/badges
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
|
||||
```bash
|
||||
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/badges
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}",
|
||||
"image_url": "https://shields.io/my/badge",
|
||||
"rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master",
|
||||
"rendered_image_url": "https://shields.io/my/badge",
|
||||
"kind": "project"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}",
|
||||
"image_url": "https://shields.io/my/badge",
|
||||
"rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master",
|
||||
"rendered_image_url": "https://shields.io/my/badge",
|
||||
"kind": "group"
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
## Get a badge of a project
|
||||
|
||||
Gets a badge of a project.
|
||||
|
||||
```
|
||||
GET /projects/:id/badges/:badge_id
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `badge_id` | integer | yes | The badge ID |
|
||||
|
||||
```bash
|
||||
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/badges/:badge_id
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}",
|
||||
"image_url": "https://shields.io/my/badge",
|
||||
"rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master",
|
||||
"rendered_image_url": "https://shields.io/my/badge",
|
||||
"kind": "project"
|
||||
}
|
||||
```
|
||||
|
||||
## Add a badge to a project
|
||||
|
||||
Adds a badge to a project.
|
||||
|
||||
```
|
||||
POST /projects/:id/badges
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project ](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `link_url` | string | yes | URL of the badge link |
|
||||
| `image_url` | string | yes | URL of the badge image |
|
||||
|
||||
```bash
|
||||
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "link_url=https://gitlab.com/gitlab-org/gitlab-ce/commits/master&image_url=https://shields.io/my/badge1&position=0" https://gitlab.example.com/api/v4/projects/:id/badges
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master",
|
||||
"image_url": "https://shields.io/my/badge1",
|
||||
"rendered_link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master",
|
||||
"rendered_image_url": "https://shields.io/my/badge1",
|
||||
"kind": "project"
|
||||
}
|
||||
```
|
||||
|
||||
## Edit a badge of a project
|
||||
|
||||
Updates a badge of a project.
|
||||
|
||||
```
|
||||
PUT /projects/:id/badges/:badge_id
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `badge_id` | integer | yes | The badge ID |
|
||||
| `link_url` | string | no | URL of the badge link |
|
||||
| `image_url` | string | no | URL of the badge image |
|
||||
|
||||
```bash
|
||||
curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/badges/:badge_id
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master",
|
||||
"image_url": "https://shields.io/my/badge",
|
||||
"rendered_link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master",
|
||||
"rendered_image_url": "https://shields.io/my/badge",
|
||||
"kind": "project"
|
||||
}
|
||||
```
|
||||
|
||||
## Remove a badge from a project
|
||||
|
||||
Removes a badge from a project. Only project's badges will be removed by using this endpoint.
|
||||
|
||||
```
|
||||
DELETE /projects/:id/badges/:badge_id
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `badge_id` | integer | yes | The badge ID |
|
||||
|
||||
```bash
|
||||
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/badges/:badge_id
|
||||
```
|
||||
|
||||
## Preview a badge from a project
|
||||
|
||||
Returns how the `link_url` and `image_url` final URLs would be after resolving the placeholder interpolation.
|
||||
|
||||
```
|
||||
GET /projects/:id/badges/render
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `link_url` | string | yes | URL of the badge link|
|
||||
| `image_url` | string | yes | URL of the badge image |
|
||||
|
||||
```bash
|
||||
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/badges/render?link_url=http%3A%2F%2Fexample.com%2Fci_status.svg%3Fproject%3D%25%7Bproject_path%7D%26ref%3D%25%7Bdefault_branch%7D&image_url=https%3A%2F%2Fshields.io%2Fmy%2Fbadge
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}",
|
||||
"image_url": "https://shields.io/my/badge",
|
||||
"rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master",
|
||||
"rendered_image_url": "https://shields.io/my/badge",
|
||||
}
|
||||
```
|
|
@ -1340,3 +1340,7 @@ Read more in the [Project import/export](project_import_export.md) documentation
|
|||
## Project members
|
||||
|
||||
Read more in the [Project members](members.md) documentation.
|
||||
|
||||
## Project badges
|
||||
|
||||
Read more in the [Project Badges](project_badges.md) documentation.
|
||||
|
|
|
@ -35,8 +35,8 @@ to clipboard step.
|
|||
If you don't see the string or would like to generate a SSH key pair with a
|
||||
custom name continue onto the next step.
|
||||
|
||||
>
|
||||
**Note:** Public SSH key may also be named as follows:
|
||||
Note that Public SSH key may also be named as follows:
|
||||
|
||||
- `id_dsa.pub`
|
||||
- `id_ecdsa.pub`
|
||||
- `id_ed25519.pub`
|
||||
|
@ -73,7 +73,7 @@ custom name continue onto the next step.
|
|||
key pair, but it is not required and you can skip creating a password by
|
||||
pressing enter.
|
||||
|
||||
>**Note:**
|
||||
NOTE: **Note:**
|
||||
If you want to change the password of your SSH key pair, you can use
|
||||
`ssh-keygen -p <keyname>`.
|
||||
|
||||
|
@ -162,11 +162,13 @@ That's why it needs to uniquely map to a single user.
|
|||
|
||||
## Deploy keys
|
||||
|
||||
### Per-repository deploy keys
|
||||
|
||||
Deploy keys allow read-only or read-write (if enabled) access to one or
|
||||
multiple projects with a single SSH key pair.
|
||||
|
||||
This is really useful for cloning repositories to your Continuous
|
||||
Integration (CI) server. By using deploy keys, you don't have to setup a
|
||||
Integration (CI) server. By using deploy keys, you don't have to set up a
|
||||
dummy user account.
|
||||
|
||||
If you are a project master or owner, you can add a deploy key in the
|
||||
|
@ -185,6 +187,47 @@ a group.
|
|||
Deploy keys can be shared between projects, you just need to add them to each
|
||||
project.
|
||||
|
||||
### Global shared deploy keys
|
||||
|
||||
Global Shared Deploy keys allow read-only or read-write (if enabled) access to
|
||||
be configured on any repository in the entire GitLab installation.
|
||||
|
||||
This is really useful for integrating repositories to secured, shared Continuous
|
||||
Integration (CI) services or other shared services.
|
||||
GitLab administrators can set up the Global Shared Deploy key in GitLab and
|
||||
add the private key to any shared systems. Individual repositories opt into
|
||||
exposing their repsitory using these keys when a project masters (or higher)
|
||||
authorizes a Global Shared Deploy key to be used with their project.
|
||||
|
||||
Global Shared Keys can provide greater security compared to Per-Project Deploy
|
||||
Keys since an administrator of the target integrated system is the only one
|
||||
who needs to know and configure the private key.
|
||||
|
||||
GitLab administrators set up Global Deploy keys in the Admin area under the
|
||||
section **Deploy Keys**. Ensure keys have a meaningful title as that will be
|
||||
the primary way for project masters and owners to identify the correct Global
|
||||
Deploy key to add. For instance, if the key gives access to a SaaS CI instance,
|
||||
use the name of that service in the key name if that is all it is used for.
|
||||
When creating Global Shared Deploy keys, give some thought to the granularity
|
||||
of keys - they could be of very narrow usage such as just a specific service or
|
||||
of broader usage for something like "Anywhere you need to give read access to
|
||||
your repository".
|
||||
|
||||
Once a GitLab administrator adds the Global Deployment key, project masters
|
||||
and owners can add it in project's **Settings > Repository** section by expanding the
|
||||
**Deploy Key** section and clicking **Enable** next to the appropriate key listed
|
||||
under **Public deploy keys available to any project**.
|
||||
|
||||
NOTE: **Note:**
|
||||
The heading **Public deploy keys available to any project** only appears
|
||||
if there is at least one Global Deploy Key configured.
|
||||
|
||||
CAUTION: **Warning:**
|
||||
Defining Global Deploy Keys does not expose any given repository via
|
||||
the key until that respository adds the Global Deploy Key to their project.
|
||||
In this way the Global Deploy Keys enable access by other systems, but do
|
||||
not implicitly give any access just by setting them up.
|
||||
|
||||
## Applications
|
||||
|
||||
### Eclipse
|
||||
|
|
|
@ -31,7 +31,8 @@ with all their related data and be moved into a new GitLab instance.
|
|||
|
||||
| GitLab version | Import/Export version |
|
||||
| ---------------- | --------------------- |
|
||||
| 10.4 to current | 0.2.2 |
|
||||
| 10.6 to current | 0.2.3 |
|
||||
| 10.4 | 0.2.2 |
|
||||
| 10.3 | 0.2.1 |
|
||||
| 10.0 | 0.2.0 |
|
||||
| 9.4.0 | 0.1.8 |
|
||||
|
|
|
@ -108,6 +108,7 @@ module API
|
|||
mount ::API::AccessRequests
|
||||
mount ::API::Applications
|
||||
mount ::API::AwardEmoji
|
||||
mount ::API::Badges
|
||||
mount ::API::Boards
|
||||
mount ::API::Branches
|
||||
mount ::API::BroadcastMessages
|
||||
|
|
134
lib/api/badges.rb
Normal file
134
lib/api/badges.rb
Normal file
|
@ -0,0 +1,134 @@
|
|||
module API
|
||||
class Badges < Grape::API
|
||||
include PaginationParams
|
||||
|
||||
before { authenticate_non_get! }
|
||||
|
||||
helpers ::API::Helpers::BadgesHelpers
|
||||
|
||||
helpers do
|
||||
def find_source_if_admin(source_type)
|
||||
source = find_source(source_type, params[:id])
|
||||
|
||||
authorize_admin_source!(source_type, source)
|
||||
|
||||
source
|
||||
end
|
||||
end
|
||||
|
||||
%w[group project].each do |source_type|
|
||||
params do
|
||||
requires :id, type: String, desc: "The ID of a #{source_type}"
|
||||
end
|
||||
resource source_type.pluralize, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
|
||||
desc "Gets a list of #{source_type} badges viewable by the authenticated user." do
|
||||
detail 'This feature was introduced in GitLab 10.6.'
|
||||
success Entities::Badge
|
||||
end
|
||||
params do
|
||||
use :pagination
|
||||
end
|
||||
get ":id/badges" do
|
||||
source = find_source(source_type, params[:id])
|
||||
|
||||
present_badges(source, paginate(source.badges))
|
||||
end
|
||||
|
||||
desc "Preview a badge from a #{source_type}." do
|
||||
detail 'This feature was introduced in GitLab 10.6.'
|
||||
success Entities::BasicBadgeDetails
|
||||
end
|
||||
params do
|
||||
requires :link_url, type: String, desc: 'URL of the badge link'
|
||||
requires :image_url, type: String, desc: 'URL of the badge image'
|
||||
end
|
||||
get ":id/badges/render" do
|
||||
authenticate!
|
||||
|
||||
source = find_source_if_admin(source_type)
|
||||
|
||||
badge = ::Badges::BuildService.new(declared_params(include_missing: false))
|
||||
.execute(source)
|
||||
|
||||
if badge.valid?
|
||||
present_badges(source, badge, with: Entities::BasicBadgeDetails)
|
||||
else
|
||||
render_validation_error!(badge)
|
||||
end
|
||||
end
|
||||
|
||||
desc "Gets a badge of a #{source_type}." do
|
||||
detail 'This feature was introduced in GitLab 10.6.'
|
||||
success Entities::Badge
|
||||
end
|
||||
params do
|
||||
requires :badge_id, type: Integer, desc: 'The badge ID'
|
||||
end
|
||||
get ":id/badges/:badge_id" do
|
||||
source = find_source(source_type, params[:id])
|
||||
badge = find_badge(source)
|
||||
|
||||
present_badges(source, badge)
|
||||
end
|
||||
|
||||
desc "Adds a badge to a #{source_type}." do
|
||||
detail 'This feature was introduced in GitLab 10.6.'
|
||||
success Entities::Badge
|
||||
end
|
||||
params do
|
||||
requires :link_url, type: String, desc: 'URL of the badge link'
|
||||
requires :image_url, type: String, desc: 'URL of the badge image'
|
||||
end
|
||||
post ":id/badges" do
|
||||
source = find_source_if_admin(source_type)
|
||||
|
||||
badge = ::Badges::CreateService.new(declared_params(include_missing: false)).execute(source)
|
||||
|
||||
if badge.persisted?
|
||||
present_badges(source, badge)
|
||||
else
|
||||
render_validation_error!(badge)
|
||||
end
|
||||
end
|
||||
|
||||
desc "Updates a badge of a #{source_type}." do
|
||||
detail 'This feature was introduced in GitLab 10.6.'
|
||||
success Entities::Badge
|
||||
end
|
||||
params do
|
||||
optional :link_url, type: String, desc: 'URL of the badge link'
|
||||
optional :image_url, type: String, desc: 'URL of the badge image'
|
||||
end
|
||||
put ":id/badges/:badge_id" do
|
||||
source = find_source_if_admin(source_type)
|
||||
|
||||
badge = ::Badges::UpdateService.new(declared_params(include_missing: false))
|
||||
.execute(find_badge(source))
|
||||
|
||||
if badge.valid?
|
||||
present_badges(source, badge)
|
||||
else
|
||||
render_validation_error!(badge)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Removes a badge from a project or group.' do
|
||||
detail 'This feature was introduced in GitLab 10.6.'
|
||||
end
|
||||
params do
|
||||
requires :badge_id, type: Integer, desc: 'The badge ID'
|
||||
end
|
||||
delete ":id/badges/:badge_id" do
|
||||
source = find_source_if_admin(source_type)
|
||||
badge = find_badge(source)
|
||||
|
||||
if badge.is_a?(GroupBadge) && source.is_a?(Project)
|
||||
error!('To delete a Group badge please use the Group endpoint', 403)
|
||||
end
|
||||
|
||||
destroy_conditionally!(badge)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -16,6 +16,10 @@ module API
|
|||
render_api_error!('The branch refname is invalid', 400)
|
||||
end
|
||||
end
|
||||
|
||||
params :filter_params do
|
||||
optional :search, type: String, desc: 'Return list of branches matching the search criteria'
|
||||
end
|
||||
end
|
||||
|
||||
params do
|
||||
|
@ -27,15 +31,23 @@ module API
|
|||
end
|
||||
params do
|
||||
use :pagination
|
||||
use :filter_params
|
||||
end
|
||||
get ':id/repository/branches' do
|
||||
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42329')
|
||||
|
||||
repository = user_project.repository
|
||||
branches = ::Kaminari.paginate_array(repository.branches.sort_by(&:name))
|
||||
|
||||
branches = BranchesFinder.new(repository, declared_params(include_missing: false)).execute
|
||||
|
||||
merged_branch_names = repository.merged_branch_names(branches.map(&:name))
|
||||
|
||||
present paginate(branches), with: Entities::Branch, project: user_project, merged_branch_names: merged_branch_names
|
||||
present(
|
||||
paginate(::Kaminari.paginate_array(branches)),
|
||||
with: Entities::Branch,
|
||||
project: user_project,
|
||||
merged_branch_names: merged_branch_names
|
||||
)
|
||||
end
|
||||
|
||||
resource ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
|
||||
|
|
|
@ -1235,5 +1235,23 @@ module API
|
|||
expose :startline
|
||||
expose :project_id
|
||||
end
|
||||
|
||||
class BasicBadgeDetails < Grape::Entity
|
||||
expose :link_url
|
||||
expose :image_url
|
||||
expose :rendered_link_url do |badge, options|
|
||||
badge.rendered_link_url(options.fetch(:project, nil))
|
||||
end
|
||||
expose :rendered_image_url do |badge, options|
|
||||
badge.rendered_image_url(options.fetch(:project, nil))
|
||||
end
|
||||
end
|
||||
|
||||
class Badge < BasicBadgeDetails
|
||||
expose :id
|
||||
expose :kind do |badge|
|
||||
badge.type == 'ProjectBadge' ? 'project' : 'group'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
28
lib/api/helpers/badges_helpers.rb
Normal file
28
lib/api/helpers/badges_helpers.rb
Normal file
|
@ -0,0 +1,28 @@
|
|||
module API
|
||||
module Helpers
|
||||
module BadgesHelpers
|
||||
include ::API::Helpers::MembersHelpers
|
||||
|
||||
def find_badge(source)
|
||||
source.badges.find(params[:badge_id])
|
||||
end
|
||||
|
||||
def present_badges(source, records, options = {})
|
||||
entity_type = options[:with] || Entities::Badge
|
||||
badge_params = badge_source_params(source).merge(with: entity_type)
|
||||
|
||||
present records, badge_params
|
||||
end
|
||||
|
||||
def badge_source_params(source)
|
||||
project = if source.is_a?(Project)
|
||||
source
|
||||
else
|
||||
GroupProjectsFinder.new(group: source, current_user: current_user).execute.first
|
||||
end
|
||||
|
||||
{ project: project }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -32,6 +32,8 @@ module API
|
|||
optional :search, type: String, desc: 'Search issues for text present in the title or description'
|
||||
optional :created_after, type: DateTime, desc: 'Return issues created after the specified time'
|
||||
optional :created_before, type: DateTime, desc: 'Return issues created before the specified time'
|
||||
optional :updated_after, type: DateTime, desc: 'Return issues updated after the specified time'
|
||||
optional :updated_before, type: DateTime, desc: 'Return issues updated before the specified time'
|
||||
optional :author_id, type: Integer, desc: 'Return issues which are authored by the user with the given ID'
|
||||
optional :assignee_id, type: Integer, desc: 'Return issues which are assigned to the user with the given ID'
|
||||
optional :scope, type: String, values: %w[created-by-me assigned-to-me all],
|
||||
|
|
|
@ -42,6 +42,8 @@ module API
|
|||
optional :labels, type: String, desc: 'Comma-separated list of label names'
|
||||
optional :created_after, type: DateTime, desc: 'Return merge requests created after the specified time'
|
||||
optional :created_before, type: DateTime, desc: 'Return merge requests created before the specified time'
|
||||
optional :updated_after, type: DateTime, desc: 'Return merge requests updated after the specified time'
|
||||
optional :updated_before, type: DateTime, desc: 'Return merge requests updated before the specified time'
|
||||
optional :view, type: String, values: %w[simple], desc: 'If simple, returns the `iid`, URL, title, description, and basic state of merge request'
|
||||
optional :author_id, type: Integer, desc: 'Return merge requests which are authored by the user with the given ID'
|
||||
optional :assignee_id, type: Integer, desc: 'Return merge requests which are assigned to the user with the given ID'
|
||||
|
|
|
@ -25,8 +25,8 @@ module Banzai
|
|||
# period or comma for punctuation without those characters being included
|
||||
# in the generated link.
|
||||
#
|
||||
# Rubular: http://rubular.com/r/cxjPyZc7Sb
|
||||
LINK_PATTERN = %r{([a-z][a-z0-9\+\.-]+://\S+)(?<!,|\.)}
|
||||
# Rubular: http://rubular.com/r/JzPhi6DCZp
|
||||
LINK_PATTERN = %r{([a-z][a-z0-9\+\.-]+://[^\s>]+)(?<!,|\.)}
|
||||
|
||||
# Text matching LINK_PATTERN inside these elements will not be linked
|
||||
IGNORE_PARENTS = %w(a code kbd pre script style).to_set
|
||||
|
@ -35,53 +35,19 @@ module Banzai
|
|||
TEXT_QUERY = %Q(descendant-or-self::text()[
|
||||
not(#{IGNORE_PARENTS.map { |p| "ancestor::#{p}" }.join(' or ')})
|
||||
and contains(., '://')
|
||||
and not(starts-with(., 'http'))
|
||||
and not(starts-with(., 'ftp'))
|
||||
]).freeze
|
||||
|
||||
PUNCTUATION_PAIRS = {
|
||||
"'" => "'",
|
||||
'"' => '"',
|
||||
')' => '(',
|
||||
']' => '[',
|
||||
'}' => '{'
|
||||
}.freeze
|
||||
|
||||
def call
|
||||
return doc if context[:autolink] == false
|
||||
|
||||
rinku_parse
|
||||
text_parse
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Run the text through Rinku as a first pass
|
||||
#
|
||||
# This will quickly autolink http(s) and ftp links.
|
||||
#
|
||||
# `@doc` will be re-parsed with the HTML String from Rinku.
|
||||
def rinku_parse
|
||||
# Convert the options from a Hash to a String that Rinku expects
|
||||
options = tag_options(link_options)
|
||||
|
||||
# NOTE: We don't parse email links because it will erroneously match
|
||||
# external Commit and CommitRange references.
|
||||
#
|
||||
# The final argument tells Rinku to link short URLs that don't include a
|
||||
# period (e.g., http://localhost:3000/)
|
||||
rinku = Rinku.auto_link(html, :urls, options, IGNORE_PARENTS.to_a, 1)
|
||||
|
||||
return if rinku == html
|
||||
|
||||
# Rinku returns a String, so parse it back to a Nokogiri::XML::Document
|
||||
# for further processing.
|
||||
@doc = parse_html(rinku)
|
||||
end
|
||||
|
||||
# Return true if any of the UNSAFE_PROTOCOLS strings are included in the URI scheme
|
||||
def contains_unsafe?(scheme)
|
||||
return false unless scheme
|
||||
|
||||
scheme = scheme.strip.downcase
|
||||
Banzai::Filter::SanitizationFilter::UNSAFE_PROTOCOLS.any? { |protocol| scheme.include?(protocol) }
|
||||
end
|
||||
|
||||
# Autolinks any text matching LINK_PATTERN that Rinku didn't already
|
||||
# replace
|
||||
def text_parse
|
||||
doc.xpath(TEXT_QUERY).each do |node|
|
||||
content = node.to_html
|
||||
|
||||
|
@ -97,6 +63,16 @@ module Banzai
|
|||
doc
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Return true if any of the UNSAFE_PROTOCOLS strings are included in the URI scheme
|
||||
def contains_unsafe?(scheme)
|
||||
return false unless scheme
|
||||
|
||||
scheme = scheme.strip.downcase
|
||||
Banzai::Filter::SanitizationFilter::UNSAFE_PROTOCOLS.any? { |protocol| scheme.include?(protocol) }
|
||||
end
|
||||
|
||||
def autolink_match(match)
|
||||
# start by stripping out dangerous links
|
||||
begin
|
||||
|
@ -112,12 +88,30 @@ module Banzai
|
|||
match.gsub!(/((?:&[\w#]+;)+)\z/, '')
|
||||
dropped = ($1 || '').html_safe
|
||||
|
||||
# To match the behaviour of Rinku, if the matched link ends with a
|
||||
# closing part of a matched pair of punctuation, we remove that trailing
|
||||
# character unless there are an equal number of closing and opening
|
||||
# characters in the link.
|
||||
if match.end_with?(*PUNCTUATION_PAIRS.keys)
|
||||
close_character = match[-1]
|
||||
close_count = match.count(close_character)
|
||||
open_character = PUNCTUATION_PAIRS[close_character]
|
||||
open_count = match.count(open_character)
|
||||
|
||||
if open_count != close_count || open_character == close_character
|
||||
dropped += close_character
|
||||
match = match[0..-2]
|
||||
end
|
||||
end
|
||||
|
||||
options = link_options.merge(href: match)
|
||||
content_tag(:a, match, options) + dropped
|
||||
content_tag(:a, match.html_safe, options) + dropped
|
||||
end
|
||||
|
||||
def autolink_filter(text)
|
||||
text.gsub(LINK_PATTERN) { |match| autolink_match(match) }
|
||||
Gitlab::StringRegexMarker.new(CGI.unescapeHTML(text), text.html_safe).mark(LINK_PATTERN) do |link, left:, right:|
|
||||
autolink_match(link)
|
||||
end
|
||||
end
|
||||
|
||||
def link_options
|
||||
|
|
|
@ -23,7 +23,7 @@ module Gitlab
|
|||
mr_events = event_counts(date_from, :merge_requests)
|
||||
.having(action: [Event::MERGED, Event::CREATED, Event::CLOSED], target_type: "MergeRequest")
|
||||
note_events = event_counts(date_from, :merge_requests)
|
||||
.having(action: [Event::COMMENTED], target_type: "Note")
|
||||
.having(action: [Event::COMMENTED], target_type: %w(Note DiffNote))
|
||||
|
||||
union = Gitlab::SQL::Union.new([repo_events, issue_events, mr_events, note_events])
|
||||
events = Event.find_by_sql(union.to_sql).map(&:attributes)
|
||||
|
|
|
@ -238,9 +238,9 @@ module Gitlab
|
|||
self.__send__("#{key}=", options[key.to_sym]) # rubocop:disable GitlabSecurity/PublicSend
|
||||
end
|
||||
|
||||
@loaded_all_data = false
|
||||
# Retain the actual size before it is encoded
|
||||
@loaded_size = @data.bytesize if @data
|
||||
@loaded_all_data = @loaded_size == size
|
||||
end
|
||||
|
||||
def binary?
|
||||
|
@ -255,10 +255,15 @@ module Gitlab
|
|||
# memory as a Ruby string.
|
||||
def load_all_data!(repository)
|
||||
return if @data == '' # don't mess with submodule blobs
|
||||
return @data if @loaded_all_data
|
||||
|
||||
Gitlab::GitalyClient.migrate(:git_blob_load_all_data) do |is_enabled|
|
||||
@data = begin
|
||||
# Even if we return early, recalculate wether this blob is binary in
|
||||
# case a blob was initialized as text but the full data isn't
|
||||
@binary = nil
|
||||
|
||||
return if @loaded_all_data
|
||||
|
||||
@data = Gitlab::GitalyClient.migrate(:git_blob_load_all_data) do |is_enabled|
|
||||
begin
|
||||
if is_enabled
|
||||
repository.gitaly_blob_client.get_blob(oid: id, limit: -1).data
|
||||
else
|
||||
|
@ -269,7 +274,6 @@ module Gitlab
|
|||
|
||||
@loaded_all_data = true
|
||||
@loaded_size = @data.bytesize
|
||||
@binary = nil
|
||||
end
|
||||
|
||||
def name
|
||||
|
|
|
@ -7,6 +7,28 @@ module Gitlab
|
|||
end
|
||||
|
||||
def new_pointers(object_limit: nil, not_in: nil)
|
||||
@repository.gitaly_migrate(:blob_get_new_lfs_pointers) do |is_enabled|
|
||||
if is_enabled
|
||||
@repository.gitaly_blob_client.get_new_lfs_pointers(@newrev, object_limit, not_in)
|
||||
else
|
||||
git_new_pointers(object_limit, not_in)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def all_pointers
|
||||
@repository.gitaly_migrate(:blob_get_all_lfs_pointers) do |is_enabled|
|
||||
if is_enabled
|
||||
@repository.gitaly_blob_client.get_all_lfs_pointers(@newrev)
|
||||
else
|
||||
git_all_pointers
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def git_new_pointers(object_limit, not_in)
|
||||
@new_pointers ||= begin
|
||||
rev_list.new_objects(not_in: not_in, require_path: true) do |object_ids|
|
||||
object_ids = object_ids.take(object_limit) if object_limit
|
||||
|
@ -16,14 +38,12 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def all_pointers
|
||||
def git_all_pointers
|
||||
rev_list.all_objects(require_path: true) do |object_ids|
|
||||
Gitlab::Git::Blob.batch_lfs_pointers(@repository, object_ids)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def rev_list
|
||||
Gitlab::Git::RevList.new(@repository, newrev: @newrev)
|
||||
end
|
||||
|
|
|
@ -479,9 +479,8 @@ module Gitlab
|
|||
raise ArgumentError.new("invalid Repository#log limit: #{limit.inspect}")
|
||||
end
|
||||
|
||||
# TODO support options[:all] in Gitaly https://gitlab.com/gitlab-org/gitaly/issues/1049
|
||||
gitaly_migrate(:find_commits) do |is_enabled|
|
||||
if is_enabled && !options[:all]
|
||||
if is_enabled
|
||||
gitaly_commit_client.find_commits(options)
|
||||
else
|
||||
raw_log(options).map { |c| Commit.decorate(self, c) }
|
||||
|
@ -508,9 +507,8 @@ module Gitlab
|
|||
def count_commits(options)
|
||||
count_commits_options = process_count_commits_options(options)
|
||||
|
||||
# TODO add support for options[:all] in Gitaly https://gitlab.com/gitlab-org/gitaly/issues/1050
|
||||
gitaly_migrate(:count_commits) do |is_enabled|
|
||||
if is_enabled && !options[:all]
|
||||
if is_enabled
|
||||
count_commits_by_gitaly(count_commits_options)
|
||||
else
|
||||
count_commits_by_shelling_out(count_commits_options)
|
||||
|
|
|
@ -45,16 +45,7 @@ module Gitlab
|
|||
|
||||
response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_lfs_pointers, request)
|
||||
|
||||
response.flat_map do |message|
|
||||
message.lfs_pointers.map do |lfs_pointer|
|
||||
Gitlab::Git::Blob.new(
|
||||
id: lfs_pointer.oid,
|
||||
size: lfs_pointer.size,
|
||||
data: lfs_pointer.data,
|
||||
binary: Gitlab::Git::Blob.binary?(lfs_pointer.data)
|
||||
)
|
||||
end
|
||||
end
|
||||
map_lfs_pointers(response)
|
||||
end
|
||||
|
||||
def get_blobs(revision_paths, limit = -1)
|
||||
|
@ -80,6 +71,50 @@ module Gitlab
|
|||
|
||||
GitalyClient::BlobsStitcher.new(response)
|
||||
end
|
||||
|
||||
def get_new_lfs_pointers(revision, limit, not_in)
|
||||
request = Gitaly::GetNewLFSPointersRequest.new(
|
||||
repository: @gitaly_repo,
|
||||
revision: encode_binary(revision),
|
||||
limit: limit || 0
|
||||
)
|
||||
|
||||
if not_in.nil? || not_in == :all
|
||||
request.not_in_all = true
|
||||
else
|
||||
request.not_in_refs += not_in
|
||||
end
|
||||
|
||||
response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_new_lfs_pointers, request)
|
||||
|
||||
map_lfs_pointers(response)
|
||||
end
|
||||
|
||||
def get_all_lfs_pointers(revision)
|
||||
request = Gitaly::GetNewLFSPointersRequest.new(
|
||||
repository: @gitaly_repo,
|
||||
revision: encode_binary(revision)
|
||||
)
|
||||
|
||||
response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_all_lfs_pointers, request)
|
||||
|
||||
map_lfs_pointers(response)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def map_lfs_pointers(response)
|
||||
response.flat_map do |message|
|
||||
message.lfs_pointers.map do |lfs_pointer|
|
||||
Gitlab::Git::Blob.new(
|
||||
id: lfs_pointer.oid,
|
||||
size: lfs_pointer.size,
|
||||
data: lfs_pointer.data,
|
||||
binary: Gitlab::Git::Blob.binary?(lfs_pointer.data)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -134,7 +134,8 @@ module Gitlab
|
|||
def commit_count(ref, options = {})
|
||||
request = Gitaly::CountCommitsRequest.new(
|
||||
repository: @gitaly_repo,
|
||||
revision: encode_binary(ref)
|
||||
revision: encode_binary(ref),
|
||||
all: !!options[:all]
|
||||
)
|
||||
request.after = Google::Protobuf::Timestamp.new(seconds: options[:after].to_i) if options[:after].present?
|
||||
request.before = Google::Protobuf::Timestamp.new(seconds: options[:before].to_i) if options[:before].present?
|
||||
|
@ -269,6 +270,7 @@ module Gitlab
|
|||
offset: options[:offset],
|
||||
follow: options[:follow],
|
||||
skip_merges: options[:skip_merges],
|
||||
all: !!options[:all],
|
||||
disable_walk: true # This option is deprecated. The 'walk' implementation is being removed.
|
||||
)
|
||||
request.after = GitalyClient.timestamp(options[:after]) if options[:after]
|
||||
|
|
|
@ -20,6 +20,7 @@ module Gitlab
|
|||
gon.sprite_icons = IconsHelper.sprite_icon_path
|
||||
gon.sprite_file_icons = IconsHelper.sprite_file_icons_path
|
||||
gon.test_env = Rails.env.test?
|
||||
gon.suggested_label_colors = LabelsHelper.suggested_colors
|
||||
|
||||
if current_user
|
||||
gon.current_user_id = current_user.id
|
||||
|
|
|
@ -3,7 +3,7 @@ module Gitlab
|
|||
extend self
|
||||
|
||||
# For every version update, the version history in import_export.md has to be kept up to date.
|
||||
VERSION = '0.2.2'.freeze
|
||||
VERSION = '0.2.3'.freeze
|
||||
FILENAME_LIMIT = 50
|
||||
|
||||
def export_path(relative_path:)
|
||||
|
|
|
@ -65,6 +65,7 @@ project_tree:
|
|||
- :create_access_levels
|
||||
- :project_feature
|
||||
- :custom_attributes
|
||||
- :project_badges
|
||||
|
||||
# Only include the following attributes for the models specified.
|
||||
included_attributes:
|
||||
|
@ -125,6 +126,8 @@ excluded_attributes:
|
|||
- :when
|
||||
push_event_payload:
|
||||
- :event_id
|
||||
project_badges:
|
||||
- :group_id
|
||||
|
||||
methods:
|
||||
labels:
|
||||
|
@ -147,3 +150,5 @@ methods:
|
|||
- :action
|
||||
push_event_payload:
|
||||
- :action
|
||||
project_badges:
|
||||
- :type
|
||||
|
|
|
@ -16,7 +16,8 @@ module Gitlab
|
|||
priorities: :label_priorities,
|
||||
auto_devops: :project_auto_devops,
|
||||
label: :project_label,
|
||||
custom_attributes: 'ProjectCustomAttribute' }.freeze
|
||||
custom_attributes: 'ProjectCustomAttribute',
|
||||
project_badges: 'Badge' }.freeze
|
||||
|
||||
USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id created_by_id last_edited_by_id merge_user_id resolved_by_id].freeze
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue