Commit 0f953ac4 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch '10-8-stable-prepare-rc8' into '10-8-stable'

Prepare 10.8 RC8 release

See merge request gitlab-org/gitlab-ce!18883
parents d66ae2d5 abd24d1c
Pipeline #21886833 passed with stages
in 56 minutes and 43 seconds
......@@ -110,8 +110,8 @@ export default {
Welcome to the GitLab IDE
</h4>
<p>
You can select a file in the left sidebar to begin
editing and use the right sidebar to commit your changes.
Select a file from the left sidebar to begin editing.
Afterwards, you'll be able to commit your changes.
</p>
</div>
</div>
......
......@@ -140,7 +140,7 @@ export default {
this.file.staged && this.file.key.indexOf('unstaged-') === 0 ? head : null,
);
if (this.viewer === viewerTypes.mr) {
if (this.viewer === viewerTypes.mr && this.file.mrChange) {
this.editor.attachMergeRequestModel(this.model);
} else {
this.editor.attachModel(this.model);
......
......@@ -44,6 +44,7 @@ export const dataStructure = () => ({
size: 0,
parentPath: null,
lastOpenedAt: 0,
mrChange: null,
});
export const decorateData = entity => {
......
......@@ -17,6 +17,7 @@
display: flex;
align-items: center;
justify-content: space-between;
line-height: $line-height-base;
.title {
display: flex;
......@@ -33,10 +34,14 @@
.navbar-collapse {
padding-right: 0;
.navbar-nav {
margin: 0;
}
}
.nav li a {
color: $theme-gray-700;
.nav li {
float: none;
}
}
......
......@@ -3,6 +3,10 @@ module Users
include InternalRedirect
skip_before_action :enforce_terms!
skip_before_action :check_password_expiration
skip_before_action :check_two_factor_requirement
skip_before_action :require_email
before_action :terms
layout 'terms'
......
......@@ -107,7 +107,13 @@ module Ci
end
def assign_to(project, current_user = nil)
self.is_shared = false if shared?
if shared?
self.is_shared = false if shared?
self.runner_type = :project_type
elsif group_type?
raise ArgumentError, 'Transitioning a group runner to a project runner is not supported'
end
self.save
project.runner_projects.create(runner_id: self.id)
end
......
......@@ -22,7 +22,8 @@ module ShaAttribute
column = columns.find { |c| c.name == name.to_s }
unless column
raise ArgumentError.new("sha_attribute #{name.inspect} is invalid since the column doesn't exist")
warn "WARNING: sha_attribute #{name.inspect} is invalid since the column doesn't exist - you may need to run database migrations"
return
end
unless column.type == :binary
......
......@@ -20,10 +20,10 @@
= brand_header_logo
- logo_text = brand_header_logo_type
- if logo_text.present?
%span.logo-text.hidden-xs.prepend-left-8
%span.logo-text.prepend-left-8
= logo_text
- if header_link?(:user_dropdown)
.navbar-collapse.collapse
.navbar-collapse
%ul.nav.navbar-nav
%li.header-user.dropdown
= link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
......
......@@ -9,85 +9,6 @@ module ObjectStorage
SanityCheckError = Class.new(StandardError)
class Upload < ActiveRecord::Base
# Upper limit for foreground checksum processing
CHECKSUM_THRESHOLD = 100.megabytes
belongs_to :model, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
validates :size, presence: true
validates :path, presence: true
validates :model, presence: true
validates :uploader, presence: true
before_save :calculate_checksum!, if: :foreground_checksummable?
after_commit :schedule_checksum, if: :checksummable?
scope :stored_locally, -> { where(store: [nil, ObjectStorage::Store::LOCAL]) }
scope :stored_remotely, -> { where(store: ObjectStorage::Store::REMOTE) }
def self.hexdigest(path)
Digest::SHA256.file(path).hexdigest
end
def absolute_path
raise ObjectStorage::RemoteStoreError, "Remote object has no absolute path." unless local?
return path unless relative_path?
uploader_class.absolute_path(self)
end
def calculate_checksum!
self.checksum = nil
return unless checksummable?
self.checksum = self.class.hexdigest(absolute_path)
end
def build_uploader(mounted_as = nil)
uploader_class.new(model, mounted_as).tap do |uploader|
uploader.upload = self
uploader.retrieve_from_store!(identifier)
end
end
def exist?
File.exist?(absolute_path)
end
def local?
return true if store.nil?
store == ObjectStorage::Store::LOCAL
end
private
def checksummable?
checksum.nil? && local? && exist?
end
def foreground_checksummable?
checksummable? && size <= CHECKSUM_THRESHOLD
end
def schedule_checksum
UploadChecksumWorker.perform_async(id)
end
def relative_path?
!path.start_with?('/')
end
def identifier
File.basename(path)
end
def uploader_class
Object.const_get(uploader)
end
end
class MigrationResult
attr_reader :upload
attr_accessor :error
......
---
title: 46210 Display logo and user dropdown on mobile for terms page and fix styling
merge_request:
author:
type: fixed
---
title: Fixes database inconsistencies between Community and Enterprise Edition on
import state
merge_request: 18811
author:
type: fixed
---
title: Fix finding wiki pages when they have invalidly-encoded content
merge_request: 18856
author:
type: fixed
---
title: Fix outdated Web IDE welcome copy
merge_request: 18861
author:
type: fixed
......@@ -7,6 +7,20 @@ module Gollum
end
require "gollum-lib"
module Gollum
class Page
def text_data(encoding = nil)
data = if raw_data.respond_to?(:encoding)
raw_data.force_encoding(encoding || Encoding::UTF_8)
else
raw_data
end
Gitlab::EncodingHelper.encode!(data)
end
end
end
Rails.application.configure do
config.after_initialize do
Gollum::Page.per_page = Kaminari.config.default_per_page
......
class AddNotNullConstraintToProjectMirrorDataForeignKey < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
class ProjectImportState < ActiveRecord::Base
include EachBatch
self.table_name = 'project_mirror_data'
end
def up
ProjectImportState.where(project_id: nil).delete_all
change_column_null :project_mirror_data, :project_id, false
end
def down
change_column_null :project_mirror_data, :project_id, true
end
end
class AddUniqueConstraintToProjectMirrorDataProjectIdIndex < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index(:project_mirror_data,
:project_id,
unique: true,
name: 'index_project_mirror_data_on_project_id_unique')
remove_concurrent_index_by_name(:project_mirror_data, 'index_project_mirror_data_on_project_id')
rename_index(:project_mirror_data,
'index_project_mirror_data_on_project_id_unique',
'index_project_mirror_data_on_project_id')
end
def down
rename_index(:project_mirror_data,
'index_project_mirror_data_on_project_id',
'index_project_mirror_data_on_project_id_old')
add_concurrent_index(:project_mirror_data, :project_id)
remove_concurrent_index_by_name(:project_mirror_data,
'index_project_mirror_data_on_project_id_old')
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180508055821) do
ActiveRecord::Schema.define(version: 20180508102840) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -1529,14 +1529,14 @@ ActiveRecord::Schema.define(version: 20180508055821) do
add_index "project_import_data", ["project_id"], name: "index_project_import_data_on_project_id", using: :btree
create_table "project_mirror_data", force: :cascade do |t|
t.integer "project_id"
t.integer "project_id", null: false
t.string "status"
t.string "jid"
t.text "last_error"
end
add_index "project_mirror_data", ["jid"], name: "index_project_mirror_data_on_jid", using: :btree
add_index "project_mirror_data", ["project_id"], name: "index_project_mirror_data_on_project_id", using: :btree
add_index "project_mirror_data", ["project_id"], name: "index_project_mirror_data_on_project_id", unique: true, using: :btree
add_index "project_mirror_data", ["status"], name: "index_project_mirror_data_on_status", using: :btree
create_table "project_statistics", force: :cascade do |t|
......
......@@ -437,5 +437,107 @@ feature 'Login' do
expect(current_path).to eq(root_path)
end
context 'when 2FA is required for the user' do
before do
group = create(:group, require_two_factor_authentication: true)
group.add_developer(user)
end
context 'when the user did not enable 2FA' do
it 'asks to set 2FA before asking to accept the terms' do
visit new_user_session_path
fill_in 'user_login', with: user.email
fill_in 'user_password', with: '12345678'
click_button 'Sign in'
expect_to_be_on_terms_page
click_button 'Accept terms'
expect(current_path).to eq(profile_two_factor_auth_path)
fill_in 'pin_code', with: user.reload.current_otp
click_button 'Register with two-factor app'
click_link 'Proceed'
expect(current_path).to eq(profile_account_path)
end
end
context 'when the user already enabled 2FA' do
before do
user.update!(otp_required_for_login: true,
otp_secret: User.generate_otp_secret(32))
end
it 'asks the user to accept the terms' do
visit new_user_session_path
fill_in 'user_login', with: user.email
fill_in 'user_password', with: '12345678'
click_button 'Sign in'
fill_in 'user_otp_attempt', with: user.reload.current_otp
click_button 'Verify code'
expect_to_be_on_terms_page
click_button 'Accept terms'
expect(current_path).to eq(root_path)
end
end
end
context 'when the users password is expired' do
before do
user.update!(password_expires_at: Time.parse('2018-05-08 11:29:46 UTC'))
end
it 'asks the user to accept the terms before setting a new password' do
visit new_user_session_path
fill_in 'user_login', with: user.email
fill_in 'user_password', with: '12345678'
click_button 'Sign in'
expect_to_be_on_terms_page
click_button 'Accept terms'
expect(current_path).to eq(new_profile_password_path)
fill_in 'user_current_password', with: '12345678'
fill_in 'user_password', with: 'new password'
fill_in 'user_password_confirmation', with: 'new password'
click_button 'Set new password'
expect(page).to have_content('Password successfully changed')
end
end
context 'when the user does not have an email configured' do
let(:user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'saml', email: 'temp-email-for-oauth-user@gitlab.localhost') }
before do
stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [mock_saml_config])
end
it 'asks the user to accept the terms before setting an email' do
gitlab_sign_in_via('saml', user, 'my-uid')
expect_to_be_on_terms_page
click_button 'Accept terms'
expect(current_path).to eq(profile_path)
fill_in 'Email', with: 'hello@world.com'
click_button 'Update profile settings'
expect(page).to have_content('Profile was successfully updated')
end
end
end
end
......@@ -24,7 +24,7 @@ describe('RepoEditor', () => {
f.active = true;
f.tempFile = true;
vm.$store.state.openFiles.push(f);
vm.$store.state.entries[f.path] = f;
Vue.set(vm.$store.state.entries, f.path, f);
vm.monaco = true;
vm.$mount();
......@@ -215,6 +215,30 @@ describe('RepoEditor', () => {
expect(vm.editor.attachModel).toHaveBeenCalledWith(vm.model);
});
it('attaches model to merge request editor', () => {
vm.$store.state.viewer = 'mrdiff';
vm.file.mrChange = true;
spyOn(vm.editor, 'attachMergeRequestModel');
Editor.editorInstance.modelManager.dispose();
vm.setupEditor();
expect(vm.editor.attachMergeRequestModel).toHaveBeenCalledWith(vm.model);
});
it('does not attach model to merge request editor when not a MR change', () => {
vm.$store.state.viewer = 'mrdiff';
vm.file.mrChange = false;
spyOn(vm.editor, 'attachMergeRequestModel');
Editor.editorInstance.modelManager.dispose();
vm.setupEditor();
expect(vm.editor.attachMergeRequestModel).not.toHaveBeenCalledWith(vm.model);
});
it('adds callback methods', () => {
spyOn(vm.editor, 'onPositionChange').and.callThrough();
......
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20180508100222_add_not_null_constraint_to_project_mirror_data_foreign_key.rb')
describe AddNotNullConstraintToProjectMirrorDataForeignKey, :migration do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:import_state) { table(:project_mirror_data) }
before do
import_state.create!(id: 1, project_id: nil, status: :started)
end
it 'removes every import state without an associated project_id' do
expect do
subject.up
end.to change { import_state.count }.from(1).to(0)
end
end
......@@ -198,16 +198,30 @@ describe Ci::Runner do
end
describe '#assign_to' do
let!(:project) { FactoryBot.create :project }
let!(:shared_runner) { FactoryBot.create(:ci_runner, :shared) }
let!(:project) { FactoryBot.create(:project) }
before do
shared_runner.assign_to(project)
subject { runner.assign_to(project) }
context 'with shared runner' do
let!(:runner) { FactoryBot.create(:ci_runner, :shared) }
it 'transitions shared runner to project runner and assigns project' do
subject
expect(runner).to be_specific
expect(runner).to be_project_type
expect(runner.projects).to eq([project])
expect(runner.only_for?(project)).to be_truthy
end
end
it { expect(shared_runner).to be_specific }
it { expect(shared_runner.projects).to eq([project]) }
it { expect(shared_runner.only_for?(project)).to be_truthy }
context 'with group runner' do
let!(:runner) { FactoryBot.create(:ci_runner, runner_type: :group_type) }
it 'raises an error' do
expect { subject }
.to raise_error(ArgumentError, 'Transitioning a group runner to a project runner is not supported')
end
end
end
describe '.online' do
......
......@@ -36,24 +36,26 @@ describe ShaAttribute do
end
context 'when the table does not exist' do
it 'allows the attribute to be added' do
it 'allows the attribute to be added and issues a warning' do
allow(model).to receive(:table_exists?).and_return(false)
expect(model).not_to receive(:columns)
expect(model).to receive(:attribute)
expect(model).to receive(:warn)
model.sha_attribute(:name)
end
end
context 'when the column does not exist' do
it 'raises ArgumentError' do
it 'allows the attribute to be added and issues a warning' do
allow(model).to receive(:table_exists?).and_return(true)
expect(model).to receive(:columns)
expect(model).not_to receive(:attribute)
expect(model).to receive(:attribute)
expect(model).to receive(:warn)
expect { model.sha_attribute(:no_name) }.to raise_error(ArgumentError)
model.sha_attribute(:no_name)
end
end
......
......@@ -159,6 +159,17 @@ describe ProjectWiki do
expect(page.title).to eq("autre pagé")
end
end
context 'pages with invalidly-encoded content' do
before do
create_page("encoding is fun", "f\xFCr".b)
end
it "can find the page" do
page = subject.find_page("encoding is fun")
expect(page.content).to eq("fr")
end
end
end
context 'when Gitaly wiki_find_page is enabled' do
......
......@@ -7,113 +7,138 @@ describe ObjectStorage::MigrateUploadsWorker, :sidekiq do
end
end
let!(:projects) { create_list(:project, 10, :with_avatar) }
let(:uploads) { Upload.all }
let(:model_class) { Project }
let(:mounted_as) { :avatar }
let(:uploads) { Upload.all }
let(:to_store) { ObjectStorage::Store::REMOTE }
before do
stub_uploads_object_storage(AvatarUploader)
end
describe '.enqueue!' do
def enqueue!
described_class.enqueue!(uploads, Project, mounted_as, to_store)
end
shared_examples "uploads migration worker" do
describe '.enqueue!' do
def enqueue!
described_class.enqueue!(uploads, Project, mounted_as, to_store)
end
it 'is guarded by .sanity_check!' do
expect(described_class).to receive(:perform_async)
expect(described_class).to receive(:sanity_check!)
it 'is guarded by .sanity_check!' do
expect(described_class).to receive(:perform_async)
expect(described_class).to receive(:sanity_check!)
enqueue!
end
enqueue!
end
context 'sanity_check! fails' do
include_context 'sanity_check! fails'
context 'sanity_check! fails' do
include_context 'sanity_check! fails'
it 'does not enqueue a job' do
expect(described_class).not_to receive(:perform_async)
it 'does not enqueue a job' do
expect(described_class).not_to receive(:perform_async)
expect { enqueue! }.to raise_error(described_class::SanityCheckError)
expect { enqueue! }.to raise_error(described_class::SanityCheckError)
end
end
end
end
describe '.sanity_check!' do
shared_examples 'raises a SanityCheckError' do
let(:mount_point) { nil }
describe '.sanity_check!' do
shared_examples 'raises a SanityCheckError' do
let(:mount_point) { nil }
it do
expect { described_class.sanity_check!(uploads, model_class, mount_point) }
.to raise_error(described_class::SanityCheckError)
it do
expect { described_class.sanity_check!(uploads, model_class, mount_point) }
.to raise_error(described_class::SanityCheckError)
end
end
end
context 'uploader types mismatch' do
let!(:outlier) { create(:upload, uploader: 'FileUploader') }
before do
stub_const("WrongModel", Class.new)
end
include_examples 'raises a SanityCheckError'
end
context 'uploader types mismatch' do
let!(:outlier) { create(:upload, uploader: 'GitlabUploader') }
context 'model types mismatch' do
let!(:outlier) { create(:upload, model_type: 'Potato') }
include_examples 'raises a SanityCheckError'
end
include_examples 'raises a SanityCheckError'
end
context 'model types mismatch' do
let!(:outlier) { create(:upload, model_type: 'WrongModel') }
context 'mount point not found' do
include_examples 'raises a SanityCheckError' do
let(:mount_point) { :potato }
include_examples 'raises a SanityCheckError'
end
end
end
describe '#perform' do
def perform
described_class.new.perform(uploads.ids, model_class.to_s, mounted_as, to_store)
rescue ObjectStorage::MigrateUploadsWorker::Report::MigrationFailures
# swallow
context 'mount point not found' do
include_examples 'raises a SanityCheckError' do
let(:mount_point) { :potato }
end