Commit 9586a57e authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge remote-tracking branch 'ce-com/master' into ce-to-ee

Signed-off-by: default avatarDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
parents 9567a1ee 4b3011e1
...@@ -72,16 +72,18 @@ stages: ...@@ -72,16 +72,18 @@ stages:
- docker.elastic.co/elasticsearch/elasticsearch:5.3.2 - docker.elastic.co/elasticsearch/elasticsearch:5.3.2
.only-master-and-ee-or-mysql: &only-master-and-ee-or-mysql .only-if-want-mysql: &only-if-want-mysql
only: only:
- /mysql/ - /mysql/
- /-stable/ - /-stable/
- master@gitlab-org/gitlab-ce - master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
- master@gitlab/gitlabhq - master@gitlab/gitlabhq
- master@gitlab/gitlab-ee
- tags@gitlab-org/gitlab-ce - tags@gitlab-org/gitlab-ce
- tags@gitlab-org/gitlab-ee
- tags@gitlab/gitlabhq - tags@gitlab/gitlabhq
- //@gitlab-org/gitlab-ee - tags@gitlab/gitlab-ee
- //@gitlab/gitlab-ee
# Skip all jobs except the ones that begin with 'docs/'. # Skip all jobs except the ones that begin with 'docs/'.
# Used for commits including ONLY documentation changes. # Used for commits including ONLY documentation changes.
...@@ -120,7 +122,7 @@ stages: ...@@ -120,7 +122,7 @@ stages:
.rspec-knapsack-mysql: &rspec-knapsack-mysql .rspec-knapsack-mysql: &rspec-knapsack-mysql
<<: *rspec-knapsack <<: *rspec-knapsack
<<: *use-mysql <<: *use-mysql
<<: *only-master-and-ee-or-mysql <<: *only-if-want-mysql
<<: *except-docs <<: *except-docs
.spinach-knapsack: &spinach-knapsack .spinach-knapsack: &spinach-knapsack
...@@ -152,7 +154,7 @@ stages: ...@@ -152,7 +154,7 @@ stages:
.spinach-knapsack-mysql: &spinach-knapsack-mysql .spinach-knapsack-mysql: &spinach-knapsack-mysql
<<: *spinach-knapsack <<: *spinach-knapsack
<<: *use-mysql <<: *use-mysql
<<: *only-master-and-ee-or-mysql <<: *only-if-want-mysql
<<: *except-docs <<: *except-docs
.only-canonical-masters: &only-canonical-masters .only-canonical-masters: &only-canonical-masters
......
...@@ -406,7 +406,7 @@ gem 'sys-filesystem', '~> 1.1.6' ...@@ -406,7 +406,7 @@ gem 'sys-filesystem', '~> 1.1.6'
gem 'net-ntp' gem 'net-ntp'
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly', '~> 0.23.0' gem 'gitaly', '~> 0.24.0'
gem 'toml-rb', '~> 0.3.15', require: false gem 'toml-rb', '~> 0.3.15', require: false
......
...@@ -293,7 +293,7 @@ GEM ...@@ -293,7 +293,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 (0.23.0) gitaly (0.24.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.0) grpc (~> 1.0)
github-linguist (4.7.6) github-linguist (4.7.6)
...@@ -1008,7 +1008,7 @@ DEPENDENCIES ...@@ -1008,7 +1008,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 (~> 0.23.0) gitaly (~> 0.24.0)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
......
...@@ -383,7 +383,10 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -383,7 +383,10 @@ import initGroupAnalytics from './init_group_analytics';
setupProjectEdit(); setupProjectEdit();
// Initialize expandable settings panels // Initialize expandable settings panels
initSettingsPanels(); initSettingsPanels();
<<<<<<< HEAD
new UsersSelect(); new UsersSelect();
=======
>>>>>>> ce-com/master
break; break;
case 'projects:imports:show': case 'projects:imports:show':
new ProjectImport(); new ProjectImport();
......
<script> <script>
import loadingIcon from '~/vue_shared/components/loading_icon.vue'; import loadingIcon from '~/vue_shared/components/loading_icon.vue';
<<<<<<< HEAD
import linkedPipelinesColumn from './linked_pipelines_column.vue'; import linkedPipelinesColumn from './linked_pipelines_column.vue';
=======
import '~/flash';
>>>>>>> ce-com/master
import stageColumnComponent from './stage_column_component.vue'; import stageColumnComponent from './stage_column_component.vue';
export default { export default {
......
...@@ -7,6 +7,14 @@ const LOADING_HTML = ` ...@@ -7,6 +7,14 @@ const LOADING_HTML = `
</div> </div>
`; `;
function getSystemDate(systemUtcOffsetSeconds) {
const date = new Date();
const localUtcOffsetMinutes = 0 - date.getTimezoneOffset();
const systemUtcOffsetMinutes = systemUtcOffsetSeconds / 60;
date.setMinutes((date.getMinutes() - localUtcOffsetMinutes) + systemUtcOffsetMinutes);
return date;
}
function formatTooltipText({ date, count }) { function formatTooltipText({ date, count }) {
const dateObject = new Date(date); const dateObject = new Date(date);
const dateDayName = gl.utils.getDayName(dateObject); const dateDayName = gl.utils.getDayName(dateObject);
...@@ -22,7 +30,7 @@ function formatTooltipText({ date, count }) { ...@@ -22,7 +30,7 @@ function formatTooltipText({ date, count }) {
const initColorKey = () => d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]); const initColorKey = () => d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]);
export default class ActivityCalendar { export default class ActivityCalendar {
constructor(container, timestamps, calendarActivitiesPath) { constructor(container, timestamps, calendarActivitiesPath, utcOffset = 0) {
this.calendarActivitiesPath = calendarActivitiesPath; this.calendarActivitiesPath = calendarActivitiesPath;
this.clickDay = this.clickDay.bind(this); this.clickDay = this.clickDay.bind(this);
this.currentSelectedDate = ''; this.currentSelectedDate = '';
...@@ -37,7 +45,7 @@ export default class ActivityCalendar { ...@@ -37,7 +45,7 @@ export default class ActivityCalendar {
this.timestampsTmp = []; this.timestampsTmp = [];
let group = 0; let group = 0;
const today = new Date(); const today = getSystemDate(utcOffset);
today.setHours(0, 0, 0, 0, 0); today.setHours(0, 0, 0, 0, 0);
const oneYearAgo = new Date(today); const oneYearAgo = new Date(today);
......
...@@ -150,15 +150,21 @@ export default class UserTabs { ...@@ -150,15 +150,21 @@ export default class UserTabs {
const $calendarWrap = this.$parentEl.find('.user-calendar'); const $calendarWrap = this.$parentEl.find('.user-calendar');
const calendarPath = $calendarWrap.data('calendarPath'); const calendarPath = $calendarWrap.data('calendarPath');
const calendarActivitiesPath = $calendarWrap.data('calendarActivitiesPath'); const calendarActivitiesPath = $calendarWrap.data('calendarActivitiesPath');
const utcOffset = $calendarWrap.data('utcOffset');
let utcFormatted = 'UTC';
if (utcOffset !== 0) {
utcFormatted = `UTC${utcOffset > 0 ? '+' : ''}${(utcOffset / 3600)}`;
}
$.ajax({ $.ajax({
dataType: 'json', dataType: 'json',
url: calendarPath, url: calendarPath,
success: (activityData) => { success: (activityData) => {
$calendarWrap.html(CALENDAR_TEMPLATE); $calendarWrap.html(CALENDAR_TEMPLATE);
$calendarWrap.find('.calendar-hint').append(`(Timezone: ${utcFormatted})`);
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new ActivityCalendar('.js-contrib-calendar', activityData, calendarActivitiesPath); new ActivityCalendar('.js-contrib-calendar', activityData, calendarActivitiesPath, utcOffset);
}, },
}); });
......
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
margin-top: -23px; margin-top: -23px;
float: right; float: right;
font-size: 12px; font-size: 12px;
direction: ltr;
} }
.pika-single.gitlab-theme { .pika-single.gitlab-theme {
......
...@@ -329,9 +329,9 @@ header { ...@@ -329,9 +329,9 @@ header {
li { li {
.badge { .badge {
position: inherit; position: inherit;
top: -3px; top: -8px;
font-weight: normal; font-weight: normal;
margin-left: -12px; margin-left: -11px;
font-size: 11px; font-size: 11px;
color: $white-light; color: $white-light;
padding: 1px 5px 2px; padding: 1px 5px 2px;
......
...@@ -328,9 +328,17 @@ ...@@ -328,9 +328,17 @@
margin-bottom: 10px; margin-bottom: 10px;
color: $issuable-sidebar-color; color: $issuable-sidebar-color;
svg {
fill: $issuable-sidebar-color;
}
&:hover, &:hover,
&:hover .todo-undone { &:hover .todo-undone {
color: $gl-text-color; color: $gl-text-color;
svg {
fill: $gl-text-color;
}
} }
span { span {
......
...@@ -620,17 +620,26 @@ class Repository ...@@ -620,17 +620,26 @@ class Repository
end end
def last_commit_for_path(sha, path) def last_commit_for_path(sha, path)
sha = last_commit_id_for_path(sha, path) raw_repository.gitaly_migrate(:last_commit_for_path) do |is_enabled|
commit(sha) if is_enabled
last_commit_for_path_by_gitaly(sha, path)
else
last_commit_for_path_by_rugged(sha, path)
end
end
end end
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/383
def last_commit_id_for_path(sha, path) def last_commit_id_for_path(sha, path)
key = path.blank? ? "last_commit_id_for_path:#{sha}" : "last_commit_id_for_path:#{sha}:#{Digest::SHA1.hexdigest(path)}" key = path.blank? ? "last_commit_id_for_path:#{sha}" : "last_commit_id_for_path:#{sha}:#{Digest::SHA1.hexdigest(path)}"
cache.fetch(key) do cache.fetch(key) do
args = %W(#{Gitlab.config.git.bin_path} rev-list --max-count=1 #{sha} -- #{path}) raw_repository.gitaly_migrate(:last_commit_for_path) do |is_enabled|
Gitlab::Popen.popen(args, path_to_repo).first.strip if is_enabled
last_commit_for_path_by_gitaly(sha, path).id
else
last_commit_id_for_path_by_shelling_out(sha, path)
end
end
end end
end end
...@@ -1218,6 +1227,21 @@ class Repository ...@@ -1218,6 +1227,21 @@ class Repository
Rugged::Commit.create(rugged, params) Rugged::Commit.create(rugged, params)
end end
def last_commit_for_path_by_gitaly(sha, path)
c = raw_repository.gitaly_commit_client.last_commit_for_path(sha, path)
commit(c)
end
def last_commit_for_path_by_rugged(sha, path)
sha = last_commit_id_for_path_by_shelling_out(sha, path)
commit(sha)
end
def last_commit_id_for_path_by_shelling_out(sha, path)
args = %W(#{Gitlab.config.git.bin_path} rev-list --max-count=1 #{sha} -- #{path})
Gitlab::Popen.popen(args, path_to_repo).first.strip
end
def repository_storage_path def repository_storage_path
@project.repository_storage_path @project.repository_storage_path
end end
......
...@@ -12,7 +12,6 @@ module MergeRequests ...@@ -12,7 +12,6 @@ module MergeRequests
merge_request.source_project = source_project merge_request.source_project = source_project
merge_request.source_branch = params[:source_branch] merge_request.source_branch = params[:source_branch]
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
merge_request.head_pipeline = head_pipeline_for(merge_request)
create(merge_request) create(merge_request)
end end
...@@ -22,10 +21,16 @@ module MergeRequests ...@@ -22,10 +21,16 @@ module MergeRequests
notification_service.new_merge_request(issuable, current_user) notification_service.new_merge_request(issuable, current_user)
todo_service.new_merge_request(issuable, current_user) todo_service.new_merge_request(issuable, current_user)
issuable.cache_merge_request_closes_issues!(current_user) issuable.cache_merge_request_closes_issues!(current_user)
update_merge_requests_head_pipeline(issuable)
end end
private private
def update_merge_requests_head_pipeline(merge_request)
pipeline = head_pipeline_for(merge_request)
merge_request.update(head_pipeline_id: pipeline.id) if pipeline
end
def head_pipeline_for(merge_request) def head_pipeline_for(merge_request)
return unless merge_request.source_project return unless merge_request.source_project
......
- page_title "CI Lint" - page_title "CI Lint"
- page_description "Validate your GitLab CI configuration file" - page_description "Validate your GitLab CI configuration file"
- content_for :page_specific_javascripts do - content_for :library_javascripts do
= page_specific_javascript_tag('lib/ace.js') = page_specific_javascript_tag('lib/ace.js')
%h2 Check your .gitlab-ci.yml %h2 Check your .gitlab-ci.yml
......
...@@ -38,6 +38,9 @@ ...@@ -38,6 +38,9 @@
= Gon::Base.render_data = Gon::Base.render_data
- if content_for?(:library_javascripts)
= yield :library_javascripts
= webpack_bundle_tag "webpack_runtime" = webpack_bundle_tag "webpack_runtime"
= webpack_bundle_tag "common" = webpack_bundle_tag "common"
= webpack_bundle_tag "locale" = webpack_bundle_tag "locale"
......
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
= icon('tachometer fw') = icon('tachometer fw')
%li %li
= link_to assigned_issues_dashboard_path, title: 'Issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = link_to assigned_issues_dashboard_path, title: 'Issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('hashtag fw') = custom_icon('issues')
- issues_count = assigned_issuables_count(:issues) - issues_count = assigned_issuables_count(:issues)
%span.badge.issues-count{ class: ('hidden' if issues_count.zero?) } %span.badge.issues-count{ class: ('hidden' if issues_count.zero?) }
= number_with_delimiter(issues_count) = number_with_delimiter(issues_count)
......
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
= icon('tachometer fw') = icon('tachometer fw')
%li %li
= link_to assigned_issues_dashboard_path, title: 'Issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = link_to assigned_issues_dashboard_path, title: 'Issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('hashtag fw') = custom_icon('issues')
- issues_count = assigned_issuables_count(:issues) - issues_count = assigned_issuables_count(:issues)
%span.badge.issues-count{ class: ('hidden' if issues_count.zero?) } %span.badge.issues-count{ class: ('hidden' if issues_count.zero?) }
= number_with_delimiter(issues_count) = number_with_delimiter(issues_count)
......
...@@ -30,7 +30,11 @@ ...@@ -30,7 +30,11 @@
%span %span
Contribution Analytics Contribution Analytics
- if current_user && can?(current_user, :admin_group, @group) - if current_user && can?(current_user, :admin_group, @group)
<<<<<<< HEAD
= nav_link(path: %w[groups#projects groups#edit ldap_group_links#index hooks#index audit_events#index pipeline_quota#index]) do = nav_link(path: %w[groups#projects groups#edit ldap_group_links#index hooks#index audit_events#index pipeline_quota#index]) do
=======
= nav_link(path: %w[groups#projects groups#edit ci_cd#show]) do
>>>>>>> ce-com/master
= link_to edit_group_path(@group), title: 'Settings' do = link_to edit_group_path(@group), title: 'Settings' do
%span %span
Settings Settings
...@@ -121,7 +121,7 @@ ...@@ -121,7 +121,7 @@
= nav_link(controller: :spam_logs) do = nav_link(controller: :spam_logs) do
= link_to admin_spam_logs_path, title: "Spam Logs" do = link_to admin_spam_logs_path, title: "Spam Logs" do
.nav-icon-container .nav-icon-container
= custom_icon('mr_bold') = custom_icon('spam_logs')
%span.nav-item-name %span.nav-item-name
Spam Logs Spam Logs
......
...@@ -94,5 +94,8 @@ ...@@ -94,5 +94,8 @@
= link_to group_settings_ci_cd_path(@group), title: 'CI / CD' do = link_to group_settings_ci_cd_path(@group), title: 'CI / CD' do
%span %span
CI / CD CI / CD
<<<<<<< HEAD
= render "groups/ee/settings_nav" = render "groups/ee/settings_nav"
=======
>>>>>>> ce-com/master
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
= nav_link(controller: %w[projects/registry/repositories]) do = nav_link(controller: %w[projects/registry/repositories]) do
= link_to project_container_registry_index_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do = link_to project_container_registry_index_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
.nav-icon-container .nav-icon-container
= custom_icon('mr_bold') = custom_icon('container_registry')
%span.nav-item-name %span.nav-item-name
Registry Registry
......
- form = local_assigns.fetch(:form) - form = local_assigns.fetch(:form)
<<<<<<< HEAD
= render 'projects/ee/merge_request_settings', form: form, project: @project = render 'projects/ee/merge_request_settings', form: form, project: @project
=======
>>>>>>> ce-com/master
= render 'projects/merge_request_merge_settings', form: form = render 'projects/merge_request_merge_settings', form: form
- page_title "General" - page_title "General"
- @content_class = "limit-container-width" unless fluid_layout - @content_class = "limit-container-width" unless fluid_layout
- expanded = Rails.env.test? - expanded = Rails.env.test?
<<<<<<< HEAD
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= webpack_bundle_tag('common_vue') = webpack_bundle_tag('common_vue')
= webpack_bundle_tag('service_desk') = webpack_bundle_tag('service_desk')
=======
>>>>>>> ce-com/master
= render "projects/settings/head" = render "projects/settings/head"
...@@ -49,6 +52,10 @@ ...@@ -49,6 +52,10 @@
= f.label :tag_list, "Tags", class: 'label-light' = f.label :tag_list, "Tags", class: 'label-light'
= f.text_field :tag_list, value: @project.tag_list.sort.join(', '), maxlength: 2000, class: "form-control" = f.text_field :tag_list, value: @project.tag_list.sort.join(', '), maxlength: 2000, class: "form-control"
%p.help-block Separate tags with commas. %p.help-block Separate tags with commas.
<<<<<<< HEAD
=======
%fieldset.features
>>>>>>> ce-com/master
%h5.prepend-top-0 %h5.prepend-top-0
Project avatar Project avatar
.form-group .form-group
...@@ -116,6 +123,7 @@ ...@@ -116,6 +123,7 @@
%span.help-block Share code pastes with others out of Git repository %span.help-block Share code pastes with others out of Git repository
.col-md-4 .col-md-4
= project_feature_access_select(:snippets_access_level) = project_feature_access_select(:snippets_access_level)
<<<<<<< HEAD
.row .row
.col-md-8.project-feature .col-md-8.project-feature
...@@ -183,6 +191,59 @@ ...@@ -183,6 +191,59 @@
enabled: "#{@project.service_desk_enabled}", enabled: "#{@project.service_desk_enabled}",
incoming_email: (@project.service_desk_address if @project.service_desk_enabled) } } incoming_email: (@project.service_desk_address if @project.service_desk_enabled) } }
=======
.row
.col-md-8.project-feature
= feature_fields.label :issues_access_level, "Issues", class: 'label-light'
%span.help-block Lightweight issue tracking system for this project
.col-md-4
= project_feature_access_select(:issues_access_level)
.row
.col-md-8.project-feature
= feature_fields.label :wiki_access_level, "Wiki", class: 'label-light'
%span.help-block Pages for project documentation
.col-md-4
= project_feature_access_select(:wiki_access_level)
.form-group
= render 'shared/allow_request_access', form: f
- if Gitlab.config.lfs.enabled && current_user.admin?
.row.js-lfs-enabled.form-group.sharing-and-permissions
.col-md-8
= f.label :lfs_enabled, 'Git Large File Storage', class: 'label-light'
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
%span.help-block Manages large files such as audio, video and graphics files.
.col-md-4
.select-wrapper
= f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control project-repo-select select-control', data: { field: 'lfs_enabled' }
= icon('chevron-down')
- if Gitlab.config.registry.enabled
.form-group.js-container-registry{ style: ("display: none;" if @project.project_feature.send(:repository_access_level) == 0) }
.checkbox
= f.label :container_registry_enabled do
= f.check_box :container_registry_enabled
%strong Container Registry
%br
%span.descr Enable Container Registry for this project
= link_to icon('question-circle'), help_page_path('user/project/container_registry'), target: '_blank'
= f.submit 'Save changes', class: "btn btn-save"
%section.settings.merge-requests-feature{ style: ("display: none;" if @project.project_feature.send(:merge_requests_access_level) == 0) }
.settings-header
%h4
Merge request settings
%button.btn.js-settings-toggle
= expanded ? 'Collapse' : 'Expand'
%p
Customize your merge request restrictions.
.settings-content.no-animate{ class: ('expanded' if expanded) }
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "merge-request-settings-form" }, authenticity_token: true do |f|
= render 'merge_request_settings', form: f
= f.submit 'Save changes', class: "btn btn-save"
>>>>>>> ce-com/master
%section.settings %section.settings
.settings-header .settings-header
%h4 %h4
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
- else - else
= s_("PipelineSchedules|None") = s_("PipelineSchedules|None")
%td.next-run-cell %td.next-run-cell
- if pipeline_schedule.active? - if pipeline_schedule.active? && pipeline_schedule.next_run_at
= time_ago_with_tooltip(pipeline_schedule.real_next_run) = time_ago_with_tooltip(pipeline_schedule.real_next_run)
- else - else
= s_("PipelineSchedules|Inactive") = s_("PipelineSchedules|Inactive")
......
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="m16 11.764v-8.764c0-1.657-1.343-3-3-3h-10c-1.657 0-3 1.343-3 3v8.764c.531-.475 1.232-.764 2-.764v-8c0-.552.448-1 1-1h10c.552 0 1 .448 1 1v8c.768 0 1.469.289 2 .764m-14 .236h12c1.105 0 2 .895 2 2 0 1.105-.895 2-2 2h-12c-1.105 0-2-.895-2-2 0-1.105.895-2 2-2m10 1c-.552 0-1 .448-1 1 0 .552.448 1 1 1 .552 0 1-.448 1-1 0-.552-.448-1-1-1"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M8.75.433l5.428 3.134a1.5 1.5 0 0 1 .75 1.299v6.268a1.5 1.5 0 0 1-.75 1.299L8.75 15.567a1.5 1.5 0 0 1-1.5 0l-5.428-3.134a1.5 1.5 0 0 1-.75-1.299V4.866a1.5 1.5 0 0 1 .75-1.299L7.25.433a1.5 1.5 0 0 1 1.5 0zM3.072 5.155v5.69L8 13.691l4.928-2.846v-5.69L8 2.309 3.072 5.155zM8 4a1 1 0 0 1 1 1v3a1 1 0 1 1-2 0V5a1 1 0 0 1 1-1zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></svg>
...@@ -67,7 +67,7 @@ ...@@ -67,7 +67,7 @@
.block.issues .block.issues
.sidebar-collapsed-icon .sidebar-collapsed-icon
%strong %strong
= icon('hashtag', 'aria-hidden': 'true') = custom_icon('issues')
%span= milestone.issues_visible_to_user(current_user).count %span= milestone.issues_visible_to_user(current_user).count
.title.hide-collapsed .title.hide-collapsed
Issues Issues
......
%h4.prepend-top-20 %h4.prepend-top-20
Contributions for Contributions for
%strong= @calendar_date.to_s(:short) %strong= @calendar_date.to_s(:medium)
- if @events.any? - if @events.any?
%ul.bordered-list %ul.bordered-list
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
%li %li
%span.light %span.light
%i.fa.fa-clock-o %i.fa.fa-clock-o
= event.created_at.to_s(:time) = event.created_at.strftime('%-I:%M%P')
- if event.push? - if event.push?
#{event.action_name} #{event.ref_type} #{event.action_name} #{event.ref_type}
%strong %strong
...@@ -30,4 +30,4 @@ ...@@ -30,4 +30,4 @@
= event.project_name = event.project_name
- else - else
%p %p
No contributions found for #{@calendar_date.to_s(:short)} No contributions found for #{@calendar_date.to_s(:medium)}
...@@ -104,7 +104,7 @@ ...@@ -104,7 +104,7 @@
.tab-content .tab-content
#activity.tab-pane #activity.tab-pane
.row-content-block.calender-block.white.second-block.hidden-xs .row-content-block.calender-block.white.second-block.hidden-xs
.user-calendar{ data: { calendar_path: user_calendar_path(@user, :json), calendar_activities_path: user_calendar_activities_path } } .user-calendar{ data: { calendar_path: user_calendar_path(@user, :json), calendar_activities_path: user_calendar_activities_path, utc_offset: Time.zone.utc_offset } }
%h4.center.light %h4.center.light
%i.fa.fa-spinner.fa-spin %i.fa.fa-spinner.fa-spin
.user-calendar-activities .user-calendar-activities
......
---
title: Fix timezone inconsistencies in user contribution graph
merge_request: 13208
author:
---
title: Fix an order of operations for CI connection error message in merge request
widget
merge_request: 13252
author:
---
title: Fix pipeline_schedules pages when active schedule has an abnormal state
merge_request: 13286
author:
...@@ -80,7 +80,7 @@ Install, upgrade, integrate, migrate to GitLab: ...@@ -80,7 +80,7 @@ Install, upgrade, integrate, migrate to GitLab:
| :------------ | :------: | --------------: | | :------------ | :------: | --------------: |
| [Video Tutorial: Idea to Production on Google Container Engine (GKE)](https://about.gitlab.com/2017/01/23/video-tutorial-idea-to-production-on-google-container-engine-gke/) | Tutorial | 2017/01/23 | | [Video Tutorial: Idea to Production on Google Container Engine (GKE)](https://about.gitlab.com/2017/01/23/video-tutorial-idea-to-production-on-google-container-engine-gke/) | Tutorial | 2017/01/23 |
| [How to Setup a GitLab Instance on Microsoft Azure](https://about.gitlab.com/2016/07/13/how-to-setup-a-gitlab-instance-on-microsoft-azure/) | Tutorial | 2016/07/13 | | [How to Setup a GitLab Instance on Microsoft Azure](https://about.gitlab.com/2016/07/13/how-to-setup-a-gitlab-instance-on-microsoft-azure/) | Tutorial | 2016/07/13 |
| [Get started with OpenShift Origin 3 and GitLab](https://about.gitlab.com/2016/06/28/get-started-with-openshift-origin-3-and-gitlab/) | Tutorial | 2016/06/28 | | [Get started with OpenShift Origin 3 and GitLab](openshift_and_gitlab/index.md) | Tutorial | 2016/06/28 |
| [Getting started with GitLab and DigitalOcean](https://about.gitlab.com/2016/04/27/getting-started-with-gitlab-and-digitalocean/) | Tutorial | 2016/04/27 | | [Getting started with GitLab and DigitalOcean](https://about.gitlab.com/2016/04/27/getting-started-with-gitlab-and-digitalocean/) | Tutorial | 2016/04/27 |
## Software development ## Software development
......
This diff is collapsed.
...@@ -168,8 +168,10 @@ are out of date, so we'll need to install through the following commands: ...@@ -168,8 +168,10 @@ are out of date, so we'll need to install through the following commands:
curl --location https://deb.nodesource.com/setup_7.x | sudo bash - curl --location https://deb.nodesource.com/setup_7.x | sudo bash -
sudo apt-get install -y nodejs sudo apt-get install -y nodejs
# install yarn curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
curl --location https://yarnpkg.com/install.sh | bash - echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update
sudo apt-get install yarn
Visit the official websites for [node](https://nodejs.org/en/download/package-manager/) and [yarn](https://yarnpkg.com/en/docs/install/) if you have any trouble with these steps. Visit the official websites for [node](https://nodejs.org/en/download/package-manager/) and [yarn](https://yarnpkg.com/en/docs/install/) if you have any trouble with these steps.
......
...@@ -65,7 +65,10 @@ Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage ...@@ -65,7 +65,10 @@ Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage
JavaScript dependencies. JavaScript dependencies.
```bash ```bash
curl --location https://yarnpkg.com/install.sh | bash - curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update
sudo apt-get install yarn
``` ```
More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install). More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install).
......
...@@ -65,7 +65,10 @@ Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage ...@@ -65,7 +65,10 @@ Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage
JavaScript dependencies. JavaScript dependencies.
```bash ```bash
curl --location https://yarnpkg.com/install.sh | bash - curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update
sudo apt-get install yarn
``` ```
More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install). More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install).
......
...@@ -65,7 +65,10 @@ Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage ...@@ -65,7 +65,10 @@ Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage
JavaScript dependencies. JavaScript dependencies.
```bash ```bash
curl --location https://yarnpkg.com/install.sh | bash - curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update
sudo apt-get install yarn
``` ```
More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install). More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install).
......
...@@ -65,7 +65,10 @@ Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage ...@@ -65,7 +65,10 @@ Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage
JavaScript dependencies. JavaScript dependencies.
```bash ```bash
curl --location https://yarnpkg.com/install.sh | bash - curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update
sudo apt-get install yarn
``` ```
More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install). More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install).
......
...@@ -65,7 +65,10 @@ Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage ...@@ -65,7 +65,10 @@ Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage
JavaScript dependencies. JavaScript dependencies.
```bash ```bash
curl --location https://yarnpkg.com/install.sh | bash - curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update
sudo apt-get install yarn
``` ```
More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install). More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install).
......
...@@ -48,7 +48,7 @@ module Gitlab ...@@ -48,7 +48,7 @@ module Gitlab
end end
def starting_month def starting_month
Date.today.month Date.current.month
end end
private private
...@@ -66,12 +66,18 @@ module Gitlab ...@@ -66,12 +66,18 @@ module Gitlab
.select(:id) .select(:id)
conditions = t[:created_at].gteq(date_from.beginning_of_day) conditions = t[:created_at].gteq(date_from.beginning_of_day)
.and(t[:created_at].lteq(Date.today.end_of_day)) .and(t[:created_at].lteq(Date.current.end_of_day))
.and(t[:author_id].eq(contributor.id)) .and(t[:author_id].eq(contributor.id))
date_interval = if Gitlab::Database.postgresql?
"INTERVAL '#{Time.zone.now.utc_offset} seconds'"
else
"INTERVAL #{Time.zone.now.utc_offset} SECOND"
end
Event.reorder(nil) Event.reorder(nil)
.select(t[:project_id], t[:target_type], t[:action], 'date(created_at) AS date', 'count(id) as total_amount') .select(t[:project_id], t[:target_type], t[:action], "date(created_at + #{date_interval}) AS date", 'count(id) as total_amount')
.group(t[:project_id], t[:target_type], t[:action], 'date(created_at)') .group(t[:project_id], t[:target_type], t[:action], "date(created_at + #{date_interval})")
.where(conditions) .where(conditions)
.having(t[:project_id].in(Arel::Nodes::SqlLiteral.new(authed_projects.to_sql))) .having(t[:project_id].in(Arel::Nodes::SqlLiteral.new(authed_projects.to_sql)))
end end
......
# Gitaly note: JV: needs 1 RPC for #load_blame.
module Gitlab module Gitlab
module Git module Git
class Blame class Blame
...@@ -26,15 +24,29 @@ module Gitlab ...@@ -26,15 +24,29 @@ module Gitlab
private private
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/376
def load_blame def load_blame
cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{@repo.path} blame -p #{@sha} -- #{@path}) raw_output = @repo.gitaly_migrate(:blame) do |is_enabled|
# Read in binary mode to ensure ASCII-8BIT if is_enabled
raw_output = IO.popen(cmd, 'rb') {|io| io.read } load_blame_by_gitaly
else
load_blame_by_shelling_out
end
end
output = encode_utf8(raw_output) output = encode_utf8(raw_output)
process_raw_blame output process_raw_blame output
end end
def load_blame_by_gitaly
@repo.gitaly_commit_client.raw_blame(@sha, @path)
end
def load_blame_by_shelling_out
cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{@repo.path} blame -p #{@sha} -- #{@path})
# Read in binary mode to ensure ASCII-8BIT
IO.popen(cmd, 'rb') {|io| io.read }
end
def process_raw_blame(output) def process_raw_blame(output)
lines, final = [], [] lines, final = [], []
info, commits = {}, {} info, commits = {}, {}
......
...@@ -683,6 +683,14 @@ module Gitlab ...@@ -683,6 +683,14 @@ module Gitlab
@gitaly_repository_client ||= Gitlab::GitalyClient::RepositoryService.new(self) @gitaly_repository_client ||= Gitlab::GitalyClient::RepositoryService.new(self)
end end
def gitaly_migrate(method, &block)
Gitlab::GitalyClient.migrate(method, &block)
rescue GRPC::NotFound => e
raise NoRepository.new(e)
rescue GRPC::BadStatus => e
raise CommandError.new(e)
end
private private
# Gitaly note: JV: Trying to get rid of the 'filter' option so we can implement this with 'git'. # Gitaly note: JV: Trying to get rid of the 'filter' option so we can implement this with 'git'.
...@@ -998,6 +1006,11 @@ module Gitlab ...@@ -998,6 +1006,11 @@ module Gitlab
end.sort_by(&:name) end.sort_by(&:name)
end end
def last_commit_for_path_by_rugged(sha, path)
sha = last_commit_id_for_path(sha, path)
commit(sha)
end
def tags_from_gitaly def tags_from_gitaly
gitaly_ref_client.tags gitaly_ref_client.tags
end end
...@@ -1017,14 +1030,6 @@ module Gitlab ...@@ -1017,14 +1030,6 @@ module Gitlab
raw_output.to_i raw_output.to_i
end end
def gitaly_migrate(method, &block)
Gitlab::GitalyClient.migrate(method, &block)
rescue GRPC::NotFound => e
raise NoRepository.new(e)
rescue GRPC::BadStatus => e
raise CommandError.new(e)
end
end end
end end
end end
...@@ -97,6 +97,20 @@ module Gitlab ...@@ -97,6 +97,20 @@ module Gitlab
GitalyClient.call(@repository.storage, :commit_service, :count_commits, request).count GitalyClient.call(@repository.storage, :commit_service, :count_commits, request).count
end end
def last_commit_for_path(revision, path)
request = Gitaly::LastCommitForPathRequest.new(
repository: @gitaly_repo,
revision: revision.force_encoding(Encoding::ASCII_8BIT),
path: path.to_s.force_encoding(Encoding::ASCII_8BIT)
)
gitaly_commit = GitalyClient.call(@repository.storage, :commit_service, :last_commit_for_path, request).commit
return unless gitaly_commit
commit = GitalyClient::Commit.new(@repository, gitaly_commit)
Gitlab::Git::Commit.new(commit)
end
def between(from, to) def between(from, to)
request = Gitaly::CommitsBetweenRequest.new( request = Gitaly::CommitsBetweenRequest.new(
repository: @gitaly_repo, repository: @gitaly_repo,
...@@ -128,6 +142,17 @@ module Gitlab ...@@ -128,6 +142,17 @@ module Gitlab
response.languages.map { |l| { value: l.share.round(2), label: l.name, color: l.color, highlight: l.color } } response.languages.map { |l| { value: l.share.round(2), label: l.name, color: l.color, highlight: l.color } }
end end
def raw_blame(revision, path)
request = Gitaly::RawBlameRequest.new(
repository: @gitaly_repo,
revision: revision,
path: path
)
response = GitalyClient.call(@repository.storage, :commit_service, :raw_blame, request)
response.reduce("") { |memo, msg| memo << msg.data }
end
private private
def commit_diff_request_params(commit, options = {}) def commit_diff_request_params(commit, options = {})
......
require 'spec_helper' require 'spec_helper'
feature 'Pipelines for Merge Requests', js: true do feature 'Pipelines for Merge Requests', js: true do
given(:user) { create(:user) } describe 'pipeline tab' do
given(:merge_request) { create(:merge_request) } given(:user) { create(:user) }
given(:project) { merge_request.target_project } given(:merge_request) { create(:merge_request) }
given(:project) { merge_request.target_project }
before do before do
project.team << [user, :master] project.team << [user, :master]
sign_in user sign_in user
end
context 'with pipelines' do
let!(:pipeline) do
create(:ci_empty_pipeline,
project: merge_request.source_project,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha)
end end
before do context 'with pipelines' do
visit project_merge_request_path(project, merge_request) let!(:pipeline) do
create(:ci_empty_pipeline,
project: merge_request.source_project,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha)
end
before do
visit project_merge_request_path(project, merge_request)
end
scenario 'user visits merge request pipelines tab' do
page.within('.merge-request-tabs') do
click_link('Pipelines')
end
wait_for_requests
expect(page).to have_selector('.stage-cell')
end
end end
scenario 'user visits merge request pipelines tab' do context 'without pipelines' do
page.within('.merge-request-tabs') do before do
click_link('Pipelines') visit project_merge_request_path(project, merge_request)
end end
wait_for_requests
expect(page).to have_selector('.stage-cell') scenario 'user visits merge request page' do
page.within('.merge-request-tabs') do
expect(page).to have_no_link('Pipelines')
end
end
end end
end end
context 'without pipelines' do describe 'race condition' do
before do given(:project) { create(:project, :repository) }
visit project_merge_request_path(project, merge_request) given(:user) { create(:user) }
given(:build_push_data) { { ref: 'feature', checkout_sha: TestEnv::BRANCH_SHA['feature'] } }
given(:merge_request_params) do
{ "source_branch" => "feature", "source_project_id" => project.id,
"target_branch" => "master", "target_project_id" => project.id, "title" => "A" }
end
background do
project.add_master(user)
sign_in user
end end
scenario 'user visits merge request page' do context 'when pipeline and merge request were created simultaneously' do
page.within('.merge-request-tabs') do background do
expect(page).to have_no_link('Pipelines') stub_ci_pipeline_to_return_yaml_file
threads = []
threads << Thread.new do
@merge_request = MergeRequests::CreateService.new(project, user, merge_request_params).execute
end
threads << Thread.new do
@pipeline = Ci::CreatePipelineService.new(project, user, build_push_data).execute(:push)
end
threads.each { |thr| thr.join }
end
scenario 'user sees pipeline in merge request widget' do
visit project_merge_request_path(project, @merge_request)
expect(page.find(".ci-widget")).to have_content(TestEnv::BRANCH_SHA['feature'])
expect(page.find(".ci-widget")).to have_content("##{@pipeline.id}")
end end
end end
end end
......
...@@ -219,6 +219,25 @@ feature 'Pipeline Schedules', :js do ...@@ -219,6 +219,25 @@ feature 'Pipeline Schedules', :js do
end end
end end
end end
context 'when active is true and next_run_at is NULL' do
background do
create(:ci_pipeline_schedule, project: project, owner: user).tap do |pipeline_schedule|
pipeline_schedule.update_attribute(:cron, nil) # Consequently next_run_at will be nil
end
end
scenario 'user edit and recover the problematic pipeline schedule' do
visit_pipelines_schedules
find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
fill_in 'schedule_cron', with: '* 1 2 3 4'
click_button 'Save pipeline schedule'
page.within('.pipeline-schedule-table-row:nth-child(1)') do
expect(page).to have_css(".next-run-cell time")
end
end
end
end end
context 'logged in as non-member' do context 'logged in as non-member' do
......
...@@ -22,12 +22,14 @@ describe Gitlab::ContributionsCalendar do ...@@ -22,12 +22,14 @@ describe Gitlab::ContributionsCalendar do
end end
end end
let(:today) { Time.now.to_date } let(:today) { Time.now.utc.to_date }
let(:yesterday) { today - 1.day }
let(:tomorrow) { today + 1.day }
let(:last_week) { today - 7.days } let(:last_week) { today - 7.days }
let(:last_year) { today - 1.year } let(:last_year) { today - 1.year }
before do before do
travel_to today travel_to Time.now.utc.end_of_day
end end
after do after do
...@@ -38,7 +40,7 @@ describe Gitlab::ContributionsCalendar do ...@@ -38,7 +40,7 @@ describe Gitlab::ContributionsCalendar do
described_class.new(contributor, current_user) described_class.new(contributor, current_user)
end end
def create_event(project, day) def create_event(project, day, hour = 0)
@targets ||= {} @targets ||= {}
@targets[project] ||= create(:issue, project: project, author: contributor) @targets[project] ||= create(:issue, project: project, author: contributor)
...@@ -47,7 +49,7 @@ describe Gitlab::ContributionsCalendar do ...@@ -47,7 +49,7 @@ describe Gitlab::ContributionsCalendar do
action: Event::CREATED, action: Event::CREATED,
target: @targets[project], target: @targets[project],
author: contributor, author: contributor,
created_at: day created_at: DateTime.new(day.year, day.month, day.day, hour)
) )
end end
...@@ -68,6 +70,34 @@ describe Gitlab::ContributionsCalendar do ...@@ -68,6 +70,34 @@ describe Gitlab::ContributionsCalendar do
expect(calendar(user).activity_dates[today]).to eq(0) expect(calendar(user).activity_dates[today]).to eq(0)
expect(calendar(contributor).activity_dates[today]).to eq(2) expect(calendar(contributor).activity_dates[today]).to eq(2)
end end
context "when events fall under different dates depending on the time zone" do
before do
create_event(public_project, today, 1)
create_event(public_project, today, 4)
create_event(public_project, today, 10)
create_event(public_project, today, 16)
create_event(public_project, today, 23)
end
it "renders correct event counts within the UTC timezone" do
Time.use_zone('UTC') do
expect(calendar.activity_dates).to eq(today => 5)
end
end
it "renders correct event counts within the Sydney timezone" do
Time.use_zone('Sydney') do
expect(calendar.activity_dates).to eq(today => 3, tomorrow => 2)
end
end
it "renders correct event counts within the US Central timezone" do
Time.use_zone('Central Time (US & Canada)') do
expect(calendar.activity_dates).to eq(yesterday => 2, today => 3)
end
end
end
end end
describe '#events_by_date' do describe '#events_by_date' do
......
...@@ -7,63 +7,73 @@ describe Gitlab::Git::Blame, seed_helper: true do ...@@ -7,63 +7,73 @@ describe Gitlab::Git::Blame, seed_helper: true do
Gitlab::Git::Blame.new(repository, SeedRepo::Commit::ID, "CONTRIBUTING.md") Gitlab::Git::Blame.new(repository, SeedRepo::Commit::ID, "CONTRIBUTING.md")
end end
context "each count" do shared_examples 'blaming a file' do
it do context "each count" do
data = [] it do
blame.each do |commit, line| data = []
data << { blame.each do |commit, line|
commit: commit, data << {
line: line commit: commit,
} line: line
end }
end
expect(data.size).to eq(95)
expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
expect(data.first[:line]).to eq("# Contribute to GitLab")
expect(data.first[:line]).to be_utf8
end
end
context "ISO-8859 encoding" do expect(data.size).to eq(95)
let(:blame) do expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
Gitlab::Git::Blame.new(repository, SeedRepo::EncodingCommit::ID, "encoding/iso8859.txt") expect(data.first[:line]).to eq("# Contribute to GitLab")
expect(data.first[:line]).to be_utf8
end
end end
it 'converts to UTF-8' do context "ISO-8859 encoding" do
data = [] let(:blame) do
blame.each do |commit, line| Gitlab::Git::Blame.new(repository, SeedRepo::EncodingCommit::ID, "encoding/iso8859.txt")
data << {
commit: commit,
line: line
}
end end
expect(data.size).to eq(1) it 'converts to UTF-8' do
expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit) data = []
expect(data.first[:line]).to eq("Ä ü") blame.each do |commit, line|
expect(data.first[:line]).to be_utf8 data << {
end commit: commit,
end line: line
}
end
context "unknown encoding" do expect(data.size).to eq(1)
let(:blame) do expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
Gitlab::Git::Blame.new(repository, SeedRepo::EncodingCommit::ID, "encoding/iso8859.txt") expect(data.first[:line]).to eq("Ä ü")
expect(data.first[:line]).to be_utf8
end
end end
it 'converts to UTF-8' do context "unknown encoding" do
expect(CharlockHolmes::EncodingDetector).to receive(:detect).and_return(nil) let(:blame) do
data = [] Gitlab::Git::Blame.new(repository, SeedRepo::EncodingCommit::ID, "encoding/iso8859.txt")
blame.each do |commit, line| end
data << {
it 'converts to UTF-8' do
expect(CharlockHolmes::EncodingDetector).to receive(:detect).and_return(nil)
data = []
blame.each do |commit, line|
data << {
commit: commit, commit: commit,
line: line line: line
} }
end end
expect(data.size).to eq(1) expect(data.size).to eq(1)
expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit) expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
expect(data.first[:line]).to eq(" ") expect(data.first[:line]).to eq(" ")
expect(data.first[:line]).to be_utf8 expect(data.first[:line]).to be_utf8
end
end end
end end
context 'when Gitaly blame feature is enabled' do
it_behaves_like 'blaming a file'
end
context 'when Gitaly blame feature is disabled', skip_gitaly_mock: true do
it_behaves_like 'blaming a file'
end
end end
...@@ -139,24 +139,44 @@ describe Repository do ...@@ -139,24 +139,44 @@ describe Repository do
end end
describe '#last_commit_for_path' do describe '#last_commit_for_path' do
subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id } shared_examples 'getting last commit for path' do
subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id }
it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') } it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') }
end
context 'when Gitaly feature last_commit_for_path is enabled' do
it_behaves_like 'getting last commit for path'
end
context 'when Gitaly feature last_commit_for_path is disabled', skip_gitaly_mock: true do
it_behaves_like 'getting last commit for path'
end
end end
describe '#last_commit_id_for_path' do describe '#last_commit_id_for_path' do
subject { repository.last_commit_id_for_path(sample_commit.id, '.gitignore') } shared_examples 'getting last commit ID for path' do
subject { repository.last_commit_id_for_path(sample_commit.id, '.gitignore') }
it "returns last commit id for a given path" do it "returns last commit id for a given path" do
is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8')
end
it "caches last commit id for a given path" do
cache = repository.send(:cache)
key = "last_commit_id_for_path:#{sample_commit.id}:#{Digest::SHA1.hexdigest('.gitignore')}"
expect(cache).to receive(:fetch).with(key).and_return('c1acaa5')
is_expected.to eq('c1acaa5')
end
end end
it "caches last commit id for a given path" do context 'when Gitaly feature last_commit_for_path is enabled' do
cache = repository.send(:cache) it_behaves_like 'getting last commit ID for path'
key = "last_commit_id_for_path:#{sample_commit.id}:#{Digest::SHA1.hexdigest('.gitignore')}" end
expect(cache).to receive(:fetch).with(key).and_return('c1acaa5') context 'when Gitaly feature last_commit_for_path is disabled', skip_gitaly_mock: true do
is_expected.to eq('c1acaa5') it_behaves_like 'getting last commit ID for path'
end end
end end
......
...@@ -294,6 +294,26 @@ describe API::MergeRequests do ...@@ -294,6 +294,26 @@ describe API::MergeRequests do
expect(json_response.length).to eq(0) expect(json_response.length).to eq(0)
end end
it 'returns an array of labeled merge requests that are merged for a milestone' do
bug_label = create(:label, title: 'bug', color: '#FFAABB', project: project)
mr1 = create(:merge_request, state: "merged", source_project: project, target_project: project, milestone: milestone)
mr2 = create(:merge_request, state: "merged", source_project: project, target_project: project, milestone: milestone1)
mr3 = create(:merge_request, state: "closed", source_project: project, target_project: project, milestone: milestone1)
_mr = create(:merge_request, state: "merged", source_project: project, target_project: project, milestone: milestone1)
create(:label_link, label: bug_label, target: mr1)
create(:label_link, label: bug_label, target: mr2)
create(:label_link, label: bug_label, target: mr3)
get api("/projects/#{project.id}/merge_requests?labels=#{bug_label.title}&milestone=#{milestone1.title}&state=merged", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(mr2.id)
end
context "with ordering" do context "with ordering" do
before do before do
@mr_later = mr_with_later_created_and_updated_at_time @mr_later = mr_with_later_created_and_updated_at_time
......
require_relative 'devise_helpers'
module LoginHelpers module LoginHelpers
include DeviseHelpers include DeviseHelpers
......
...@@ -231,7 +231,6 @@ module TestEnv ...@@ -231,7 +231,6 @@ module TestEnv
# Otherwise they'd be created by the first test, often timing out and # Otherwise they'd be created by the first test, often timing out and
# causing a transient test failure # causing a transient test failure
def eager_load_driver_server def eager_load_driver_server
return unless ENV['CI']
return unless defined?(Capybara) return unless defined?(Capybara)
puts "Starting the Capybara driver server..." puts "Starting the Capybara driver server..."
......
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