Commit 8ec0fd0a authored by Filipa Lacerda's avatar Filipa Lacerda

[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
  ...
parents d0b3d120 5656a1d6
...@@ -619,9 +619,10 @@ codequality: ...@@ -619,9 +619,10 @@ codequality:
cache: {} cache: {}
dependencies: [] dependencies: []
script: script:
- apk update && apk add jq
- ./scripts/codequality analyze -f json > raw_codeclimate.json || true - ./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 # 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: artifacts:
paths: [codeclimate.json] paths: [codeclimate.json]
expire_in: 1 week expire_in: 1 week
......
...@@ -196,6 +196,17 @@ release. There are two levels of priority labels: ...@@ -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 milestone. If these issues are not done in the current release, they will
strongly be considered for the next release. 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") ### Label for community contributors (~"Accepting Merge Requests")
Issues that are beneficial to our users, 'nice to haves', that we currently do Issues that are beneficial to our users, 'nice to haves', that we currently do
......
...@@ -411,7 +411,7 @@ group :ed25519 do ...@@ -411,7 +411,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # 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 # Locked until https://github.com/google/protobuf/issues/4210 is closed
gem 'google-protobuf', '= 3.5.1' gem 'google-protobuf', '= 3.5.1'
......
...@@ -285,7 +285,7 @@ GEM ...@@ -285,7 +285,7 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gherkin-ruby (0.3.2) gherkin-ruby (0.3.2)
gitaly-proto (0.87.0) gitaly-proto (0.88.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.0) grpc (~> 1.0)
github-linguist (5.3.3) github-linguist (5.3.3)
...@@ -601,7 +601,7 @@ GEM ...@@ -601,7 +601,7 @@ GEM
atomic (>= 1.0.0) atomic (>= 1.0.0)
mysql2 mysql2
peek peek
peek-performance_bar (1.3.0) peek-performance_bar (1.3.1)
peek (>= 0.1.0) peek (>= 0.1.0)
peek-pg (1.3.0) peek-pg (1.3.0)
concurrent-ruby concurrent-ruby
...@@ -1057,7 +1057,7 @@ DEPENDENCIES ...@@ -1057,7 +1057,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0) gettext_i18n_rails_js (~> 1.2.0)
gitaly-proto (~> 0.87.0) gitaly-proto (~> 0.88.0)
github-linguist (~> 5.3.3) github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2) gitlab-markup (~> 1.6.2)
......
...@@ -5,12 +5,12 @@ import Vue from 'vue'; ...@@ -5,12 +5,12 @@ import Vue from 'vue';
import Flash from '~/flash'; import Flash from '~/flash';
import { __ } from '~/locale'; import { __ } from '~/locale';
import '~/vue_shared/models/label';
import FilteredSearchBoards from './filtered_search_boards'; import FilteredSearchBoards from './filtered_search_boards';
import eventHub from './eventhub'; import eventHub from './eventhub';
import sidebarEventHub from '~/sidebar/event_hub'; // eslint-disable-line import/first import sidebarEventHub from '~/sidebar/event_hub'; // eslint-disable-line import/first
import './models/issue'; import './models/issue';
import './models/label';
import './models/list'; import './models/list';
import './models/milestone'; import './models/milestone';
import './models/assignee'; import './models/assignee';
......
...@@ -216,6 +216,9 @@ export default class MilestoneSelect { ...@@ -216,6 +216,9 @@ export default class MilestoneSelect {
$value.html(milestoneLinkNoneTemplate); $value.html(milestoneLinkNoneTemplate);
return $sidebarCollapsedValue.find('span').text('No'); return $sidebarCollapsedValue.find('span').text('No');
} }
})
.catch(() => {
$loading.fadeOut();
}); });
} }
} }
......
<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>
<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>
<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,
}"
>
&nbsp;
</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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
/* eslint-disable no-unused-vars, space-before-function-paren */
class ListLabel { class ListLabel {
constructor (obj) { constructor(obj) {
this.id = obj.id; this.id = obj.id;
this.title = obj.title; this.title = obj.title;
this.type = obj.type; this.type = obj.type;
......
...@@ -9,7 +9,6 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController ...@@ -9,7 +9,6 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController
@impersonation_token = finder.build(impersonation_token_params) @impersonation_token = finder.build(impersonation_token_params)
if @impersonation_token.save 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." redirect_to admin_user_impersonation_tokens_path, notice: "A new impersonation token has been created."
else else
set_index_vars set_index_vars
......
...@@ -17,11 +17,9 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -17,11 +17,9 @@ class Projects::CompareController < Projects::ApplicationController
def show def show
apply_diff_view_cookie! apply_diff_view_cookie!
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37430
Gitlab::GitalyClient.allow_n_plus_1_calls do
render render
end end
end
def diff_for_path def diff_for_path
return render_404 unless @compare return render_404 unless @compare
......
...@@ -19,6 +19,10 @@ ...@@ -19,6 +19,10 @@
# non_archived: boolean # non_archived: boolean
# iids: integer[] # iids: integer[]
# my_reaction_emoji: string # my_reaction_emoji: string
# created_after: datetime
# created_before: datetime
# updated_after: datetime
# updated_before: datetime
# #
class IssuableFinder class IssuableFinder
prepend FinderWithCrossProjectAccess prepend FinderWithCrossProjectAccess
...@@ -79,6 +83,7 @@ class IssuableFinder ...@@ -79,6 +83,7 @@ class IssuableFinder
def filter_items(items) def filter_items(items)
items = by_scope(items) items = by_scope(items)
items = by_created_at(items) items = by_created_at(items)
items = by_updated_at(items)
items = by_state(items) items = by_state(items)
items = by_group(items) items = by_group(items)
items = by_search(items) items = by_search(items)
...@@ -283,6 +288,13 @@ class IssuableFinder ...@@ -283,6 +288,13 @@ class IssuableFinder
end end
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) def by_state(items)
case params[:state].to_s case params[:state].to_s
when 'closed' when 'closed'
......
...@@ -17,6 +17,10 @@ ...@@ -17,6 +17,10 @@
# my_reaction_emoji: string # my_reaction_emoji: string
# public_only: boolean # public_only: boolean
# due_date: date or '0', '', 'overdue', 'week', or 'month' # due_date: date or '0', '', 'overdue', 'week', or 'month'
# created_after: datetime
# created_before: datetime
# updated_after: datetime
# updated_before: datetime
# #
class IssuesFinder < IssuableFinder class IssuesFinder < IssuableFinder
CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER
......
...@@ -19,6 +19,10 @@ ...@@ -19,6 +19,10 @@
# my_reaction_emoji: string # my_reaction_emoji: string
# source_branch: string # source_branch: string
# target_branch: string # target_branch: string
# created_after: datetime
# created_before: datetime
# updated_after: datetime
# updated_before: datetime
# #
class MergeRequestsFinder < IssuableFinder class MergeRequestsFinder < IssuableFinder
def klass def klass
......
...@@ -48,11 +48,23 @@ class NotesFinder ...@@ -48,11 +48,23 @@ class NotesFinder
def init_collection def init_collection
if target if target
notes_on_target notes_on_target
elsif target_type
notes_of_target_type
else else
notes_of_any_type notes_of_any_type
end end
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 def notes_of_any_type
types = %w(commit issue merge_request snippet) types = %w(commit issue merge_request snippet)
note_relations = types.map { |t| notes_for_type(t) } note_relations = types.map { |t| notes_for_type(t) }
......
...@@ -110,10 +110,6 @@ class TodosFinder ...@@ -110,10 +110,6 @@ class TodosFinder
ids ids
end end
def projects(items)
ProjectsFinder.new(current_user: current_user, project_ids_relation: project_ids(items)).execute
end
def type? def type?
type.present? && %w(Issue MergeRequest).include?(type) type.present? && %w(Issue MergeRequest).include?(type)
end end
...@@ -152,13 +148,14 @@ class TodosFinder ...@@ -152,13 +148,14 @@ class TodosFinder
def by_project(items) def by_project(items)
if project? if project?
items = items.where(project: project) items.where(project: project)
else else
item_projects = projects(items) projects = Project
items = items.merge(item_projects).joins(:project) .public_or_visible_to_user(current_user)
end .order_id_desc
items items.joins(:project).merge(projects)
end
end end
def by_state(items) def by_state(items)
......
module LabelsHelper module LabelsHelper
extend self
include ActionView::Helpers::TagHelper include ActionView::Helpers::TagHelper
def show_label_issuables_link?(label, issuables_type, current_user: nil, project: nil) def show_label_issuables_link?(label, issuables_type, current_user: nil, project: nil)
......
...@@ -11,7 +11,7 @@ module NotesHelper ...@@ -11,7 +11,7 @@ module NotesHelper
end end
def note_supports_quick_actions?(note) def note_supports_quick_actions?(note)
Notes::QuickActionsService.supported?(note, current_user) Notes::QuickActionsService.supported?(note)
end end
def noteable_json(noteable) def noteable_json(noteable)
......
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
class GroupBadge < Badge
belongs_to :group
validates :group, presence: true
end
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 ...@@ -19,6 +19,7 @@ module Issuable
include AfterCommitQueue include AfterCommitQueue
include Sortable include Sortable
include CreatedAtFilterable include CreatedAtFilterable
include UpdatedAtFilterable
# This object is used to gather issuable meta data for displaying # This object is used to gather issuable meta data for displaying
# upvotes, downvotes, notes and closing merge requests count for issues and merge requests # upvotes, downvotes, notes and closing merge requests count for issues and merge requests
......
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 ...@@ -31,6 +31,8 @@ class Group < Namespace
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent 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 accepts_nested_attributes_for :variables, allow_destroy: true
validate :visibility_level_allowed_by_projects validate :visibility_level_allowed_by_projects
......
...@@ -15,4 +15,8 @@ class LfsObject < ActiveRecord::Base ...@@ -15,4 +15,8 @@ class LfsObject < ActiveRecord::Base
.where(lfs_objects_projects: { id: nil }) .where(lfs_objects_projects: { id: nil })
.destroy_all .destroy_all
end end
def self.calculate_oid(path)
Digest::SHA256.file(path).hexdigest
end
end end
...@@ -197,10 +197,6 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -197,10 +197,6 @@ class MergeRequestDiff < ActiveRecord::Base
CompareService.new(project, head_commit_sha).execute(project, sha, straight: true) CompareService.new(project, head_commit_sha).execute(project, sha, straight: true)
end end
def commits_count
super || merge_request_diff_commits.size
end
private private
def create_merge_request_diff_files(diffs) def create_merge_request_diff_files(diffs)
......
...@@ -221,6 +221,8 @@ class Project < ActiveRecord::Base ...@@ -221,6 +221,8 @@ class Project < ActiveRecord::Base
has_one :auto_devops, class_name: 'ProjectAutoDevops' has_one :auto_devops, class_name: 'ProjectAutoDevops'
has_many :custom_attributes, class_name: 'ProjectCustomAttribute' 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 :variables, allow_destroy: true
accepts_nested_attributes_for :project_feature, update_only: true accepts_nested_attributes_for :project_feature, update_only: true
accepts_nested_attributes_for :import_data accepts_nested_attributes_for :import_data
...@@ -1766,6 +1768,17 @@ class Project < ActiveRecord::Base ...@@ -1766,6 +1768,17 @@ class Project < ActiveRecord::Base
.set(import_jid, StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION) .set(import_jid, StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION)
end 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 private
def storage def storage
......
...@@ -253,7 +253,7 @@ class Repository ...@@ -253,7 +253,7 @@ class Repository
# branches or tags, but we want to keep some of these commits around, for # branches or tags, but we want to keep some of these commits around, for
# example if they have comments or CI builds. # example if they have comments or CI builds.
def keep_around(sha) def keep_around(sha)
return unless sha && commit_by(oid: sha) return unless sha.present? && commit_by(oid: sha)
return if kept_around?(sha) return if kept_around?(sha)
......
module Badges
class BaseService
protected
attr_accessor :params
def initialize(params = {})
@params = params.dup
end
end
end
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
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
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 ...@@ -109,6 +109,10 @@ class IssuableBaseService < BaseService
@available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute
end end
def handle_quick_actions_on_create(issuable)
merge_quick_actions_into_params!(issuable)
end
def merge_quick_actions_into_params!(issuable) def merge_quick_actions_into_params!(issuable)
original_description = params.fetch(:description, issuable.description) original_description = params.fetch(:description, issuable.description)
...@@ -131,7 +135,7 @@ class IssuableBaseService < BaseService ...@@ -131,7 +135,7 @@ class IssuableBaseService < BaseService
end end
def create(issuable) def create(issuable)
merge_quick_actions_into_params!(issuable) handle_quick_actions_on_create(issuable)
filter_params(issuable) filter_params(issuable)
params.delete(:state_event) params.delete(:state_event)
......
...@@ -24,6 +24,17 @@ module MergeRequests ...@@ -24,6 +24,17 @@ module MergeRequests
private 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) def merge_request_metrics_service(merge_request)
MergeRequestMetricsService.new(merge_request.metrics) MergeRequestMetricsService.new(merge_request.metrics)
end end
......
...@@ -34,6 +34,12 @@ module MergeRequests ...@@ -34,6 +34,12 @@ module MergeRequests
super super
end end
# Override from IssuableBaseService
def handle_quick_actions_on_create(merge_request)
super
handle_wip_event(merge_request)
end
private private
def update_merge_requests_head_pipeline(merge_request) def update_merge_requests_head_pipeline(merge_request)
......
...@@ -98,17 +98,6 @@ module MergeRequests ...@@ -98,17 +98,6 @@ module MergeRequests
private 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) def create_branch_change_note(issuable, branch_type, old_branch, new_branch)
SystemNoteService.change_branch( SystemNoteService.change_branch(
issuable, issuable.project, current_user, branch_type, issuable, issuable.project, current_user, branch_type,
......
...@@ -9,14 +9,12 @@ module Notes ...@@ -9,14 +9,12 @@ module Notes
UPDATE_SERVICES[note.noteable_type] UPDATE_SERVICES[note.noteable_type]
end end
def self.supported?(note, current_user) def self.supported?(note)
noteable_update_service(note) && !!noteable_update_service(note)
current_user &&
current_user.can?(:"update_#{note.to_ability_name}", note.noteable)
end end
def supported?(note) def supported?(note)
self.class.supported?(note, current_user) self.class.supported?(note)
end end
def extract_commands(note, options = {}) def extract_commands(note, options = {})
......
...@@ -347,9 +347,9 @@ module QuickActions ...@@ -347,9 +347,9 @@ module QuickActions
"#{verb} this #{noun} as Work In Progress." "#{verb} this #{noun} as Work In Progress."
end end
condition do condition do
issuable.persisted? &&
issuable.respond_to?(:work_in_progress?) && issuable.respond_to?(:work_in_progress?) &&
current_user.can?(:"update_#{issuable.to_ability_name}", issuable) # 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 end
command :wip do command :wip do
@updates[:wip_event] = issuable.work_in_progress? ? 'unwip' : 'wip' @updates[:wip_event] = issuable.work_in_progress? ? 'unwip' : 'wip'
......
# 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 @@ ...@@ -23,6 +23,12 @@
- deleted_message = s_('ForkedFromProjectPath|Forked from %{project_name} (deleted)') - deleted_message = s_('ForkedFromProjectPath|Forked from %{project_name} (deleted)')
= deleted_message % { project_name: fork_source_name(@project) } = 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 .project-repo-buttons
.count-buttons .count-buttons
= render 'projects/buttons/star' = render 'projects/buttons/star'
......
...@@ -14,25 +14,25 @@ ...@@ -14,25 +14,25 @@
= link_to search_filter_path(scope: 'issues') do = link_to search_filter_path(scope: 'issues') do
Issues Issues
%span.badge %span.badge
= @search_results.issues_count = limited_count(@search_results.limited_issues_count)
- if project_search_tabs?(:merge_requests) - if project_search_tabs?(:merge_requests)
%li{ class: active_when(@scope == 'merge_requests') } %li{ class: active_when(@scope == 'merge_requests') }
= link_to search_filter_path(scope: 'merge_requests') do = link_to search_filter_path(scope: 'merge_requests') do
Merge requests Merge requests
%span.badge %span.badge
= @search_results.merge_requests_count = limited_count(@search_results.limited_merge_requests_count)
- if project_search_tabs?(:milestones) - if project_search_tabs?(:milestones)
%li{ class: active_when(@scope == 'milestones') } %li{ class: active_when(@scope == 'milestones') }
= link_to search_filter_path(scope: 'milestones') do = link_to search_filter_path(scope: 'milestones') do
Milestones Milestones
%span.badge %span.badge
= @search_results.milestones_count = limited_count(@search_results.limited_milestones_count)
- if project_search_tabs?(:notes) - if project_search_tabs?(:notes)
%li{ class: active_when(@scope == 'notes') } %li{ class: active_when(@scope == 'notes') }
= link_to search_filter_path(scope: 'notes') do = link_to search_filter_path(scope: 'notes') do
Comments Comments
%span.badge %span.badge
= @search_results.notes_count = limited_count(@search_results.limited_notes_count)
- if project_search_tabs?(:wiki) - if project_search_tabs?(:wiki)
%li{ class: active_when(@scope == 'wiki_blobs') } %li{ class: active_when(@scope == 'wiki_blobs') }
= link_to search_filter_path(scope: 'wiki_blobs') do = link_to search_filter_path(scope: 'wiki_blobs') do
......
...@@ -30,10 +30,9 @@ class ProcessCommitWorker ...@@ -30,10 +30,9 @@ class ProcessCommitWorker
end end
def process_commit_message(project, commit, user, author, default = false) def process_commit_message(project, commit, user, author, default = false)
# this is a GitLab generated commit message, ignore it. # Ignore closing references from GitLab-generated commit messages.
return if commit.merged_merge_request?(user) find_closing_issues = default && !commit.merged_merge_request?(user)
closed_issues = find_closing_issues ? commit.closes_issues(user) : []
closed_issues = default ? commit.closes_issues(user) : []
close_issues(project, user, author, commit, closed_issues) if closed_issues.any? close_issues(project, user, author, commit, closed_issues) if closed_issues.any?
commit.create_cross_references!(author, closed_issues) commit.create_cross_references!(author, closed_issues)
......
---
title: Adds updated_at filter to issues and merge_requests API
merge_request: 17417
author: Jacopo Beschi @jacopo-beschi
type: added
---
title: Render htmlentities correctly for links not supported by Rinku
merge_request:
author:
type: fixed
---
title: Add search param to Branches API
merge_request: 17005
author: bunufi
type: added
---
title: Fix quick actions for users who cannot update issues and merge requests
merge_request: 17482
author:
type: fixed
---
title: Stop loading spinner on error of milestone update on issue
merge_request: 17507
author: Takuya Noguchi
type: fixed
---
title: Upgrade Workhorse to version 3.8.0 to support structured logging
merge_request:
author:
type: other
---
title: Foreground verification of uploads and LFS objects
merge_request: 17402
author:
type: added
---
title: Count comments on diffs as contributions for the contributions calendar
merge_request: 17418
author: Riccardo Padovani
type: fixed
---
title: Implemented badge API endpoints
merge_request: 17082
author:
type: added
---
title: Optimize search queries on the search page by setting a limit for matching
records in project scope
merge_request:
author:
type: performance
---
title: Port Labels Select dropdown to Vue
merge_request: 17411
author:
type: other
---
title: Don't use ProjectsFinder in TodosFinder
merge_request:
author:
type: performance
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| ...@@ -23,5 +23,6 @@ warmup do |app|
end end
map ENV['RAILS_RELATIVE_URL_ROOT'] || "/" do map ENV['RAILS_RELATIVE_URL_ROOT'] || "/" do
use Gitlab::Middleware::ReleaseEnv
run Gitlab::Application run Gitlab::Application
end end
...@@ -26,6 +26,7 @@ module Gitlab ...@@ -26,6 +26,7 @@ module Gitlab
# This is a nice reference article on autoloading/eager loading: # This is a nice reference article on autoloading/eager loading:
# http://blog.arkency.com/2014/11/dont-forget-about-eager-load-when-extending-autoload # http://blog.arkency.com/2014/11/dont-forget-about-eager-load-when-extending-autoload
config.eager_load_paths.push(*%W[#{config.root}/lib config.eager_load_paths.push(*%W[#{config.root}/lib
#{config.root}/app/models/badges
#{config.root}/app/models/hooks #{config.root}/app/models/hooks
#{config.root}/app/models/members #{config.root}/app/models/members
#{config.root}/app/models/project_services #{config.root}/app/models/project_services
......
...@@ -18,6 +18,7 @@ module Sidekiq ...@@ -18,6 +18,7 @@ module Sidekiq
%i(perform_async perform_at perform_in).each do |name| %i(perform_async perform_at perform_in).each do |name|
define_method(name) do |*args| define_method(name) do |*args|
if !Sidekiq::Worker.skip_transaction_check && AfterCommitQueue.inside_transaction? if !Sidekiq::Worker.skip_transaction_check && AfterCommitQueue.inside_transaction?
begin
raise Sidekiq::Worker::EnqueueFromTransactionError, <<~MSG raise Sidekiq::Worker::EnqueueFromTransactionError, <<~MSG
`#{self}.#{name}` cannot be called inside a transaction as this can lead to `#{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 race conditions when the worker runs before the transaction is committed and
...@@ -25,6 +26,18 @@ module Sidekiq ...@@ -25,6 +26,18 @@ module Sidekiq
Use an `after_commit` hook, or include `AfterCommitQueue` and use a `run_after_commit` block instead. 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 end
super(*args) super(*args)
......
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
class CleanCommitsCountMigration < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
Gitlab::BackgroundMigration.steal('AddMergeRequestDiffCommitsCount')
end
def down
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -183,6 +183,19 @@ ActiveRecord::Schema.define(version: 20180301084653) do ...@@ -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", ["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 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| create_table "boards", force: :cascade do |t|
t.integer "project_id", null: false t.integer "project_id", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
...@@ -1969,6 +1982,8 @@ ActiveRecord::Schema.define(version: 20180301084653) do ...@@ -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", ["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_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 "boards", "projects", name: "fk_f15266b5f9", on_delete: :cascade
add_foreign_key "chat_teams", "namespaces", on_delete: :cascade add_foreign_key "chat_teams", "namespaces", on_delete: :cascade
add_foreign_key "ci_build_trace_section_names", "projects", on_delete: :cascade add_foreign_key "ci_build_trace_section_names", "projects", on_delete: :cascade
......
...@@ -78,34 +78,41 @@ Example output: ...@@ -78,34 +78,41 @@ Example output:
## Uploaded Files Integrity ## Uploaded Files Integrity
The uploads check Rake task will loop through all uploads in the database Various types of file can be uploaded to a GitLab installation by users.
and run two checks to determine the integrity of each file: 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. Currently, integrity checks are supported for the following types of file:
1. Check if the checksum of the file on the file system matches the checksum in the database.
* LFS objects
* User uploads
**Omnibus Installation** **Omnibus Installation**
``` ```
sudo gitlab-rake gitlab:lfs:check
sudo gitlab-rake gitlab:uploads:check sudo gitlab-rake gitlab:uploads:check
``` ```
**Source Installation** **Source Installation**
```bash ```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 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: certain values:
Variable | Type | Description Variable | Type | Description
-------- | ---- | ----------- --------- | ------- | -----------
`BATCH` | integer | Specifies the size of the batch. Defaults to 200. `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_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. `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 ```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 sudo gitlab-rake gitlab:uploads:check BATCH=100 ID_FROM=50 ID_TO=250
``` ```
......
...@@ -24,6 +24,7 @@ following locations: ...@@ -24,6 +24,7 @@ following locations:
- [GitLab CI Config templates](templates/gitlab_ci_ymls.md) - [GitLab CI Config templates](templates/gitlab_ci_ymls.md)
- [Groups](groups.md) - [Groups](groups.md)
- [Group Access Requests](access_requests.md) - [Group Access Requests](access_requests.md)
- [Group Badges](group_badges.md)
- [Group Members](members.md) - [Group Members](members.md)
- [Issues](issues.md) - [Issues](issues.md)
- [Issue Boards](boards.md) - [Issue Boards](boards.md)
...@@ -43,6 +44,7 @@ following locations: ...@@ -43,6 +44,7 @@ following locations:
- [Pipeline Schedules](pipeline_schedules.md) - [Pipeline Schedules](pipeline_schedules.md)
- [Projects](projects.md) including setting Webhooks - [Projects](projects.md) including setting Webhooks
- [Project Access Requests](access_requests.md) - [Project Access Requests](access_requests.md)
- [Project Badges](project_badges.md)
- [Project import/export](project_import_export.md) - [Project import/export](project_import_export.md)
- [Project Members](members.md) - [Project Members](members.md)
- [Project Snippets](project_snippets.md) - [Project Snippets](project_snippets.md)
......
...@@ -13,6 +13,7 @@ GET /projects/:id/repository/branches ...@@ -13,6 +13,7 @@ GET /projects/:id/repository/branches
| Attribute | Type | Required | Description | | 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 | | `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 ```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/repository/branches curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/repository/branches
......
# 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: ...@@ -525,3 +525,7 @@ And to switch pages add:
``` ```
[ce-15142]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15142 [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 ...@@ -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` | | `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` | | `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Search issues against their `title` and `description` | | `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 ```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/issues curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/issues
...@@ -152,6 +156,10 @@ GET /groups/:id/issues?my_reaction_emoji=star ...@@ -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` | | `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` | | `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` | | `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 ```bash
...@@ -259,8 +267,10 @@ GET /projects/:id/issues?my_reaction_emoji=star ...@@ -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` | | `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` | | `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` | | `search` | string | no | Search project issues against their `title` and `description` |
| `created_after` | datetime | no | Return issues created after the given time (inclusive) | | `created_after` | datetime | no | Return issues created on or after the given time |
| `created_before` | datetime | no | Return issues created before the given time (inclusive) | | `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 ```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues
......
...@@ -41,8 +41,10 @@ Parameters: ...@@ -41,8 +41,10 @@ Parameters:
| `milestone` | string | no | Return merge requests for a specific milestone | | `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 | | `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 | | `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_after` | datetime | no | Return merge requests created on or after the given time |
| `created_before` | datetime | no | Return merge requests created before the given time (inclusive) | | `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` | | `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` | | `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` | | `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` |
...@@ -158,8 +160,10 @@ Parameters: ...@@ -158,8 +160,10 @@ Parameters:
| `milestone` | string | no | Return merge requests for a specific milestone | | `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 | | `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 | | `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_after` | datetime | no | Return merge requests created on or after the given time |
| `created_before` | datetime | no | Return merge requests created before the given time (inclusive) | | `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)_ | | `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)_ | | `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)_ | | `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: ...@@ -494,6 +498,8 @@ Parameters:
## List MR pipelines ## List MR pipelines
> [Introduced][ce-15454] in GitLab 10.5.0.
Get a list of merge request pipelines. Get a list of merge request pipelines.
``` ```
...@@ -1449,3 +1455,4 @@ Example response: ...@@ -1449,3 +1455,4 @@ Example response:
[ce-13060]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13060 [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-14016]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14016
[ce-15454]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15454
# 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 ...@@ -1340,3 +1340,7 @@ Read more in the [Project import/export](project_import_export.md) documentation
## Project members ## Project members
Read more in the [Project members](members.md) documentation. 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. ...@@ -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 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. custom name continue onto the next step.
> Note that Public SSH key may also be named as follows:
**Note:** Public SSH key may also be named as follows:
- `id_dsa.pub` - `id_dsa.pub`
- `id_ecdsa.pub` - `id_ecdsa.pub`
- `id_ed25519.pub` - `id_ed25519.pub`
...@@ -73,7 +73,7 @@ custom name continue onto the next step. ...@@ -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 key pair, but it is not required and you can skip creating a password by
pressing enter. pressing enter.
>**Note:** NOTE: **Note:**
If you want to change the password of your SSH key pair, you can use If you want to change the password of your SSH key pair, you can use
`ssh-keygen -p <keyname>`. `ssh-keygen -p <keyname>`.
...@@ -162,11 +162,13 @@ That's why it needs to uniquely map to a single user. ...@@ -162,11 +162,13 @@ That's why it needs to uniquely map to a single user.
## Deploy keys ## Deploy keys
### Per-repository deploy keys
Deploy keys allow read-only or read-write (if enabled) access to one or Deploy keys allow read-only or read-write (if enabled) access to one or
multiple projects with a single SSH key pair. multiple projects with a single SSH key pair.
This is really useful for cloning repositories to your Continuous 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. dummy user account.
If you are a project master or owner, you can add a deploy key in the If you are a project master or owner, you can add a deploy key in the
...@@ -185,6 +187,47 @@ a group. ...@@ -185,6 +187,47 @@ a group.
Deploy keys can be shared between projects, you just need to add them to each Deploy keys can be shared between projects, you just need to add them to each
project. 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 ## Applications
### Eclipse ### Eclipse
......
...@@ -31,7 +31,8 @@ with all their related data and be moved into a new GitLab instance. ...@@ -31,7 +31,8 @@ with all their related data and be moved into a new GitLab instance.
| GitLab version | Import/Export version | | 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.3 | 0.2.1 |
| 10.0 | 0.2.0 | | 10.0 | 0.2.0 |
| 9.4.0 | 0.1.8 | | 9.4.0 | 0.1.8 |
......
...@@ -108,6 +108,7 @@ module API ...@@ -108,6 +108,7 @@ module API
mount ::API::AccessRequests mount ::API::AccessRequests
mount ::API::Applications mount ::API::Applications
mount ::API::AwardEmoji mount ::API::AwardEmoji
mount ::API::Badges
mount ::API::Boards mount ::API::Boards
mount ::API::Branches mount ::API::Branches
mount ::API::BroadcastMessages mount ::API::BroadcastMessages
......
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 ...@@ -16,6 +16,10 @@ module API
render_api_error!('The branch refname is invalid', 400) render_api_error!('The branch refname is invalid', 400)
end end
end end
params :filter_params do
optional :search, type: String, desc: 'Return list of branches matching the search criteria'
end
end end
params do params do
...@@ -27,15 +31,23 @@ module API ...@@ -27,15 +31,23 @@ module API
end end
params do params do
use :pagination use :pagination
use :filter_params
end end
get ':id/repository/branches' do get ':id/repository/branches' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42329') Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42329')
repository = user_project.repository 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)) 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 end
resource ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do resource ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
......
...@@ -1235,5 +1235,23 @@ module API ...@@ -1235,5 +1235,23 @@ module API
expose :startline expose :startline
expose :project_id expose :project_id
end 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
end end
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 ...@@ -32,6 +32,8 @@ module API
optional :search, type: String, desc: 'Search issues for text present in the title or description' 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_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 :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 :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 :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], optional :scope, type: String, values: %w[created-by-me assigned-to-me all],
......
...@@ -42,6 +42,8 @@ module API ...@@ -42,6 +42,8 @@ module API
optional :labels, type: String, desc: 'Comma-separated list of label names' 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_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 :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 :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 :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' 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 ...@@ -25,8 +25,8 @@ module Banzai
# period or comma for punctuation without those characters being included # period or comma for punctuation without those characters being included
# in the generated link. # in the generated link.
# #
# Rubular: http://rubular.com/r/cxjPyZc7Sb # Rubular: http://rubular.com/r/JzPhi6DCZp
LINK_PATTERN = %r{([a-z][a-z0-9\+\.-]+://\S+)(?<!,|\.)} LINK_PATTERN = %r{([a-z][a-z0-9\+\.-]+://[^\s>]+)(?<!,|\.)}
# Text matching LINK_PATTERN inside these elements will not be linked # Text matching LINK_PATTERN inside these elements will not be linked
IGNORE_PARENTS = %w(a code kbd pre script style).to_set IGNORE_PARENTS = %w(a code kbd pre script style).to_set
...@@ -35,53 +35,19 @@ module Banzai ...@@ -35,53 +35,19 @@ module Banzai
TEXT_QUERY = %Q(descendant-or-self::text()[ TEXT_QUERY = %Q(descendant-or-self::text()[
not(#{IGNORE_PARENTS.map { |p| "ancestor::#{p}" }.join(' or ')}) not(#{IGNORE_PARENTS.map { |p| "ancestor::#{p}" }.join(' or ')})
and contains(., '://') and contains(., '://')
and not(starts-with(., 'http'))
and not(starts-with(., 'ftp'))
]).freeze ]).freeze
PUNCTUATION_PAIRS = {
"'" => "'",
'"' => '"',
')' => '(',
']' => '[',
'}' => '{'
}.freeze
def call def call
return doc if context[:autolink] == false 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| doc.xpath(TEXT_QUERY).each do |node|
content = node.to_html content = node.to_html
...@@ -97,6 +63,16 @@ module Banzai ...@@ -97,6 +63,16 @@ module Banzai
doc doc
end 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) def autolink_match(match)
# start by stripping out dangerous links # start by stripping out dangerous links
begin begin
...@@ -112,12 +88,30 @@ module Banzai ...@@ -112,12 +88,30 @@ module Banzai
match.gsub!(/((?:&[\w#]+;)+)\z/, '') match.gsub!(/((?:&[\w#]+;)+)\z/, '')
dropped = ($1 || '').html_safe 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) options = link_options.merge(href: match)
content_tag(:a, match, options) + dropped content_tag(:a, match.html_safe, options) + dropped
end end
def autolink_filter(text) 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 end
def link_options def link_options
......
...@@ -23,7 +23,7 @@ module Gitlab ...@@ -23,7 +23,7 @@ module Gitlab
mr_events = event_counts(date_from, :merge_requests) mr_events = event_counts(date_from, :merge_requests)
.having(action: [Event::MERGED, Event::CREATED, Event::CLOSED], target_type: "MergeRequest") .having(action: [Event::MERGED, Event::CREATED, Event::CLOSED], target_type: "MergeRequest")
note_events = event_counts(date_from, :merge_requests) 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]) union = Gitlab::SQL::Union.new([repo_events, issue_events, mr_events, note_events])
events = Event.find_by_sql(union.to_sql).map(&:attributes) events = Event.find_by_sql(union.to_sql).map(&:attributes)
......
...@@ -238,9 +238,9 @@ module Gitlab ...@@ -238,9 +238,9 @@ module Gitlab
self.__send__("#{key}=", options[key.to_sym]) # rubocop:disable GitlabSecurity/PublicSend self.__send__("#{key}=", options[key.to_sym]) # rubocop:disable GitlabSecurity/PublicSend
end end
@loaded_all_data = false
# Retain the actual size before it is encoded # Retain the actual size before it is encoded
@loaded_size = @data.bytesize if @data @loaded_size = @data.bytesize if @data
@loaded_all_data = @loaded_size == size
end end
def binary? def binary?
...@@ -255,10 +255,15 @@ module Gitlab ...@@ -255,10 +255,15 @@ module Gitlab
# memory as a Ruby string. # memory as a Ruby string.
def load_all_data!(repository) def load_all_data!(repository)
return if @data == '' # don't mess with submodule blobs 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| # Even if we return early, recalculate wether this blob is binary in
@data = begin # 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 if is_enabled
repository.gitaly_blob_client.get_blob(oid: id, limit: -1).data repository.gitaly_blob_client.get_blob(oid: id, limit: -1).data
else else
...@@ -269,7 +274,6 @@ module Gitlab ...@@ -269,7 +274,6 @@ module Gitlab
@loaded_all_data = true @loaded_all_data = true
@loaded_size = @data.bytesize @loaded_size = @data.bytesize
@binary = nil
end end
def name def name
......
...@@ -7,6 +7,28 @@ module Gitlab ...@@ -7,6 +7,28 @@ module Gitlab
end end
def new_pointers(object_limit: nil, not_in: nil) 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 @new_pointers ||= begin
rev_list.new_objects(not_in: not_in, require_path: true) do |object_ids| rev_list.new_objects(not_in: not_in, require_path: true) do |object_ids|
object_ids = object_ids.take(object_limit) if object_limit object_ids = object_ids.take(object_limit) if object_limit
...@@ -16,14 +38,12 @@ module Gitlab ...@@ -16,14 +38,12 @@ module Gitlab
end end
end end
def all_pointers def git_all_pointers
rev_list.all_objects(require_path: true) do |object_ids| rev_list.all_objects(require_path: true) do |object_ids|
Gitlab::Git::Blob.batch_lfs_pointers(@repository, object_ids) Gitlab::Git::Blob.batch_lfs_pointers(@repository, object_ids)
end end
end end
private
def rev_list def rev_list
Gitlab::Git::RevList.new(@repository, newrev: @newrev) Gitlab::Git::RevList.new(@repository, newrev: @newrev)
end end
......
...@@ -479,9 +479,8 @@ module Gitlab ...@@ -479,9 +479,8 @@ module Gitlab
raise ArgumentError.new("invalid Repository#log limit: #{limit.inspect}") raise ArgumentError.new("invalid Repository#log limit: #{limit.inspect}")
end end
# TODO support options[:all] in Gitaly https://gitlab.com/gitlab-org/gitaly/issues/1049
gitaly_migrate(:find_commits) do |is_enabled| gitaly_migrate(:find_commits) do |is_enabled|
if is_enabled && !options[:all] if is_enabled
gitaly_commit_client.find_commits(options) gitaly_commit_client.find_commits(options)
else else
raw_log(options).map { |c| Commit.decorate(self, c) } raw_log(options).map { |c| Commit.decorate(self, c) }
...@@ -508,9 +507,8 @@ module Gitlab ...@@ -508,9 +507,8 @@ module Gitlab
def count_commits(options) def count_commits(options)
count_commits_options = process_count_commits_options(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| gitaly_migrate(:count_commits) do |is_enabled|
if is_enabled && !options[:all] if is_enabled
count_commits_by_gitaly(count_commits_options) count_commits_by_gitaly(count_commits_options)
else else
count_commits_by_shelling_out(count_commits_options) count_commits_by_shelling_out(count_commits_options)
......
...@@ -45,16 +45,7 @@ module Gitlab ...@@ -45,16 +45,7 @@ module Gitlab
response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_lfs_pointers, request) response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_lfs_pointers, request)
response.flat_map do |message| map_lfs_pointers(response)
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
def get_blobs(revision_paths, limit = -1) def get_blobs(revision_paths, limit = -1)
...@@ -80,6 +71,50 @@ module Gitlab ...@@ -80,6 +71,50 @@ module Gitlab
GitalyClient::BlobsStitcher.new(response) GitalyClient::BlobsStitcher.new(response)
end 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 end
end end
...@@ -134,7 +134,8 @@ module Gitlab ...@@ -134,7 +134,8 @@ module Gitlab
def commit_count(ref, options = {}) def commit_count(ref, options = {})
request = Gitaly::CountCommitsRequest.new( request = Gitaly::CountCommitsRequest.new(
repository: @gitaly_repo, 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.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? request.before = Google::Protobuf::Timestamp.new(seconds: options[:before].to_i) if options[:before].present?
...@@ -269,6 +270,7 @@ module Gitlab ...@@ -269,6 +270,7 @@ module Gitlab
offset: options[:offset], offset: options[:offset],
follow: options[:follow], follow: options[:follow],
skip_merges: options[:skip_merges], skip_merges: options[:skip_merges],
all: !!options[:all],
disable_walk: true # This option is deprecated. The 'walk' implementation is being removed. disable_walk: true # This option is deprecated. The 'walk' implementation is being removed.
) )
request.after = GitalyClient.timestamp(options[:after]) if options[:after] request.after = GitalyClient.timestamp(options[:after]) if options[:after]
......
...@@ -20,6 +20,7 @@ module Gitlab ...@@ -20,6 +20,7 @@ module Gitlab
gon.sprite_icons = IconsHelper.sprite_icon_path gon.sprite_icons = IconsHelper.sprite_icon_path
gon.sprite_file_icons = IconsHelper.sprite_file_icons_path gon.sprite_file_icons = IconsHelper.sprite_file_icons_path
gon.test_env = Rails.env.test? gon.test_env = Rails.env.test?
gon.suggested_label_colors = LabelsHelper.suggested_colors
if current_user if current_user
gon.current_user_id = current_user.id gon.current_user_id = current_user.id
......
...@@ -3,7 +3,7 @@ module Gitlab ...@@ -3,7 +3,7 @@ module Gitlab
extend self extend self
# For every version update, the version history in import_export.md has to be kept up to date. # 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 FILENAME_LIMIT = 50
def export_path(relative_path:) def export_path(relative_path:)
......
...@@ -65,6 +65,7 @@ project_tree: ...@@ -65,6 +65,7 @@ project_tree:
- :create_access_levels - :create_access_levels
- :project_feature - :project_feature
- :custom_attributes - :custom_attributes
- :project_badges
# Only include the following attributes for the models specified. # Only include the following attributes for the models specified.
included_attributes: included_attributes:
...@@ -125,6 +126,8 @@ excluded_attributes: ...@@ -125,6 +126,8 @@ excluded_attributes:
- :when - :when
push_event_payload: push_event_payload:
- :event_id - :event_id
project_badges:
- :group_id
methods: methods:
labels: labels:
...@@ -147,3 +150,5 @@ methods: ...@@ -147,3 +150,5 @@ methods:
- :action - :action
push_event_payload: push_event_payload:
- :action - :action
project_badges:
- :type
...@@ -16,7 +16,8 @@ module Gitlab ...@@ -16,7 +16,8 @@ module Gitlab
priorities: :label_priorities, priorities: :label_priorities,
auto_devops: :project_auto_devops, auto_devops: :project_auto_devops,
label: :project_label, 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 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
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment