Commit b6fed0ba authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'ce-to-ee-2018-02-06' into 'master'

CE upstream - 2018-02-06 18:23 UTC

Closes gitaly#980 et #4180

See merge request gitlab-org/gitlab-ee!4410
parents b9edd4e8 3de9b1de
...@@ -427,6 +427,8 @@ end ...@@ -427,6 +427,8 @@ end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 0.83.0', require: 'gitaly' gem 'gitaly-proto', '~> 0.83.0', require: 'gitaly'
# Locked until https://github.com/google/protobuf/issues/4210 is closed
gem 'google-protobuf', '= 3.5.1'
gem 'toml-rb', '~> 0.3.15', require: false gem 'toml-rb', '~> 0.3.15', require: false
......
...@@ -365,7 +365,7 @@ GEM ...@@ -365,7 +365,7 @@ GEM
mime-types (~> 3.0) mime-types (~> 3.0)
representable (~> 3.0) representable (~> 3.0)
retriable (>= 2.0, < 4.0) retriable (>= 2.0, < 4.0)
google-protobuf (3.5.1.1) google-protobuf (3.5.1)
googleapis-common-protos-types (1.0.1) googleapis-common-protos-types (1.0.1)
google-protobuf (~> 3.0) google-protobuf (~> 3.0)
googleauth (0.5.3) googleauth (0.5.3)
...@@ -1102,6 +1102,7 @@ DEPENDENCIES ...@@ -1102,6 +1102,7 @@ DEPENDENCIES
gollum-rugged_adapter (~> 0.4.4) gollum-rugged_adapter (~> 0.4.4)
gon (~> 6.1.0) gon (~> 6.1.0)
google-api-client (~> 0.13.6) google-api-client (~> 0.13.6)
google-protobuf (= 3.5.1)
gpgme gpgme
grape (~> 1.0) grape (~> 1.0)
grape-entity (~> 0.6.0) grape-entity (~> 0.6.0)
......
...@@ -35,10 +35,11 @@ export default class Clusters { ...@@ -35,10 +35,11 @@ export default class Clusters {
clusterStatus, clusterStatus,
clusterStatusReason, clusterStatusReason,
helpPath, helpPath,
ingressHelpPath,
} = document.querySelector('.js-edit-cluster-form').dataset; } = document.querySelector('.js-edit-cluster-form').dataset;
this.store = new ClustersStore(); this.store = new ClustersStore();
this.store.setHelpPath(helpPath); this.store.setHelpPaths(helpPath, ingressHelpPath);
this.store.updateStatus(clusterStatus); this.store.updateStatus(clusterStatus);
this.store.updateStatusReason(clusterStatusReason); this.store.updateStatusReason(clusterStatusReason);
this.service = new ClustersService({ this.service = new ClustersService({
...@@ -93,6 +94,7 @@ export default class Clusters { ...@@ -93,6 +94,7 @@ export default class Clusters {
props: { props: {
applications: this.state.applications, applications: this.state.applications,
helpPath: this.state.helpPath, helpPath: this.state.helpPath,
ingressHelpPath: this.state.ingressHelpPath,
}, },
}); });
}, },
......
...@@ -18,6 +18,11 @@ ...@@ -18,6 +18,11 @@
required: false, required: false,
default: '', default: '',
}, },
ingressHelpPath: {
type: String,
required: false,
default: '',
},
}, },
computed: { computed: {
generalApplicationDescription() { generalApplicationDescription() {
...@@ -59,13 +64,28 @@ ...@@ -59,13 +64,28 @@
false, false,
); );
const externalIpParagraph = sprintf(
_.escape(s__(
`ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS
at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}`,
)), {
ingressHelpLink: `<a href="${this.ingressHelpPath}">
${_.escape(s__('ClusterIntegration|More information'))}
</a>`,
},
false,
);
return ` return `
<p> <p>
${descriptionParagraph} ${descriptionParagraph}
</p> </p>
<p class="append-bottom-0"> <p>
${extraCostParagraph} ${extraCostParagraph}
</p> </p>
<p class="settings-message append-bottom-0">
${externalIpParagraph}
</p>
`; `;
}, },
gitlabRunnerDescription() { gitlabRunnerDescription() {
......
...@@ -4,6 +4,7 @@ export default class ClusterStore { ...@@ -4,6 +4,7 @@ export default class ClusterStore {
constructor() { constructor() {
this.state = { this.state = {
helpPath: null, helpPath: null,
ingressHelpPath: null,
status: null, status: null,
statusReason: null, statusReason: null,
applications: { applications: {
...@@ -39,8 +40,9 @@ export default class ClusterStore { ...@@ -39,8 +40,9 @@ export default class ClusterStore {
}; };
} }
setHelpPath(helpPath) { setHelpPaths(helpPath, ingressHelpPath) {
this.state.helpPath = helpPath; this.state.helpPath = helpPath;
this.state.ingressHelpPath = ingressHelpPath;
} }
updateStatus(status) { updateStatus(status) {
......
...@@ -4,6 +4,11 @@ ...@@ -4,6 +4,11 @@
.page-title { .page-title {
margin-top: 0; margin-top: 0;
.color-label {
font-size: $gl-font-size;
padding: $gl-vert-padding $label-padding-modal;
}
} }
} }
......
...@@ -565,6 +565,7 @@ $jq-ui-default-color: #777; ...@@ -565,6 +565,7 @@ $jq-ui-default-color: #777;
* Label * Label
*/ */
$label-padding: 7px; $label-padding: 7px;
$label-padding-modal: 10px;
$label-gray-bg: #f8fafc; $label-gray-bg: #f8fafc;
$label-inverse-bg: #333; $label-inverse-bg: #333;
$label-remove-border: rgba(0, 0, 0, .1); $label-remove-border: rgba(0, 0, 0, .1);
......
...@@ -58,13 +58,13 @@ ...@@ -58,13 +58,13 @@
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
width: 200px; width: 200px;
margin-left: $gl-padding * 2;
margin-bottom: 0; margin-bottom: 0;
} }
.label { .label {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
vertical-align: middle;
max-width: 100%; max-width: 100%;
} }
} }
...@@ -79,26 +79,33 @@ ...@@ -79,26 +79,33 @@
width: 100px; width: 100px;
margin-left: 10px; margin-left: 10px;
margin-bottom: 0; margin-bottom: 0;
vertical-align: middle; vertical-align: top;
} }
} }
.label-description { .label-description {
display: block; display: block;
margin-bottom: 10px; margin-bottom: 10px;
margin-left: 50px;
.description-text {
margin-bottom: $gl-padding;
}
a {
color: $blue-600;
}
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
display: inline-block; display: inline-block;
width: 30%; max-width: 50%;
margin-left: 10px; margin-left: 10px;
margin-bottom: 0; margin-bottom: 0;
vertical-align: middle; vertical-align: top;
} }
} }
.label { .label {
padding: 8px 9px 9px; padding: 8px 12px;
font-size: 14px; font-size: 14px;
} }
} }
...@@ -116,6 +123,12 @@ ...@@ -116,6 +123,12 @@
} }
.manage-labels-list { .manage-labels-list {
@media(min-width: $screen-md-min) {
&.content-list li {
padding: $gl-padding 0;
}
}
> li:not(.empty-message):not(.is-not-draggable) { > li:not(.empty-message):not(.is-not-draggable) {
background-color: $white-light; background-color: $white-light;
cursor: move; cursor: move;
...@@ -133,8 +146,6 @@ ...@@ -133,8 +146,6 @@
} }
.btn-action { .btn-action {
color: $gl-text-color;
.fa { .fa {
font-size: 18px; font-size: 18px;
vertical-align: middle; vertical-align: middle;
...@@ -155,10 +166,18 @@ ...@@ -155,10 +166,18 @@
float: right; float: right;
} }
} }
@media (max-width: $screen-xs-max) {
.dropdown-menu {
min-width: 100%;
}
}
} }
.draggable-handler { .draggable-handler {
display: inline-block; display: inline-block;
vertical-align: top;
margin: 5px 0;
opacity: 0; opacity: 0;
transition: opacity .3s; transition: opacity .3s;
color: $gray-darkest; color: $gray-darkest;
...@@ -188,7 +207,7 @@ ...@@ -188,7 +207,7 @@
.toggle-priority { .toggle-priority {
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: top;
button { button {
border-color: transparent; border-color: transparent;
...@@ -255,6 +274,11 @@ ...@@ -255,6 +274,11 @@
} }
.label-subscribe-button { .label-subscribe-button {
@media(min-width: $screen-md-min) {
min-width: 105px;
margin-left: $gl-padding;
}
.label-subscribe-button-icon { .label-subscribe-button-icon {
&[disabled] { &[disabled] {
opacity: 0.5; opacity: 0.5;
......
...@@ -68,7 +68,7 @@ class Groups::LabelsController < Groups::ApplicationController ...@@ -68,7 +68,7 @@ class Groups::LabelsController < Groups::ApplicationController
respond_to do |format| respond_to do |format|
format.html do format.html do
redirect_to group_labels_path(@group), status: 302, notice: 'Label was removed' redirect_to group_labels_path(@group), status: 302, notice: "#{@label.name} deleted permanently"
end end
format.js format.js
end end
......
...@@ -76,9 +76,9 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -76,9 +76,9 @@ class Projects::WikisController < Projects::ApplicationController
@page = @project_wiki.find_page(params[:id]) @page = @project_wiki.find_page(params[:id])
if @page if @page
@page_versions = Kaminari.paginate_array(@page.versions(page: params[:page]), @page_versions = Kaminari.paginate_array(@page.versions(page: params[:page].to_i),
total_count: @page.count_versions) total_count: @page.count_versions)
.page(params[:page]) .page(params[:page])
else else
redirect_to( redirect_to(
project_wiki_path(@project, :home), project_wiki_path(@project, :home),
......
module GroupsHelper module GroupsHelper
def group_nav_link_paths
%w[groups#projects groups#edit ci_cd#show ldap_group_links#index hooks#index audit_events#index pipeline_quota#index]
end
def can_change_group_visibility_level?(group) def can_change_group_visibility_level?(group)
can?(current_user, :change_visibility_level, group) can?(current_user, :change_visibility_level, group)
end end
......
...@@ -520,10 +520,13 @@ class Project < ActiveRecord::Base ...@@ -520,10 +520,13 @@ class Project < ActiveRecord::Base
@repository ||= Repository.new(full_path, self, disk_path: disk_path) @repository ||= Repository.new(full_path, self, disk_path: disk_path)
end end
def reload_repository! def cleanup
@repository&.cleanup
@repository = nil @repository = nil
end end
alias_method :reload_repository!, :cleanup
def container_registry_url def container_registry_url
if Gitlab.config.registry.enabled if Gitlab.config.registry.enabled
"#{Gitlab.config.registry.host_port}/#{full_path.downcase}" "#{Gitlab.config.registry.host_port}/#{full_path.downcase}"
......
...@@ -100,6 +100,10 @@ class Repository ...@@ -100,6 +100,10 @@ class Repository
alias_method :raw, :raw_repository alias_method :raw, :raw_repository
def cleanup
@raw_repository&.cleanup
end
# Return absolute path to repository # Return absolute path to repository
def path_to_repo def path_to_repo
@path_to_repo ||= File.expand_path( @path_to_repo ||= File.expand_path(
......
module Files module Files
class CreateService < Files::BaseService class CreateService < Files::BaseService
def create_commit! def create_commit!
handler = Lfs::FileModificationHandler.new(project, @branch_name)
handler.new_file(@file_path, @file_content) do |content_or_lfs_pointer|
create_transformed_commit(content_or_lfs_pointer)
end
end
private
def create_transformed_commit(content_or_lfs_pointer)
repository.create_file( repository.create_file(
current_user, current_user,
@file_path, @file_path,
@file_content, content_or_lfs_pointer,
message: @commit_message, message: @commit_message,
branch_name: @branch_name, branch_name: @branch_name,
author_email: @author_email, author_email: @author_email,
......
module Lfs
class FileModificationHandler
attr_reader :project, :branch_name
delegate :repository, to: :project
def initialize(project, branch_name)
@project = project
@branch_name = branch_name
end
def new_file(file_path, file_content)
if project.lfs_enabled? && lfs_file?(file_path)
lfs_pointer_file = Gitlab::Git::LfsPointerFile.new(file_content)
lfs_object = create_lfs_object!(lfs_pointer_file, file_content)
content = lfs_pointer_file.pointer
success = yield(content)
link_lfs_object!(lfs_object) if success
else
yield(file_content)
end
end
private
def lfs_file?(file_path)
repository.attributes_at(branch_name, file_path)['filter'] == 'lfs'
end
def create_lfs_object!(lfs_pointer_file, file_content)
LfsObject.find_or_create_by(oid: lfs_pointer_file.sha256, size: lfs_pointer_file.size) do |lfs_object|
lfs_object.file = CarrierWaveStringFile.new(file_content)
end
end
def link_lfs_object!(lfs_object)
project.lfs_objects << lfs_object
end
end
end
...@@ -107,7 +107,7 @@ ...@@ -107,7 +107,7 @@
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Members') } #{ _('Members') }
- if current_user && can?(current_user, :admin_group, @group) - if current_user && can?(current_user, :admin_group, @group)
= nav_link(path: %w[groups#projects groups#edit ci_cd#show ldap_group_links#index hooks#index audit_events#index pipeline_quota#index]) do = nav_link(path: group_nav_link_paths) do
= link_to edit_group_path(@group) do = link_to edit_group_path(@group) do
.nav-icon-container .nav-icon-container
= sprite_icon('settings') = sprite_icon('settings')
......
...@@ -220,7 +220,7 @@ ...@@ -220,7 +220,7 @@
%p %p
= _('Protip:') = _('Protip:')
= link_to 'Auto DevOps', help_page_path('topics/autodevops/index.md') = link_to 'Auto DevOps', help_page_path('topics/autodevops/index.md')
%span= _('uses clusters to deploy your code!') %span= _('uses Kubernetes clusters to deploy your code!')
%hr %hr
%button.btn.btn-create.btn-xs.dismiss-feature-highlight{ type: 'button' } %button.btn.btn-create.btn-xs.dismiss-feature-highlight{ type: 'button' }
%span= _("Got it!") %span= _("Got it!")
......
...@@ -13,7 +13,8 @@ ...@@ -13,7 +13,8 @@
toggle_status: @cluster.enabled? ? 'true': 'false', toggle_status: @cluster.enabled? ? 'true': 'false',
cluster_status: @cluster.status_name, cluster_status: @cluster.status_name,
cluster_status_reason: @cluster.status_reason, cluster_status_reason: @cluster.status_reason,
help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications') } } help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'),
ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-ip-address') } }
.js-cluster-application-notice .js-cluster-application-notice
.flash-container .flash-container
......
.modal{ id: "modal-delete-label-#{label.id}", tabindex: -1 }
.modal-dialog
.modal-content
.modal-header
%button.close{ data: {dismiss: 'modal' } } &times;
%h3.page-title Delete #{render_colored_label(label, tooltip: false)} ?
.modal-body
%p
%strong= label.name
%span will be permanently deleted from #{label.is_a?(ProjectLabel)? label.project.name : label.group.name}. This cannot be undone.
.modal-footer
%a{ href: '#', data: { dismiss: 'modal' }, class: 'btn btn-default' } Cancel
= link_to 'Delete label',
destroy_label_path(label),
title: 'Delete',
method: :delete,
class: 'btn btn-remove'
...@@ -5,10 +5,10 @@ ...@@ -5,10 +5,10 @@
- show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project) - show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project)
- show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project) - show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project)
%li{ id: label_css_id, data: { id: label.id } } %li.label-list-item{ id: label_css_id, data: { id: label.id } }
= render "shared/label_row", label: label = render "shared/label_row", label: label
.visible-xs.visible-sm-inline-block.visible-md-inline-block.dropdown .visible-xs.visible-sm-inline-block.dropdown
%button.btn.btn-default.label-options-toggle{ type: 'button', data: { toggle: "dropdown" } } %button.btn.btn-default.label-options-toggle{ type: 'button', data: { toggle: "dropdown" } }
Options Options
= icon('caret-down') = icon('caret-down')
...@@ -46,14 +46,19 @@ ...@@ -46,14 +46,19 @@
data: {confirm: 'Remove this label? Are you sure?'}, data: {confirm: 'Remove this label? Are you sure?'},
class: 'text-danger' class: 'text-danger'
.pull-right.hidden-xs.hidden-sm.hidden-md .pull-right.hidden-xs.hidden-sm
- if show_label_merge_requests_link - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group)
= link_to_label(label, subject: subject, type: :merge_request, css_class: 'btn btn-transparent btn-action btn-link') do = link_to promote_project_label_path(label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "You are about to promote #{label.title} to a group level. This will make this milestone available to all projects inside #{label.project.group.name}. The existing project label will be merged into the group level. This action cannot be reversed.", toggle: "tooltip"}, method: :post do
view merge requests %span.sr-only Promote to Group
- if show_label_issues_link = sprite_icon('level-up')
= link_to_label(label, subject: subject, css_class: 'btn btn-transparent btn-action btn-link') do - if can?(current_user, :admin_label, label)
view open issues = link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do
%span.sr-only Edit
= sprite_icon('pencil')
%span{ data: { toggle: 'modal', target: "#modal-delete-label-#{label.id}" } }
= link_to "#", title: "Delete", class: 'btn btn-transparent btn-action remove-row', data: { toggle: "tooltip" } do
%span.sr-only Delete
= sprite_icon('remove')
- if current_user - if current_user
.label-subscription.inline .label-subscription.inline
- if can_subscribe_to_label_in_different_levels?(label) - if can_subscribe_to_label_in_different_levels?(label)
...@@ -76,14 +81,4 @@ ...@@ -76,14 +81,4 @@
%span= label_subscription_toggle_button_text(label, @project) %span= label_subscription_toggle_button_text(label, @project)
= icon('spinner spin', class: 'label-subscribe-button-loading') = icon('spinner spin', class: 'label-subscribe-button-loading')
- if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group) = render 'shared/delete_label_modal', label: label
= link_to promote_project_label_path(label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "You are about to promote #{label.title} to a group level. This will make this milestone available to all projects inside #{label.project.group.name}. The existing project label will be merged into the group level. This action cannot be reversed.", toggle: "tooltip"}, method: :post do
%span.sr-only Promote to Group
= icon('level-up')
- if can?(current_user, :admin_label, label)
= link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do
%span.sr-only Edit
= icon('pencil-square-o')
= link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, data: {confirm: label_deletion_confirm_text(label), toggle: "tooltip"} do
%span.sr-only Delete
= icon('trash-o')
- subject = local_assigns[:subject]
- show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project)
- show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project)
%span.label-row %span.label-row
- if can?(current_user, :admin_label, @project) - if can?(current_user, :admin_label, @project)
.draggable-handler .draggable-handler
...@@ -13,6 +17,14 @@ ...@@ -13,6 +17,14 @@
- if defined?(@project) && @project.group.present? - if defined?(@project) && @project.group.present?
%span.label-type %span.label-type
= label.model_name.human.titleize = label.model_name.human.titleize
- if label.description
%span.label-description %span.label-description
= markdown_field(label, :description) - if label.description.present?
.description-text
= markdown_field(label, :description)
.hidden-xs.hidden-sm
- if show_label_issues_link
= link_to_label(label, subject: subject) { 'Issues' }
- if show_label_merge_requests_link
&middot;
= link_to_label(label, subject: subject, type: :merge_request) { 'Merge requests' }
...@@ -19,6 +19,8 @@ class ProjectCacheWorker ...@@ -19,6 +19,8 @@ class ProjectCacheWorker
update_statistics(project, statistics.map(&:to_sym)) update_statistics(project, statistics.map(&:to_sym))
project.repository.refresh_method_caches(files.map(&:to_sym)) project.repository.refresh_method_caches(files.map(&:to_sym))
project.cleanup
end end
def update_statistics(project, statistics = []) def update_statistics(project, statistics = [])
......
---
title: Add sorting options for /users API (admin only)
merge_request: 16945
author:
type: added
---
title: Add a link to documentation on how to get external ip in the Kubernetes cluster details page
merge_request: 16937
author:
type: added
---
title: Close low level rugged repository in project cache worker
merge_request: 16930
author: Bastian Blank
type: fixed
---
title: Upgrade GitLab Workhorse to v3.6.0
merge_request:
author:
type: other
---
title: Override group sidebar links
merge_request: 16942
author: George Tsiolis
type: fixed
---
title: File Upload UI can create LFS pointers based on .gitattributes
merge_request: 16412
author:
type: fixed
---
title: Downgrade google-protobuf gem
merge_request: 16941
author:
type: other
...@@ -53,6 +53,11 @@ GET /users?blocked=true ...@@ -53,6 +53,11 @@ GET /users?blocked=true
GET /users GET /users
``` ```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `order_by` | string | no | Return projects ordered by `id`, `name`, `username`, `created_at`, or `updated_at` fields. Default is `id` |
| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
```json ```json
[ [
{ {
......
...@@ -24,9 +24,7 @@ module EE ...@@ -24,9 +24,7 @@ module EE
# Returns a list of commits that are not present in any reference # Returns a list of commits that are not present in any reference
def new_commits(newrev) def new_commits(newrev)
refs = ::Gitlab::Git::RevList.new( refs = ::Gitlab::Git::RevList.new(raw, newrev: newrev).new_refs
path_to_repo: path_to_repo,
newrev: newrev).new_refs
refs.map { |sha| commit(sha.strip) } refs.map { |sha| commit(sha.strip) }
end end
......
...@@ -154,8 +154,8 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps ...@@ -154,8 +154,8 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
step 'commit has ci status' do step 'commit has ci status' do
@project.enable_ci @project.enable_ci
pipeline = create :ci_pipeline, project: @project, sha: sample_commit.id @pipeline = create(:ci_pipeline, project: @project, sha: sample_commit.id)
create :ci_build, pipeline: pipeline create(:ci_build, pipeline: @pipeline)
end end
step 'repository contains ".gitlab-ci.yml" file' do step 'repository contains ".gitlab-ci.yml" file' do
...@@ -163,7 +163,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps ...@@ -163,7 +163,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end end
step 'I see commit ci info' do step 'I see commit ci info' do
expect(page).to have_content "Pipeline #1 pending" expect(page).to have_content "Pipeline ##{@pipeline.id} pending"
end end
step 'I search "submodules" commits' do step 'I search "submodules" commits' do
......
...@@ -15,8 +15,9 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps ...@@ -15,8 +15,9 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
step 'I delete all labels' do step 'I delete all labels' do
page.within '.labels' do page.within '.labels' do
page.all('.remove-row').each do page.all('.label-list-item').each do
accept_confirm { first('.remove-row').click } first('.remove-row').click
first(:link, 'Delete label').click
end end
end end
end end
......
...@@ -18,6 +18,14 @@ module API ...@@ -18,6 +18,14 @@ module API
User.find_by(id: id) || not_found!('User') User.find_by(id: id) || not_found!('User')
end end
def reorder_users(users)
if params[:order_by] && params[:sort]
users.reorder(params[:order_by] => params[:sort])
else
users
end
end
params :optional_attributes do params :optional_attributes do
optional :skype, type: String, desc: 'The Skype username' optional :skype, type: String, desc: 'The Skype username'
optional :linkedin, type: String, desc: 'The LinkedIn username' optional :linkedin, type: String, desc: 'The LinkedIn username'
...@@ -38,6 +46,13 @@ module API ...@@ -38,6 +46,13 @@ module API
# EE # EE
optional :shared_runners_minutes_limit, type: Integer, desc: 'Pipeline minutes quota for this user' optional :shared_runners_minutes_limit, type: Integer, desc: 'Pipeline minutes quota for this user'
end end
params :sort_params do
optional :order_by, type: String, values: %w[id name username created_at updated_at],
default: 'id', desc: 'Return users ordered by a field'
optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Return users sorted in ascending and descending order'
end
end end
desc 'Get the list of users' do desc 'Get the list of users' do
...@@ -56,19 +71,21 @@ module API ...@@ -56,19 +71,21 @@ module API
optional :created_before, type: DateTime, desc: 'Return users created before the specified time' optional :created_before, type: DateTime, desc: 'Return users created before the specified time'
all_or_none_of :extern_uid, :provider all_or_none_of :extern_uid, :provider
use :sort_params
use :pagination
# EE # EE
optional :skip_ldap, type: Boolean, default: false, desc: 'Skip LDAP users' optional :skip_ldap, type: Boolean, default: false, desc: 'Skip LDAP users'
use :pagination
end end
get do get do
authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?) authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?)
unless current_user&.admin? unless current_user&.admin?
params.except!(:created_after, :created_before) params.except!(:created_after, :created_before, :order_by, :sort)
end end
users = UsersFinder.new(current_user, params).execute users = UsersFinder.new(current_user, params).execute
users = reorder_users(users)
authorized = can?(current_user, :read_users_list) authorized = can?(current_user, :read_users_list)
......
class CarrierWaveStringFile < StringIO
def original_filename
""
end
end
...@@ -15,8 +15,8 @@ module Gitlab ...@@ -15,8 +15,8 @@ module Gitlab
.ancestor?(oldrev, newrev) .ancestor?(oldrev, newrev)
else else
Gitlab::Git::RevList.new( Gitlab::Git::RevList.new(
path_to_repo: project.repository.path_to_repo, project.repository.raw, oldrev: oldrev, newrev: newrev
oldrev: oldrev, newrev: newrev).missed_ref.present? ).missed_ref.present?
end end
end end
end end
......
...@@ -25,8 +25,7 @@ module Gitlab ...@@ -25,8 +25,7 @@ module Gitlab
private private
def rev_list def rev_list
::Gitlab::Git::RevList.new(path_to_repo: @repository.path_to_repo, Gitlab::Git::RevList.new(@repository, newrev: @newrev)
newrev: @newrev)
end end
end end
end end
......
module Gitlab
module Git
class LfsPointerFile
def initialize(data)
@data = data
end
def pointer
@pointer ||= <<~FILE
version https://git-lfs.github.com/spec/v1
oid sha256:#{sha256}
size #{size}
FILE
end
def size
@size ||= @data.bytesize
end
def sha256
@sha256 ||= Digest::SHA256.hexdigest(@data)
end
end
end
end
...@@ -25,7 +25,7 @@ module Gitlab ...@@ -25,7 +25,7 @@ module Gitlab
stdin.close stdin.close
if lazy_block if lazy_block
return lazy_block.call(stdout.lazy) return [lazy_block.call(stdout.lazy), 0]
else else
cmd_output << stdout.read cmd_output << stdout.read
end end
......
...@@ -128,6 +128,10 @@ module Gitlab ...@@ -128,6 +128,10 @@ module Gitlab
raise NoRepository.new('no repository for such path') raise NoRepository.new('no repository for such path')
end end
def cleanup
@rugged&.close
end
def circuit_breaker def circuit_breaker
@circuit_breaker ||= Gitlab::Git::Storage::CircuitBreaker.for_storage(storage) @circuit_breaker ||= Gitlab::Git::Storage::CircuitBreaker.for_storage(storage)
end end
...@@ -1427,6 +1431,26 @@ module Gitlab ...@@ -1427,6 +1431,26 @@ module Gitlab
end end
end end
def rev_list(including: [], excluding: [], objects: false, &block)
args = ['rev-list']
args.push(*rev_list_param(including))
exclude_param = *rev_list_param(excluding)
if exclude_param.any?
args.push('--not')
args.push(*exclude_param)
end
args.push('--objects') if objects
run_git!(args, lazy_block: block)
end
def missed_ref(oldrev, newrev)
run_git!(['rev-list', '--max-count=1', oldrev, "^#{newrev}"])
end
private private
def local_write_ref(ref_path, ref, old_ref: nil, shell: true) def local_write_ref(ref_path, ref, old_ref: nil, shell: true)
...@@ -1475,7 +1499,7 @@ module Gitlab ...@@ -1475,7 +1499,7 @@ module Gitlab
Rails.logger.error "Unable to create #{ref_path} reference for repository #{path}: #{ex}" Rails.logger.error "Unable to create #{ref_path} reference for repository #{path}: #{ex}"
end end
def run_git(args, chdir: path, env: {}, nice: false, &block) def run_git(args, chdir: path, env: {}, nice: false, lazy_block: nil, &block)
cmd = [Gitlab.config.git.bin_path, *args] cmd = [Gitlab.config.git.bin_path, *args]
cmd.unshift("nice") if nice cmd.unshift("nice") if nice
...@@ -1485,12 +1509,12 @@ module Gitlab ...@@ -1485,12 +1509,12 @@ module Gitlab
end end
circuit_breaker.perform do circuit_breaker.perform do
popen(cmd, chdir, env, &block) popen(cmd, chdir, env, lazy_block: lazy_block, &block)
end end
end end
def run_git!(args, chdir: path, env: {}, nice: false, &block) def run_git!(args, chdir: path, env: {}, nice: false, lazy_block: nil, &block)
output, status = run_git(args, chdir: chdir, env: env, nice: nice, &block) output, status = run_git(args, chdir: chdir, env: env, nice: nice, lazy_block: lazy_block, &block)
raise GitError, output unless status.zero? raise GitError, output unless status.zero?
...@@ -2372,6 +2396,10 @@ module Gitlab ...@@ -2372,6 +2396,10 @@ module Gitlab
rescue Rugged::ReferenceError rescue Rugged::ReferenceError
0 0
end end
def rev_list_param(spec)
spec == :all ? ['--all'] : spec
end
end end
end end
end end
...@@ -5,17 +5,17 @@ module Gitlab ...@@ -5,17 +5,17 @@ module Gitlab
class RevList class RevList
include Gitlab::Git::Popen include Gitlab::Git::Popen
attr_reader :oldrev, :newrev, :path_to_repo attr_reader :oldrev, :newrev, :repository
def initialize(path_to_repo:, newrev:, oldrev: nil) def initialize(repository, newrev:, oldrev: nil)
@oldrev = oldrev @oldrev = oldrev
@newrev = newrev @newrev = newrev
@path_to_repo = path_to_repo @repository = repository
end end
# This method returns an array of new commit references # This method returns an array of new commit references
def new_refs def new_refs
execute([*base_args, newrev, '--not', '--all']) repository.rev_list(including: newrev, excluding: :all).split("\n")
end end
# Finds newly added objects # Finds newly added objects
...@@ -28,66 +28,39 @@ module Gitlab ...@@ -28,66 +28,39 @@ module Gitlab
# When given a block it will yield objects as a lazy enumerator so # When given a block it will yield objects as a lazy enumerator so
# the caller can limit work done instead of processing megabytes of data # the caller can limit work done instead of processing megabytes of data
def new_objects(require_path: nil, not_in: nil, &lazy_block) def new_objects(require_path: nil, not_in: nil, &lazy_block)
args = [*base_args, newrev, *not_in_refs(not_in), '--objects'] opts = {
including: newrev,
excluding: not_in.nil? ? :all : not_in,
require_path: require_path
}
get_objects(args, require_path: require_path, &lazy_block) get_objects(opts, &lazy_block)
end end
def all_objects(require_path: nil, &lazy_block) def all_objects(require_path: nil, &lazy_block)
args = [*base_args, '--all', '--objects'] get_objects(including: :all, require_path: require_path, &lazy_block)
get_objects(args, require_path: require_path, &lazy_block)
end end
# This methods returns an array of missed references # This methods returns an array of missed references
# #
# Should become obsolete after https://gitlab.com/gitlab-org/gitaly/issues/348. # Should become obsolete after https://gitlab.com/gitlab-org/gitaly/issues/348.
def missed_ref def missed_ref
execute([*base_args, '--max-count=1', oldrev, "^#{newrev}"]) repository.missed_ref(oldrev, newrev).split("\n")
end end
private private
def not_in_refs(references)
return ['--not', '--all'] unless references
return [] if references.empty?
references.prepend('--not')
end
def execute(args) def execute(args)
output, status = popen(args, nil, Gitlab::Git::Env.to_env_hash) repository.rev_list(args).split("\n")
unless status.zero?
raise "Got a non-zero exit code while calling out `#{args.join(' ')}`: #{output}"
end
output.split("\n")
end
def lazy_execute(args, &lazy_block)
popen(args, nil, Gitlab::Git::Env.to_env_hash, lazy_block: lazy_block)
end
def base_args
[
Gitlab.config.git.bin_path,
"--git-dir=#{path_to_repo}",
'rev-list'
]
end end
def get_objects(args, require_path: nil) def get_objects(including: [], excluding: [], require_path: nil)
if block_given? opts = { including: including, excluding: excluding, objects: true }
lazy_execute(args) do |lazy_output|
objects = objects_from_output(lazy_output, require_path: require_path)
yield(objects) repository.rev_list(opts) do |lazy_output|
end objects = objects_from_output(lazy_output, require_path: require_path)
else
object_output = execute(args)
objects_from_output(object_output, require_path: require_path) yield(objects)
end end
end end
......
...@@ -96,11 +96,23 @@ module Gitlab ...@@ -96,11 +96,23 @@ module Gitlab
# :per_page - The number of items per page. # :per_page - The number of items per page.
# :limit - Total number of items to return. # :limit - Total number of items to return.
def page_versions(page_path, options = {}) def page_versions(page_path, options = {})
current_page = gollum_page_by_path(page_path) @repository.gitaly_migrate(:wiki_page_versions) do |is_enabled|
if is_enabled
versions = gitaly_wiki_client.page_versions(page_path, options)
# Gitaly uses gollum-lib to get the versions. Gollum defaults to 20
# per page, but also fetches 20 if `limit` or `per_page` < 20.
# Slicing returns an array with the expected number of items.
slice_bound = options[:limit] || options[:per_page] || Gollum::Page.per_page
versions[0..slice_bound]
else
current_page = gollum_page_by_path(page_path)
commits_from_page(current_page, options).map do |gitlab_git_commit| commits_from_page(current_page, options).map do |gitlab_git_commit|
gollum_page = gollum_wiki.page(current_page.title, gitlab_git_commit.id) gollum_page = gollum_wiki.page(current_page.title, gitlab_git_commit.id)
Gitlab::Git::WikiPageVersion.new(gitlab_git_commit, gollum_page&.format) Gitlab::Git::WikiPageVersion.new(gitlab_git_commit, gollum_page&.format)
end
end
end end
end end
......
...@@ -101,6 +101,30 @@ module Gitlab ...@@ -101,6 +101,30 @@ module Gitlab
pages pages
end end
# options:
# :page - The Integer page number.
# :per_page - The number of items per page.
# :limit - Total number of items to return.
def page_versions(page_path, options)
request = Gitaly::WikiGetPageVersionsRequest.new(
repository: @gitaly_repo,
page_path: encode_binary(page_path),
page: options[:page] || 1,
per_page: options[:per_page] || Gollum::Page.per_page
)
stream = GitalyClient.call(@repository.storage, :wiki_service, :wiki_get_page_versions, request)
versions = []
stream.each do |message|
message.versions.each do |version|
versions << new_wiki_page_version(version)
end
end
versions
end
def find_file(name, revision) def find_file(name, revision)
request = Gitaly::WikiFindFileRequest.new( request = Gitaly::WikiFindFileRequest.new(
repository: @gitaly_repo, repository: @gitaly_repo,
...@@ -141,7 +165,7 @@ module Gitlab ...@@ -141,7 +165,7 @@ module Gitlab
private private
# If a block is given and the yielded value is true, iteration will be # If a block is given and the yielded value is truthy, iteration will be
# stopped early at that point; else the iterator is consumed entirely. # stopped early at that point; else the iterator is consumed entirely.
# The iterator is traversed with `next` to allow resuming the iteration. # The iterator is traversed with `next` to allow resuming the iteration.
def wiki_page_from_iterator(iterator) def wiki_page_from_iterator(iterator)
...@@ -158,10 +182,7 @@ module Gitlab ...@@ -158,10 +182,7 @@ module Gitlab
else else
wiki_page = GitalyClient::WikiPage.new(page.to_h) wiki_page = GitalyClient::WikiPage.new(page.to_h)
version = Gitlab::Git::WikiPageVersion.new( version = new_wiki_page_version(page.version)
Gitlab::Git::Commit.decorate(@repository, page.version.commit),
page.version.format
)
end end
end end
...@@ -170,6 +191,13 @@ module Gitlab ...@@ -170,6 +191,13 @@ module Gitlab
[wiki_page, version] [wiki_page, version]
end end
def new_wiki_page_version(version)
Gitlab::Git::WikiPageVersion.new(
Gitlab::Git::Commit.decorate(@repository, version.commit),
version.format
)
end
def gitaly_commit_details(commit_details) def gitaly_commit_details(commit_details)
Gitaly::WikiCommitDetails.new( Gitaly::WikiCommitDetails.new(
name: encode_binary(commit_details.name), name: encode_binary(commit_details.name),
......
...@@ -8,8 +8,8 @@ msgid "" ...@@ -8,8 +8,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: gitlab 1.0.0\n" "Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-02-06 12:32+0100\n" "POT-Creation-Date: 2018-02-06 10:02+0100\n"
"PO-Revision-Date: 2018-02-06 12:32+0100\n" "PO-Revision-Date: 2018-02-06 10:02+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
...@@ -183,6 +183,9 @@ msgstr "" ...@@ -183,6 +183,9 @@ msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr "" msgstr ""
msgid "An error occurred while fetching markdown preview"
msgstr ""
msgid "An error occurred while fetching sidebar data" msgid "An error occurred while fetching sidebar data"
msgstr "" msgstr ""
...@@ -195,12 +198,18 @@ msgstr "" ...@@ -195,12 +198,18 @@ msgstr ""
msgid "An error occurred while rendering KaTeX" msgid "An error occurred while rendering KaTeX"
msgstr "" msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
msgid "An error occurred while retrieving calendar activity" msgid "An error occurred while retrieving calendar activity"
msgstr "" msgstr ""
msgid "An error occurred while retrieving diff" msgid "An error occurred while retrieving diff"
msgstr "" msgstr ""
msgid "An error occurred while validating username"
msgstr ""
msgid "An error occurred. Please try again." msgid "An error occurred. Please try again."
msgstr "" msgstr ""
...@@ -2953,9 +2962,15 @@ msgstr "" ...@@ -2953,9 +2962,15 @@ msgstr ""
msgid "There are problems accessing Git storage: " msgid "There are problems accessing Git storage: "
msgstr "" msgstr ""
msgid "There was an error loading users activity calendar."
msgstr ""
msgid "There was an error saving your notification settings." msgid "There was an error saving your notification settings."
msgstr "" msgstr ""
msgid "There was an error subscribing to this label."
msgstr ""
msgid "There was an error when reseting email token." msgid "There was an error when reseting email token."
msgstr "" msgstr ""
...@@ -3704,5 +3719,5 @@ msgstr "" ...@@ -3704,5 +3719,5 @@ msgstr ""
msgid "username" msgid "username"
msgstr "" msgstr ""
msgid "uses clusters to deploy your code!" msgid "uses Kubernetes clusters to deploy your code!"
msgstr "" msgstr ""
...@@ -14,7 +14,7 @@ describe EE::Repository do ...@@ -14,7 +14,7 @@ describe EE::Repository do
it 'delegates to Gitlab::Git::RevList' do it 'delegates to Gitlab::Git::RevList' do
expect(Gitlab::Git::RevList).to receive(:new).with( expect(Gitlab::Git::RevList).to receive(:new).with(
path_to_repo: repository.path_to_repo, repository.raw,
newrev: 'aaaabbbbccccddddeeeeffffgggghhhhiiiijjjj').and_return(new_refs) newrev: 'aaaabbbbccccddddeeeeffffgggghhhhiiiijjjj').and_return(new_refs)
commits = repository.new_commits('aaaabbbbccccddddeeeeffffgggghhhhiiiijjjj') commits = repository.new_commits('aaaabbbbccccddddeeeeffffgggghhhhiiiijjjj')
......
...@@ -99,7 +99,7 @@ feature 'Prioritize labels' do ...@@ -99,7 +99,7 @@ feature 'Prioritize labels' do
expect(page).to have_content 'wontfix' expect(page).to have_content 'wontfix'
# Sort labels # Sort labels
drag_to(selector: '.js-prioritized-labels', from_index: 1, to_index: 2) drag_to(selector: '.label-list-item', from_index: 1, to_index: 2)
page.within('.prioritized-labels') do page.within('.prioritized-labels') do
expect(first('li')).to have_content('feature') expect(first('li')).to have_content('feature')
......
...@@ -71,7 +71,8 @@ describe('Clusters', () => { ...@@ -71,7 +71,8 @@ describe('Clusters', () => {
helm: { status: APPLICATION_INSTALLABLE, title: 'Helm Tiller' }, helm: { status: APPLICATION_INSTALLABLE, title: 'Helm Tiller' },
}); });
expect(document.querySelector('.js-cluster-application-notice .flash-text')).toBeNull(); const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
expect(flashMessage).toBeNull();
}); });
it('shows an alert when something gets newly installed', () => { it('shows an alert when something gets newly installed', () => {
...@@ -83,8 +84,9 @@ describe('Clusters', () => { ...@@ -83,8 +84,9 @@ describe('Clusters', () => {
helm: { status: APPLICATION_INSTALLED, title: 'Helm Tiller' }, helm: { status: APPLICATION_INSTALLED, title: 'Helm Tiller' },
}); });
expect(document.querySelector('.js-cluster-application-notice .flash-text')).toBeDefined(); const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
expect(document.querySelector('.js-cluster-application-notice .flash-text').textContent.trim()).toEqual('Helm Tiller was successfully installed on your Kubernetes cluster'); expect(flashMessage).not.toBeNull();
expect(flashMessage.textContent.trim()).toEqual('Helm Tiller was successfully installed on your Kubernetes cluster');
}); });
it('shows an alert when multiple things gets newly installed', () => { it('shows an alert when multiple things gets newly installed', () => {
...@@ -98,8 +100,9 @@ describe('Clusters', () => { ...@@ -98,8 +100,9 @@ describe('Clusters', () => {
ingress: { status: APPLICATION_INSTALLED, title: 'Ingress' }, ingress: { status: APPLICATION_INSTALLED, title: 'Ingress' },
}); });
expect(document.querySelector('.js-cluster-application-notice .flash-text')).toBeDefined(); const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
expect(document.querySelector('.js-cluster-application-notice .flash-text').textContent.trim()).toEqual('Helm Tiller, Ingress was successfully installed on your Kubernetes cluster'); expect(flashMessage).not.toBeNull();
expect(flashMessage.textContent.trim()).toEqual('Helm Tiller, Ingress was successfully installed on your Kubernetes cluster');
}); });
}); });
......
...@@ -58,6 +58,7 @@ describe('Clusters Store', () => { ...@@ -58,6 +58,7 @@ describe('Clusters Store', () => {
expect(store.state).toEqual({ expect(store.state).toEqual({
helpPath: null, helpPath: null,
ingressHelpPath: null,
status: mockResponseData.status, status: mockResponseData.status,
statusReason: mockResponseData.status_reason, statusReason: mockResponseData.status_reason,
applications: { applications: {
......
...@@ -2,18 +2,20 @@ require 'spec_helper' ...@@ -2,18 +2,20 @@ require 'spec_helper'
describe Gitlab::Checks::ForcePush do describe Gitlab::Checks::ForcePush do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:repository) { project.repository.raw }
context "exit code checking", :skip_gitaly_mock do context "exit code checking", :skip_gitaly_mock do
it "does not raise a runtime error if the `popen` call to git returns a zero exit code" do it "does not raise a runtime error if the `popen` call to git returns a zero exit code" do
allow_any_instance_of(Gitlab::Git::RevList).to receive(:popen).and_return(['normal output', 0]) allow(repository).to receive(:popen).and_return(['normal output', 0])
expect { described_class.force_push?(project, 'oldrev', 'newrev') }.not_to raise_error expect { described_class.force_push?(project, 'oldrev', 'newrev') }.not_to raise_error
end end
it "raises a runtime error if the `popen` call to git returns a non-zero exit code" do it "raises a GitError error if the `popen` call to git returns a non-zero exit code" do
allow_any_instance_of(Gitlab::Git::RevList).to receive(:popen).and_return(['error', 1]) allow(repository).to receive(:popen).and_return(['error', 1])
expect { described_class.force_push?(project, 'oldrev', 'newrev') }.to raise_error(RuntimeError) expect { described_class.force_push?(project, 'oldrev', 'newrev') }
.to raise_error(Gitlab::Git::Repository::GitError)
end end
end end
end end
require 'spec_helper'
describe Gitlab::Git::LfsPointerFile do
let(:data) { "1234\n" }
subject { described_class.new(data) }
describe '#size' do
it 'counts the bytes' do
expect(subject.size).to eq 5
end
it 'handles non ascii data' do
expect(described_class.new("ääää").size).to eq 8
end
end
describe '#sha256' do
it 'hashes the content correctly' do
expect(subject.sha256).to eq 'a883dafc480d466ee04e0d6da986bd78eb1fdd2178d04693723da3a8f95d42f4'
end
end
describe '#pointer' do
it 'starts with the LFS version' do
expect(subject.pointer).to start_with('version https://git-lfs.github.com/spec/v1')
end
it 'includes sha256' do
expect(subject.pointer).to match(/^oid sha256:[0-9a-fA-F]{64}/)
end
it 'ends with the size' do
expect(subject.pointer).to end_with("\nsize 5\n")
end
end
end
require 'spec_helper' require 'spec_helper'
describe Gitlab::Git::RevList do describe Gitlab::Git::RevList do
let(:project) { create(:project, :repository) } let(:repository) { create(:project, :repository).repository.raw }
let(:rev_list) { described_class.new(newrev: 'newrev', path_to_repo: project.repository.path_to_repo) } let(:rev_list) { described_class.new(repository, newrev: 'newrev') }
let(:env_hash) do let(:env_hash) do
{ {
'GIT_OBJECT_DIRECTORY' => 'foo', 'GIT_OBJECT_DIRECTORY' => 'foo',
'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'bar' 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'bar'
} }
end end
let(:command_env) { { 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'foo:bar' } }
before do before do
allow(Gitlab::Git::Env).to receive(:all).and_return(env_hash.symbolize_keys) allow(Gitlab::Git::Env).to receive(:all).and_return(env_hash)
end end
def args_for_popen(args_list) def args_for_popen(args_list)
[ [Gitlab.config.git.bin_path, 'rev-list', *args_list]
Gitlab.config.git.bin_path,
"--git-dir=#{project.repository.path_to_repo}",
'rev-list',
*args_list
]
end
def stub_popen_rev_list(*additional_args, output:)
args = args_for_popen(additional_args)
expect(rev_list).to receive(:popen).with(args, nil, env_hash)
.and_return([output, 0])
end end
def stub_lazy_popen_rev_list(*additional_args, output:) def stub_popen_rev_list(*additional_args, with_lazy_block: true, output:)
params = [ params = [
args_for_popen(additional_args), args_for_popen(additional_args),
nil, repository.path,
env_hash, command_env,
hash_including(lazy_block: anything) hash_including(lazy_block: with_lazy_block ? anything : nil)
] ]
expect(rev_list).to receive(:popen).with(*params) do |*_, lazy_block:| expect(repository).to receive(:popen).with(*params) do |*_, lazy_block:|
lazy_block.call(output.lines.lazy.map(&:chomp)) output = lazy_block.call(output.lines.lazy.map(&:chomp)) if with_lazy_block
[output, 0]
end end
end end
context "#new_refs" do context "#new_refs" do
it 'calls out to `popen`' do it 'calls out to `popen`' do
stub_popen_rev_list('newrev', '--not', '--all', output: "sha1\nsha2") stub_popen_rev_list('newrev', '--not', '--all', with_lazy_block: false, output: "sha1\nsha2")
expect(rev_list.new_refs).to eq(%w[sha1 sha2]) expect(rev_list.new_refs).to eq(%w[sha1 sha2])
end end
...@@ -55,18 +46,18 @@ describe Gitlab::Git::RevList do ...@@ -55,18 +46,18 @@ describe Gitlab::Git::RevList do
it 'fetches list of newly pushed objects using rev-list' do it 'fetches list of newly pushed objects using rev-list' do
stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2") stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2")
expect(rev_list.new_objects).to eq(%w[sha1 sha2]) expect { |b| rev_list.new_objects(&b) }.to yield_with_args(%w[sha1 sha2])
end end
it 'can skip pathless objects' do it 'can skip pathless objects' do
stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2 path/to/file") stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2 path/to/file")
expect(rev_list.new_objects(require_path: true)).to eq(%w[sha2]) expect { |b| rev_list.new_objects(require_path: true, &b) }.to yield_with_args(%w[sha2])
end end
it 'can handle non utf-8 paths' do it 'can handle non utf-8 paths' do
non_utf_char = [0x89].pack("c*").force_encoding("UTF-8") non_utf_char = [0x89].pack("c*").force_encoding("UTF-8")
stub_lazy_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha2 πå†h/†ø/ƒîlé#{non_utf_char}\nsha1") stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha2 πå†h/†ø/ƒîlé#{non_utf_char}\nsha1")
rev_list.new_objects(require_path: true) do |object_ids| rev_list.new_objects(require_path: true) do |object_ids|
expect(object_ids.force).to eq(%w[sha2]) expect(object_ids.force).to eq(%w[sha2])
...@@ -74,7 +65,7 @@ describe Gitlab::Git::RevList do ...@@ -74,7 +65,7 @@ describe Gitlab::Git::RevList do
end end
it 'can yield a lazy enumerator' do it 'can yield a lazy enumerator' do
stub_lazy_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2") stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2")
rev_list.new_objects do |object_ids| rev_list.new_objects do |object_ids|
expect(object_ids).to be_a Enumerator::Lazy expect(object_ids).to be_a Enumerator::Lazy
...@@ -82,7 +73,7 @@ describe Gitlab::Git::RevList do ...@@ -82,7 +73,7 @@ describe Gitlab::Git::RevList do
end end
it 'returns the result of the block when given' do it 'returns the result of the block when given' do
stub_lazy_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2") stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2")
objects = rev_list.new_objects do |object_ids| objects = rev_list.new_objects do |object_ids|
object_ids.first object_ids.first
...@@ -94,13 +85,13 @@ describe Gitlab::Git::RevList do ...@@ -94,13 +85,13 @@ describe Gitlab::Git::RevList do
it 'can accept list of references to exclude' do it 'can accept list of references to exclude' do
stub_popen_rev_list('newrev', '--not', 'master', '--objects', output: "sha1\nsha2") stub_popen_rev_list('newrev', '--not', 'master', '--objects', output: "sha1\nsha2")
expect(rev_list.new_objects(not_in: ['master'])).to eq(%w[sha1 sha2]) expect { |b| rev_list.new_objects(not_in: ['master'], &b) }.to yield_with_args(%w[sha1 sha2])
end end
it 'handles empty list of references to exclude as listing all known objects' do it 'handles empty list of references to exclude as listing all known objects' do
stub_popen_rev_list('newrev', '--objects', output: "sha1\nsha2") stub_popen_rev_list('newrev', '--objects', output: "sha1\nsha2")
expect(rev_list.new_objects(not_in: [])).to eq(%w[sha1 sha2]) expect { |b| rev_list.new_objects(not_in: [], &b) }.to yield_with_args(%w[sha1 sha2])
end end
end end
...@@ -108,15 +99,15 @@ describe Gitlab::Git::RevList do ...@@ -108,15 +99,15 @@ describe Gitlab::Git::RevList do
it 'fetches list of all pushed objects using rev-list' do it 'fetches list of all pushed objects using rev-list' do
stub_popen_rev_list('--all', '--objects', output: "sha1\nsha2") stub_popen_rev_list('--all', '--objects', output: "sha1\nsha2")
expect(rev_list.all_objects).to eq(%w[sha1 sha2]) expect { |b| rev_list.all_objects(&b) }.to yield_with_args(%w[sha1 sha2])
end end
end end
context "#missed_ref" do context "#missed_ref" do
let(:rev_list) { described_class.new(oldrev: 'oldrev', newrev: 'newrev', path_to_repo: project.repository.path_to_repo) } let(:rev_list) { described_class.new(repository, oldrev: 'oldrev', newrev: 'newrev') }
it 'calls out to `popen`' do it 'calls out to `popen`' do
stub_popen_rev_list('--max-count=1', 'oldrev', '^newrev', output: "sha1\nsha2") stub_popen_rev_list('--max-count=1', 'oldrev', '^newrev', with_lazy_block: false, output: "sha1\nsha2")
expect(rev_list.missed_ref).to eq(%w[sha1 sha2]) expect(rev_list.missed_ref).to eq(%w[sha1 sha2])
end end
......
...@@ -364,18 +364,34 @@ describe WikiPage do ...@@ -364,18 +364,34 @@ describe WikiPage do
end end
describe "#versions" do describe "#versions" do
before do shared_examples 'wiki page versions' do
create_page("Update", "content") let(:page) { wiki.find_page("Update") }
@page = wiki.find_page("Update")
before do
create_page("Update", "content")
end
after do
destroy_page("Update")
end
it "returns an array of all commits for the page" do
3.times { |i| page.update(content: "content #{i}") }
expect(page.versions.count).to eq(4)
end
it 'returns instances of WikiPageVersion' do
expect(page.versions).to all( be_a(Gitlab::Git::WikiPageVersion) )
end
end end
after do context 'when Gitaly is enabled' do
destroy_page("Update") it_behaves_like 'wiki page versions'
end end
it "returns an array of all commits for the page" do context 'when Gitaly is disabled', :disable_gitaly do
3.times { |i| @page.update(content: "content #{i}") } it_behaves_like 'wiki page versions'
expect(@page.versions.count).to eq(4)
end end
end end
......
...@@ -199,6 +199,24 @@ describe API::Users do ...@@ -199,6 +199,24 @@ describe API::Users do
expect(json_response.size).to eq(1) expect(json_response.size).to eq(1)
expect(json_response.first['username']).to eq(user.username) expect(json_response.first['username']).to eq(user.username)
end end
it 'returns the correct order when sorted by id' do
admin
user
get api('/users', admin), { order_by: 'id', sort: 'asc' }
expect(response).to match_response_schema('public_api/v4/user/admins')
expect(json_response.size).to eq(2)
expect(json_response.first['id']).to eq(admin.id)
expect(json_response.last['id']).to eq(user.id)
end
it 'returns 400 when provided incorrect sort params' do
get api('/users', admin), { order_by: 'magic', sort: 'asc' }
expect(response).to have_gitlab_http_status(400)
end
end end
context "when authenticated and ldap is enabled" do context "when authenticated and ldap is enabled" do
......
require "spec_helper"
describe Files::CreateService do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:user) { create(:user) }
let(:file_content) { 'Test file content' }
let(:branch_name) { project.default_branch }
let(:start_branch) { branch_name }
let(:commit_params) do
{
file_path: file_path,
commit_message: "Update File",
file_content: file_content,
file_content_encoding: "text",
start_project: project,
start_branch: start_branch,
branch_name: branch_name
}
end
subject { described_class.new(project, user, commit_params) }
before do
project.add_master(user)
end
describe "#execute" do
context 'when file matches LFS filter' do
let(:file_path) { 'test_file.lfs' }
let(:branch_name) { 'lfs' }
context 'with LFS disabled' do
it 'skips gitattributes check' do
expect(repository).not_to receive(:attributes_at)
subject.execute
end
it "doesn't create LFS pointers" do
subject.execute
blob = repository.blob_at('lfs', file_path)
expect(blob.data).not_to start_with('version https://git-lfs.github.com/spec/v1')
expect(blob.data).to eq(file_content)
end
end
context 'with LFS enabled' do
before do
allow(project).to receive(:lfs_enabled?).and_return(true)
end
it 'creates an LFS pointer' do
subject.execute
blob = repository.blob_at('lfs', file_path)
expect(blob.data).to start_with('version https://git-lfs.github.com/spec/v1')
end
it "creates an LfsObject with the file's content" do
subject.execute
expect(LfsObject.last.file.read).to eq file_content
end
it 'links the LfsObject to the project' do
expect do
subject.execute
end.to change { project.lfs_objects.count }.by(1)
end
end
end
end
end
...@@ -108,7 +108,7 @@ describe MergeRequests::RebaseService do ...@@ -108,7 +108,7 @@ describe MergeRequests::RebaseService do
context 'git commands', :disable_gitaly do context 'git commands', :disable_gitaly do
it 'sets GL_REPOSITORY env variable when calling git commands' do it 'sets GL_REPOSITORY env variable when calling git commands' do
expect(repository).to receive(:popen).exactly(3) expect(repository).to receive(:popen).exactly(3)
.with(anything, anything, hash_including('GL_REPOSITORY')) .with(anything, anything, hash_including('GL_REPOSITORY'), anything)
.and_return(['', 0]) .and_return(['', 0])
service.execute(merge_request) service.execute(merge_request)
......
...@@ -154,7 +154,7 @@ describe MergeRequests::SquashService do ...@@ -154,7 +154,7 @@ describe MergeRequests::SquashService do
) )
allow(repository).to receive(:popen).and_return(['', 0]) allow(repository).to receive(:popen).and_return(['', 0])
allow(repository).to receive(:popen).with(git_command, anything, anything).and_return([error, 1]) allow(repository).to receive(:popen).with(git_command, anything, anything, anything).and_return([error, 1])
end end
it 'logs the stage and output' do it 'logs the stage and output' do
......
...@@ -59,6 +59,29 @@ describe FileUploader do ...@@ -59,6 +59,29 @@ describe FileUploader do
end end
end end
describe 'callbacks' do
describe '#prune_store_dir after :remove' do
before do
uploader.store!(fixture_file_upload('spec/fixtures/doc_sample.txt'))
end
def store_dir
File.expand_path(uploader.store_dir, uploader.root)
end
it 'is called' do
expect(uploader).to receive(:prune_store_dir).once
uploader.remove!
end
it 'prune the store directory' do
expect { uploader.remove! }
.to change { File.exist?(store_dir) }.from(true).to(false)
end
end
end
describe '#secret' do describe '#secret' do
it 'generates a secret if none is provided' do it 'generates a secret if none is provided' do
expect(described_class).to receive(:generate_secret).and_return('secret') expect(described_class).to receive(:generate_secret).and_return('secret')
......
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