Commit 9763cc76 authored by Rémy Coutable's avatar Rémy Coutable

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

CE upstream - 2018-05-02 18:27 UTC

See merge request gitlab-org/gitlab-ee!5551
parents 20e12599 bc5cc956
export default {
name: 'time-tracking-no-tracking-pane',
template: `
<div class="time-tracking-no-tracking-pane">
<span class="no-value">
{{ __('No estimate or time spent') }}
</span>
</div>
`,
};
<script>
export default {
name: 'TimeTrackingNoTrackingPane',
};
</script>
<template>
<div class="time-tracking-no-tracking-pane">
<span class="no-value">
{{ __('No estimate or time spent') }}
</span>
</div>
</template>
<script>
import $ from 'jquery';
import _ from 'underscore';
......@@ -10,14 +11,17 @@ import Mediator from '../../sidebar_mediator';
import eventHub from '../../event_hub';
export default {
components: {
IssuableTimeTracker,
},
data() {
return {
mediator: new Mediator(),
store: new Store(),
};
},
components: {
IssuableTimeTracker,
mounted() {
this.listenForQuickActions();
},
methods: {
listenForQuickActions() {
......@@ -41,18 +45,17 @@ export default {
}
},
},
mounted() {
this.listenForQuickActions();
},
template: `
<div class="block">
<issuable-time-tracker
:time_estimate="store.timeEstimate"
:time_spent="store.totalTimeSpent"
:human_time_estimate="store.humanTimeEstimate"
:human_time_spent="store.humanTotalTimeSpent"
:rootPath="store.rootPath"
/>
</div>
`,
};
</script>
<template>
<div class="block">
<issuable-time-tracker
:time_estimate="store.timeEstimate"
:time_spent="store.totalTimeSpent"
:human_time_estimate="store.humanTimeEstimate"
:human_time_spent="store.humanTotalTimeSpent"
:root-path="store.rootPath"
/>
</div>
</template>
......@@ -2,7 +2,7 @@
import TimeTrackingHelpState from './help_state.vue';
import TimeTrackingCollapsedState from './collapsed_state.vue';
import timeTrackingSpentOnlyPane from './spent_only_pane';
import timeTrackingNoTrackingPane from './no_tracking_pane';
import TimeTrackingNoTrackingPane from './no_tracking_pane.vue';
import TimeTrackingEstimateOnlyPane from './estimate_only_pane.vue';
import TimeTrackingComparisonPane from './comparison_pane.vue';
......@@ -14,7 +14,7 @@ export default {
TimeTrackingCollapsedState,
TimeTrackingEstimateOnlyPane,
'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane,
'time-tracking-no-tracking-pane': timeTrackingNoTrackingPane,
TimeTrackingNoTrackingPane,
TimeTrackingComparisonPane,
TimeTrackingHelpState,
},
......
import $ from 'jquery';
import Vue from 'vue';
import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking';
import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking.vue';
import SidebarAssignees from './components/assignees/sidebar_assignees.vue';
import ConfidentialIssueSidebar from './components/confidential/confidential_issue_sidebar.vue';
import SidebarMoveIssue from './lib/sidebar_move_issue';
......
<script>
import $ from 'jquery';
import statusIcon from '../mr_widget_status_icon.vue';
import tooltip from '../../../vue_shared/directives/tooltip';
import eventHub from '../../event_hub';
export default {
name: 'MRWidgetWIP',
props: {
mr: { type: Object, required: true },
service: { type: Object, required: true },
name: 'WorkInProgress',
components: {
statusIcon,
},
directives: {
tooltip,
},
props: {
mr: { type: Object, required: true },
service: { type: Object, required: true },
},
data() {
return {
isMakingRequest: false,
};
},
components: {
statusIcon,
},
methods: {
removeWIP() {
this.isMakingRequest = true;
......@@ -36,32 +37,40 @@ export default {
});
},
},
template: `
<div class="mr-widget-body media">
<status-icon status="warning" :show-disabled-button="Boolean(mr.removeWIPPath)" />
<div class="media-body space-children">
<span class="bold">
This is a Work in Progress
<i
v-tooltip
class="fa fa-question-circle"
title="When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
aria-label="When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged">
</i>
</span>
<button
v-if="mr.removeWIPPath"
@click="removeWIP"
:disabled="isMakingRequest"
type="button"
class="btn btn-default btn-xs js-remove-wip">
<i
v-if="isMakingRequest"
class="fa fa-spinner fa-spin"
aria-hidden="true" />
Resolve WIP status
</button>
</div>
</div>
`,
};
</script>
<template>
<div class="mr-widget-body media">
<status-icon
status="warning"
:show-disabled-button="Boolean(mr.removeWIPPath)"
/>
<div class="media-body space-children">
<span class="bold">
This is a Work in Progress
<i
v-tooltip
class="fa fa-question-circle"
title="When this merge request is ready,
remove the WIP: prefix from the title to allow it to be merged"
aria-label="When this merge request is ready,
remove the WIP: prefix from the title to allow it to be merged">
</i>
</span>
<button
v-if="mr.removeWIPPath"
@click="removeWIP"
:disabled="isMakingRequest"
type="button"
class="btn btn-default btn-xs js-remove-wip">
<i
v-if="isMakingRequest"
class="fa fa-spinner fa-spin"
aria-hidden="true">
</i>
Resolve WIP status
</button>
</div>
</div>
</template>
......@@ -21,7 +21,7 @@ export { default as MergedState } from './components/states/mr_widget_merged.vue
export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge.vue';
export { default as ClosedState } from './components/states/mr_widget_closed.vue';
export { default as MergingState } from './components/states/mr_widget_merging.vue';
export { default as WipState } from './components/states/mr_widget_wip';
export { default as WorkInProgressState } from './components/states/work_in_progress.vue';
export { default as ArchivedState } from './components/states/mr_widget_archived.vue';
export { default as ConflictsState } from './components/states/mr_widget_conflicts.vue';
export { default as NothingToMergeState } from './components/states/nothing_to_merge.vue';
......
......@@ -12,7 +12,7 @@ import {
ClosedState,
MergingState,
RebaseState,
WipState,
WorkInProgressState,
ArchivedState,
ConflictsState,
NothingToMergeState,
......@@ -221,7 +221,7 @@ export default {
'mr-widget-closed': ClosedState,
'mr-widget-merging': MergingState,
'mr-widget-failed-to-merge': FailedToMerge,
'mr-widget-wip': WipState,
'mr-widget-wip': WorkInProgressState,
'mr-widget-archived': ArchivedState,
'mr-widget-conflicts': ConflictsState,
'mr-widget-nothing-to-merge': NothingToMergeState,
......
......@@ -3,6 +3,7 @@
@import 'framework/tw_bootstrap_variables';
@import 'framework/tw_bootstrap';
@import 'framework/layout';
@import 'framework/animations';
@import 'framework/vue_transitions';
@import 'framework/avatar';
......
......@@ -46,7 +46,7 @@
}
&.middle-block {
margin-top: 0;
margin-top: $gl-padding-24;
margin-bottom: 0;
}
......@@ -61,7 +61,7 @@
}
&.footer-block {
margin-top: 0;
margin-top: $gl-padding-24;
border-bottom: 0;
margin-bottom: -$gl-padding;
}
......
......@@ -107,6 +107,16 @@
padding-top: 10px;
}
.referenced-commands {
background: $blue-50;
padding: $gl-padding-8 $gl-padding;
border-radius: $border-radius-default;
p {
margin: 0;
}
}
.md-preview-holder {
min-height: 167px;
padding: 10px 0;
......
......@@ -213,6 +213,7 @@ $tooltip-font-size: 12px;
/*
* Padding
*/
$gl-padding-24: 24px;
$gl-padding: 16px;
$gl-padding-8: 8px;
$gl-padding-4: 4px;
......
......@@ -77,21 +77,30 @@ module MergeRequests
end
def commit
message = params[:commit_message] || merge_request.merge_commit_message
log_info("Git merge started on JID #{merge_jid}")
commit_id = repository.merge(current_user, source, merge_request, message)
log_info("Git merge finished on JID #{merge_jid} commit #{commit_id}")
commit_id = try_merge
if commit_id
log_info("Git merge finished on JID #{merge_jid} commit #{commit_id}")
else
raise MergeError, 'Conflicts detected during merge'
end
raise MergeError, 'Conflicts detected during merge' unless commit_id
merge_request.update!(merge_commit_sha: commit_id)
end
def try_merge
message = params[:commit_message] || merge_request.merge_commit_message
merge_request.update(merge_commit_sha: commit_id)
repository.merge(current_user, source, merge_request, message)
rescue Gitlab::Git::HooksService::PreReceiveError => e
raise MergeError, e.message
rescue StandardError => e
raise MergeError, "Something went wrong during merge: #{e.message}"
handle_merge_error(log_message: e.message)
raise MergeError, 'Something went wrong during merge pre-receive hook'
rescue => e
handle_merge_error(log_message: e.message)
raise MergeError, 'Something went wrong during merge'
ensure
merge_request.update(in_progress_merge_commit_sha: nil)
merge_request.update!(in_progress_merge_commit_sha: nil)
end
def after_merge
......
......@@ -13,7 +13,7 @@
.panel
.panel-heading.alert.alert-danger
Last repository check
= "(#{time_ago_in_words(@project.last_repository_check_at)} ago)"
= "(#{time_ago_with_tooltip(@project.last_repository_check_at)})"
failed. See
= link_to 'repocheck.log', admin_logs_path
for error messages.
......
......@@ -31,7 +31,7 @@
= tag
%td
- if runner.contacted_at
#{time_ago_in_words(runner.contacted_at)} ago
= time_ago_with_tooltip runner.contacted_at
- else
Never
%td.admin-runner-btn-group-cell
......
......@@ -108,4 +108,4 @@
%td.timestamp
- if build.finished_at
%span #{time_ago_in_words build.finished_at} ago
%span= time_ago_with_tooltip build.finished_at
......@@ -20,5 +20,4 @@
%td
= service.description
%td.light
= time_ago_in_words service.updated_at
ago
= time_ago_with_tooltip service.updated_at
......@@ -2,7 +2,7 @@
= email_default_heading("Hello, #{@resource.name}!")
%p
Your GitLab account has been locked due to an excessive amount of unsuccessful
sign in attempts. Your account will automatically unlock in #{time_ago_in_words(Devise.unlock_in.from_now)}
sign in attempts. Your account will automatically unlock in #{distance_of_time_in_words(Devise.unlock_in)}
or you may click the link below to unlock now.
#cta
= link_to('Unlock account', unlock_url(@resource, unlock_token: @token))
Hello, <%= @resource.name %>!
Your GitLab account has been locked due to an excessive amount of unsuccessful
sign in attempts. Your account will automatically unlock in <%= time_ago_in_words(Devise.unlock_in.from_now) %>
sign in attempts. Your account will automatically unlock in <%= distance_of_time_in_words(Devise.unlock_in) %>
or you may click the link below to unlock now.
<%= unlock_url(@resource, unlock_token: @token) %>
......@@ -15,7 +15,7 @@
- elsif @build.has_expiring_artifacts?
%p.build-detail-row
The artifacts will be removed in
%span= time_ago_in_words @build.artifacts_expire_at
%span= time_ago_with_tooltip @build.artifacts_expire_at
- if @build.artifacts?
.btn-group.btn-group-justified{ role: :group }
......
......@@ -18,7 +18,7 @@
\-
%td
- if tag.created_at
= time_ago_in_words(tag.created_at)
= time_ago_with_tooltip tag.created_at
- else
.light
\-
......
......@@ -62,6 +62,6 @@
%td Last contact
%td
- if @runner.contacted_at
#{time_ago_in_words(@runner.contacted_at)} ago
= time_ago_with_tooltip @runner.contacted_at
- else
Never
......@@ -27,5 +27,4 @@
= service.description
%td.light
- if service.updated_at.present?
= time_ago_in_words service.updated_at
ago
= time_ago_with_tooltip service.updated_at
......@@ -25,7 +25,7 @@
%td
- if trigger.last_used
#{time_ago_in_words(trigger.last_used)} ago
= time_ago_with_tooltip trigger.last_used
- else
Never
......
......@@ -35,5 +35,4 @@
%span.light
#{t('sherlock.finished_at')}:
%strong
= time_ago_in_words(@transaction.finished_at)
= t('sherlock.ago')
= time_ago_with_tooltip @transaction.finished_at
......@@ -35,8 +35,7 @@
= t('sherlock.seconds')
%td= trans.queries.length
%td
= time_ago_in_words(trans.finished_at)
= t('sherlock.ago')
= time_ago_with_tooltip trans.finished_at
%td
= link_to(sherlock_transaction_path(trans), class: 'btn btn-xs') do
= t('sherlock.view')
---
title: Replace time_ago_in_words with JS-based one
merge_request: 18607
author: Takuya Noguchi
type: performance
---
title: Improve quick actions summary preview
merge_request: 18659
author: George Tsiolis
type: changed
---
title: Increase new issue metadata form margin
merge_request: 18630
author: George Tsiolis
type: fixed
---
title: Display only generic message on merge error to avoid exposing any potentially
sensitive or user unfriendly backend messages.
merge_request:
author:
type: fixed
---
title: Move WorkInProgress vue component
merge_request: 17536
author: George Tsiolis
type: performance
---
title: Move TimeTrackingNoTrackingPane vue component
merge_request: 18676
author: George Tsiolis
type: performance
---
title: Move SidebarTimeTracking vue component
merge_request: 18677
author: George Tsiolis
type: performance
---
title: Finish NamespaceService migration to Gitaly
merge_request:
author:
type: performance
require 'spec_helper'
describe Projects::UpdateRepositoryStorageService do
include StubConfiguration
include Gitlab::ShellAdapter
subject { described_class.new(project) }
......@@ -9,41 +9,23 @@ describe Projects::UpdateRepositoryStorageService do
let(:time) { Time.now }
before do
FileUtils.mkdir('tmp/tests/storage_a')
FileUtils.mkdir('tmp/tests/storage_b')
storages = {
'a' => { 'path' => 'tmp/tests/storage_a' },
'b' => { 'path' => 'tmp/tests/storage_b' }
}
stub_storage_settings(storages)
allow(Time).to receive(:now).and_return(time)
# This will force the creation of the repository and bypass Gitaly
allow_any_instance_of(Gitlab::Git::Repository).to receive(:exists?).and_return(false)
end
after do
FileUtils.rm_rf('tmp/tests/storage_a')
FileUtils.rm_rf('tmp/tests/storage_b')
end
context 'without wiki', :disable_gitaly do
let(:project) { create(:project, :repository, repository_storage: 'a', repository_read_only: true, wiki_enabled: false) }
context 'without wiki' do
let(:project) { create(:project, :repository, repository_read_only: true, wiki_enabled: false) }
context 'when the move succeeds' do
it 'moves the repository to the new storage and unmarks the repository as read only' do
old_path = project.repository.path_to_repo
expect_any_instance_of(Gitlab::Git::Repository).to receive(:fetch_repository_as_mirror)
.with(project.repository.raw).and_return(true)
expect(GitlabShellWorker).to receive(:perform_async)
.with(:mv_repository, 'a', project.disk_path,
"#{project.disk_path}+#{project.id}+moved+#{time.to_i}")
subject.execute('b')
subject.execute('test_second_storage')
expect(project).not_to be_repository_read_only
expect(project.repository_storage).to eq('b')
expect(project.repository_storage).to eq('test_second_storage')
expect(gitlab_shell.exists?('default', old_path)).to be(false)
end
end
......@@ -53,56 +35,53 @@ describe Projects::UpdateRepositoryStorageService do
.with(project.repository.raw).and_return(false)
expect(GitlabShellWorker).not_to receive(:perform_async)
subject.execute('b')
subject.execute('test_second_storage')
expect(project).not_to be_repository_read_only
expect(project.repository_storage).to eq('a')
expect(project.repository_storage).to eq('default')
end
end
end
context 'with wiki', :disable_gitaly do
let(:project) { create(:project, :repository, repository_storage: 'a', repository_read_only: true, wiki_enabled: true) }
let(:project) { create(:project, :repository, repository_read_only: true, wiki_enabled: true) }
let(:repository_double) { double(:repository) }
let(:wiki_repository_double) { double(:repository) }
before do
allow_any_instance_of(Gitlab::Git::Repository).to receive(:exists?).and_return(false)
project.create_wiki
allow_any_instance_of(Gitlab::Git::Repository).to receive(:exists?).and_return(true)
# Default stub for non-specified params
allow(Gitlab::Git::Repository).to receive(:new).and_call_original
relative_path = project.repository.raw.relative_path
allow(Gitlab::Git::Repository).to receive(:new)
.with('b', relative_path, "project-#{project.id}")
.with('test_second_storage', relative_path, "project-#{project.id}")
.and_return(repository_double)
wiki_relative_path = project.wiki.repository.raw.relative_path
allow(Gitlab::Git::Repository).to receive(:new)
.with('b', wiki_relative_path, "wiki-#{project.id}")
.with('test_second_storage', wiki_relative_path, "wiki-#{project.id}")
.and_return(wiki_repository_double)
end
context 'when the move succeeds' do
it 'moves the repository and its wiki to the new storage and unmarks the repository as read only' do
old_path = project.repository.path_to_repo
old_wiki_path = project.wiki.full_path
expect(repository_double).to receive(:fetch_repository_as_mirror)
.with(project.repository.raw).and_return(true)
expect(GitlabShellWorker).to receive(:perform_async)
.with(:mv_repository, "a", project.disk_path,
"#{project.disk_path}+#{project.id}+moved+#{time.to_i}")
expect(wiki_repository_double).to receive(:fetch_repository_as_mirror)
.with(project.wiki.repository.raw).and_return(true)
expect(GitlabShellWorker).to receive(:perform_async)
.with(:mv_repository, "a", project.wiki.disk_path,
"#{project.disk_path}+#{project.id}+moved+#{time.to_i}.wiki")
subject.execute('b')
subject.execute('test_second_storage')
expect(project).not_to be_repository_read_only
expect(project.repository_storage).to eq('b')
expect(project.repository_storage).to eq('test_second_storage')
expect(gitlab_shell.exists?('default', old_path)).to be(false)
expect(gitlab_shell.exists?('default', old_wiki_path)).to be(false)
end
end
......@@ -114,10 +93,10 @@ describe Projects::UpdateRepositoryStorageService do
.with(project.wiki.repository.raw).and_return(false)
expect(GitlabShellWorker).not_to receive(:perform_async)
subject.execute('b')
subject.execute('test_second_storage')
expect(project).not_to be_repository_read_only
expect(project.repository_storage).to eq('a')
expect(project.repository_storage).to eq('default')
end
end
end
......
......@@ -294,17 +294,7 @@ module Gitlab
# add_namespace("default", "gitlab")
#
def add_namespace(storage, name)
Gitlab::GitalyClient.migrate(:add_namespace,
status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |enabled|
if enabled
Gitlab::GitalyClient::NamespaceService.new(storage).add(name)
else
path = full_path(storage, name)
FileUtils.mkdir_p(path, mode: 0770) unless exists?(storage, name)
end
end
rescue Errno::EEXIST => e
Rails.logger.warn("Directory exists as a file: #{e} at: #{path}")
Gitlab::GitalyClient::NamespaceService.new(storage).add(name)
rescue GRPC::InvalidArgument => e
raise ArgumentError, e.message
end
......@@ -316,14 +306,7 @@ module Gitlab
# rm_namespace("default", "gitlab")
#
def rm_namespace(storage, name)
Gitlab::GitalyClient.migrate(:remove_namespace,
status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |enabled|
if enabled
Gitlab::GitalyClient::NamespaceService.new(storage).remove(name)
else
FileUtils.rm_r(full_path(storage, name), force: true)
end
end
Gitlab::GitalyClient::NamespaceService.new(storage).remove(name)
rescue GRPC::InvalidArgument => e
raise ArgumentError, e.message
end
......@@ -335,17 +318,7 @@ module Gitlab
# mv_namespace("/path/to/storage", "gitlab", "gitlabhq")
#
def mv_namespace(storage, old_name, new_name)
Gitlab::GitalyClient.migrate(:rename_namespace,
status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |enabled|
if enabled
Gitlab::GitalyClient::NamespaceService.new(storage)
.rename(old_name, new_name)
else
break false if exists?(storage, new_name) || !exists?(storage, old_name)
FileUtils.mv(full_path(storage, old_name), full_path(storage, new_name))
end
end
Gitlab::GitalyClient::NamespaceService.new(storage).rename(old_name, new_name)
rescue GRPC::InvalidArgument
false
end
......@@ -370,17 +343,8 @@ module Gitlab
# exists?(storage, 'gitlab')
# exists?(storage, 'gitlab/cookies.git')
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/385
def exists?(storage, dir_name)
Gitlab::GitalyClient.migrate(:namespace_exists,
status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |enabled|
if enabled
Gitlab::GitalyClient::NamespaceService.new(storage)
.exists?(dir_name)
else
File.exist?(full_path(storage, dir_name))
end
end
Gitlab::GitalyClient::NamespaceService.new(storage).exists?(dir_name)
end
protected
......
......@@ -19,7 +19,7 @@ feature 'Admin uses repository checks' do
expect(page).to have_content('Repository check was triggered')
end
scenario 'to see a single failed repository check' do
scenario 'to see a single failed repository check', :js do
project = create(:project)
project.update_columns(
last_repository_check_failed: true,
......
......@@ -252,9 +252,10 @@ feature 'Issues > User uses quick actions', :js do
end
context 'when the project is valid but the user not authorized' do
let(:project_unauthorized) {create(:project, :public)}
let(:project_unauthorized) { create(:project, :public) }
before do
gitlab_sign_out
sign_in(user)
visit project_issue_path(project, issue)
end
......@@ -269,6 +270,7 @@ feature 'Issues > User uses quick actions', :js do
context 'when the project is invalid' do
before do
gitlab_sign_out
sign_in(user)
visit project_issue_path(project, issue)
end
......
import Vue from 'vue';
import wipComponent from '~/vue_merge_request_widget/components/states/mr_widget_wip';
import WorkInProgress from '~/vue_merge_request_widget/components/states/work_in_progress.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
const createComponent = () => {
const Component = Vue.extend(wipComponent);
const Component = Vue.extend(WorkInProgress);
const mr = {
title: 'The best MR ever',
removeWIPPath: '/path/to/remove/wip',
......@@ -17,10 +17,10 @@ const createComponent = () => {
});
};
describe('MRWidgetWIP', () => {
describe('Wip', () => {
describe('props', () => {
it('should have props', () => {
const { mr, service } = wipComponent.props;
const { mr, service } = WorkInProgress.props;
expect(mr.type instanceof Object).toBeTruthy();
expect(mr.required).toBeTruthy();
......
......@@ -219,7 +219,7 @@ describe MergeRequests::MergeService do
service.execute(merge_request)
expect(merge_request.merge_error).to include(error_message)
expect(merge_request.merge_error).to include('Something went wrong during merge')
expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message))
end
......@@ -231,7 +231,7 @@ describe MergeRequests::MergeService do
service.execute(merge_request)
expect(merge_request.merge_error).to include(error_message)
expect(merge_request.merge_error).to include('Something went wrong during merge pre-receive hook')
expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message))
end
......
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