Commit 36b711fe authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-01-05

# Conflicts:
#	app/assets/javascripts/boards/boards_bundle.js
#	app/assets/javascripts/boards/services/board_service.js
#	app/assets/javascripts/ide/components/repo_file.vue
#	app/assets/javascripts/ide/components/repo_tab.vue
#	app/models/project.rb
#	app/models/service.rb
#	app/serializers/merge_request_widget_entity.rb
#	lib/gitlab/git/repository.rb
#	spec/controllers/projects/artifacts_controller_spec.rb
#	spec/lib/gitlab/bare_repository_import/importer_spec.rb
#	spec/lib/gitlab/git/repository_spec.rb
#	spec/models/namespace_spec.rb
#	spec/models/project_spec.rb
#	spec/models/service_spec.rb

[ci skip]
parents de4c74f9 de3491cf
...@@ -456,6 +456,7 @@ ee_compat_check: ...@@ -456,6 +456,7 @@ ee_compat_check:
- master - master
- tags - tags
- /^[\d-]+-stable(-ee)?/ - /^[\d-]+-stable(-ee)?/
- /^security-/
- branches@gitlab-org/gitlab-ee - branches@gitlab-org/gitlab-ee
- branches@gitlab/gitlab-ee - branches@gitlab/gitlab-ee
retry: 0 retry: 0
...@@ -533,7 +534,7 @@ db:rollback-mysql: ...@@ -533,7 +534,7 @@ db:rollback-mysql:
<<: *db-rollback <<: *db-rollback
<<: *use-mysql <<: *use-mysql
.db-seed_fu: &db-seed_fu .gitlab-setup: &gitlab-setup
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs-and-qa <<: *except-docs-and-qa
<<: *pull-cache <<: *pull-cache
...@@ -542,22 +543,24 @@ db:rollback-mysql: ...@@ -542,22 +543,24 @@ db:rollback-mysql:
SIZE: "1" SIZE: "1"
SETUP_DB: "false" SETUP_DB: "false"
CREATE_DB_USER: "true" CREATE_DB_USER: "true"
FIXTURE_PATH: db/fixtures/development
script: script:
- git clone https://gitlab.com/gitlab-org/gitlab-test.git - git clone https://gitlab.com/gitlab-org/gitlab-test.git
/home/git/repositories/gitlab-org/gitlab-test.git /home/git/repositories/gitlab-org/gitlab-test.git
- bundle exec rake db:setup db:seed_fu - scripts/gitaly-test-spawn
- force=yes bundle exec rake gitlab:setup
artifacts: artifacts:
when: on_failure when: on_failure
expire_in: 1d expire_in: 1d
paths: paths:
- log/development.log - log/development.log
db:seed_fu-pg: gitlab:setup-pg:
<<: *db-seed_fu <<: *gitlab-setup
<<: *use-pg <<: *use-pg
db:seed_fu-mysql: gitlab:setup-mysql:
<<: *db-seed_fu <<: *gitlab-setup
<<: *use-mysql <<: *use-mysql
# Frontend-related jobs # Frontend-related jobs
......
...@@ -554,7 +554,7 @@ the feature you contribute through all of these steps. ...@@ -554,7 +554,7 @@ the feature you contribute through all of these steps.
1. Description explaining the relevancy (see following item) 1. Description explaining the relevancy (see following item)
1. Working and clean code that is commented where needed 1. Working and clean code that is commented where needed
1. [Unit and system tests][testing] that pass on the CI server 1. [Unit, integration, and system tests][testing] that pass on the CI server
1. Performance/scalability implications have been considered, addressed, and tested 1. Performance/scalability implications have been considered, addressed, and tested
1. [Documented][doc-styleguide] in the `/doc` directory 1. [Documented][doc-styleguide] in the `/doc` directory
1. [Changelog entry added][changelog], if necessary 1. [Changelog entry added][changelog], if necessary
......
...@@ -418,7 +418,7 @@ group :ed25519 do ...@@ -418,7 +418,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 0.62.0', require: 'gitaly' gem 'gitaly-proto', '~> 0.64.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false gem 'toml-rb', '~> 0.3.15', require: false
......
...@@ -308,7 +308,7 @@ GEM ...@@ -308,7 +308,7 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gherkin-ruby (0.3.2) gherkin-ruby (0.3.2)
gitaly-proto (0.62.0) gitaly-proto (0.64.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)
...@@ -1081,7 +1081,7 @@ DEPENDENCIES ...@@ -1081,7 +1081,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0) gettext_i18n_rails_js (~> 1.2.0)
gitaly-proto (~> 0.62.0) gitaly-proto (~> 0.64.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)
......
...@@ -25,12 +25,15 @@ import './components/new_list_dropdown'; ...@@ -25,12 +25,15 @@ import './components/new_list_dropdown';
import './components/modal/index'; import './components/modal/index';
import '../vue_shared/vue_resource_interceptor'; import '../vue_shared/vue_resource_interceptor';
<<<<<<< HEAD
// EE only // EE only
import './components/boards_selector'; import './components/boards_selector';
import collapseIcon from './icons/fullscreen_collapse.svg'; import collapseIcon from './icons/fullscreen_collapse.svg';
import expandIcon from './icons/fullscreen_expand.svg'; import expandIcon from './icons/fullscreen_expand.svg';
import tooltip from '../vue_shared/directives/tooltip'; import tooltip from '../vue_shared/directives/tooltip';
=======
>>>>>>> upstream/master
$(() => { $(() => {
const $boardApp = document.getElementById('board-app'); const $boardApp = document.getElementById('board-app');
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
......
...@@ -120,7 +120,7 @@ export default { ...@@ -120,7 +120,7 @@ export default {
}, },
mounted() { mounted() {
const options = gl.issueBoards.getBoardSortableDefaultOptions({ const options = gl.issueBoards.getBoardSortableDefaultOptions({
scroll: document.querySelectorAll('.boards-list')[0], scroll: true,
group: 'issues', group: 'issues',
disabled: this.disabled, disabled: this.disabled,
filter: '.board-list-count, .is-disabled', filter: '.board-list-count, .is-disabled',
......
...@@ -12,6 +12,7 @@ export default class BoardService { ...@@ -12,6 +12,7 @@ export default class BoardService {
generateBoardsPath(id) { generateBoardsPath(id) {
return `${this.boardsEndpoint}${id ? `/${id}` : ''}.json`; return `${this.boardsEndpoint}${id ? `/${id}` : ''}.json`;
<<<<<<< HEAD
} }
generateIssuesPath(id) { generateIssuesPath(id) {
...@@ -50,6 +51,16 @@ export default class BoardService { ...@@ -50,6 +51,16 @@ export default class BoardService {
deleteBoard({ id }) { deleteBoard({ id }) {
return axios.delete(this.generateBoardsPath(id)); return axios.delete(this.generateBoardsPath(id));
=======
}
generateIssuesPath(id) {
return `${this.listsEndpoint}${id ? `/${id}` : ''}/issues`;
}
static generateIssuePath(boardId, id) {
return `${gon.relative_url_root}/-/boards/${boardId ? `/${boardId}` : ''}/issues${id ? `/${id}` : ''}`;
>>>>>>> upstream/master
} }
all() { all() {
...@@ -118,12 +129,15 @@ export default class BoardService { ...@@ -118,12 +129,15 @@ export default class BoardService {
static getIssueInfo(endpoint) { static getIssueInfo(endpoint) {
return axios.get(endpoint); return axios.get(endpoint);
<<<<<<< HEAD
} }
static updateWeight(endpoint, weight = null) { static updateWeight(endpoint, weight = null) {
return axios.put(endpoint, { return axios.put(endpoint, {
weight, weight,
}); });
=======
>>>>>>> upstream/master
} }
static toggleIssueSubscription(endpoint) { static toggleIssueSubscription(endpoint) {
......
...@@ -77,7 +77,8 @@ export default { ...@@ -77,7 +77,8 @@ export default {
class="group-row" class="group-row"
> >
<div <div
class="group-row-contents"> class="group-row-contents"
:class="{ 'project-row-contents': !isGroup }">
<item-actions <item-actions
v-if="isGroup" v-if="isGroup"
:group="group" :group="group"
...@@ -97,7 +98,7 @@ export default { ...@@ -97,7 +98,7 @@ export default {
/> />
</div> </div>
<div <div
class="avatar-container s40 hidden-xs" class="avatar-container prepend-top-8 prepend-left-5 s24 hidden-xs"
:class="{ 'content-loading': group.isChildrenLoading }" :class="{ 'content-loading': group.isChildrenLoading }"
> >
<a <a
...@@ -106,11 +107,12 @@ export default { ...@@ -106,11 +107,12 @@ export default {
> >
<img <img
v-if="hasAvatar" v-if="hasAvatar"
class="avatar s40" class="avatar s24"
:src="group.avatarUrl" :src="group.avatarUrl"
/> />
<identicon <identicon
v-else v-else
size-class="s24"
:entity-id=group.id :entity-id=group.id
:entity-name="group.name" :entity-name="group.name"
/> />
...@@ -123,7 +125,7 @@ export default { ...@@ -123,7 +125,7 @@ export default {
:href="group.relativePath" :href="group.relativePath"
:title="group.fullName" :title="group.fullName"
class="no-expand" class="no-expand"
data-placement="top" data-placement="bottom"
>{{ >{{
// ending bracket must be by closing tag to prevent // ending bracket must be by closing tag to prevent
// link hover text-decoration from over-extending // link hover text-decoration from over-extending
......
<script> <script>
import { s__ } from '../../locale'; import { s__ } from '~/locale';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
import modal from '../../vue_shared/components/modal.vue'; import icon from '~/vue_shared/components/icon.vue';
import modal from '~/vue_shared/components/modal.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import { COMMON_STR } from '../constants'; import { COMMON_STR } from '../constants';
import Icon from '../../vue_shared/components/icon.vue';
export default { export default {
components: { components: {
Icon, icon,
modal, modal,
}, },
directives: { directives: {
...@@ -64,10 +64,9 @@ export default { ...@@ -64,10 +64,9 @@ export default {
:title="editBtnTitle" :title="editBtnTitle"
:aria-label="editBtnTitle" :aria-label="editBtnTitle"
data-container="body" data-container="body"
data-placement="bottom"
class="edit-group btn no-expand"> class="edit-group btn no-expand">
<icon <icon name="settings"/>
name="settings">
</icon>
</a> </a>
<a <a
v-tooltip v-tooltip
...@@ -77,10 +76,9 @@ export default { ...@@ -77,10 +76,9 @@ export default {
:title="leaveBtnTitle" :title="leaveBtnTitle"
:aria-label="leaveBtnTitle" :aria-label="leaveBtnTitle"
data-container="body" data-container="body"
data-placement="bottom"
class="leave-group btn no-expand"> class="leave-group btn no-expand">
<i <icon name="leave"/>
class="fa fa-sign-out"
aria-hidden="true"/>
</a> </a>
<modal <modal
v-show="modalStatus" v-show="modalStatus"
......
<script> <script>
import icon from '~/vue_shared/components/icon.vue';
export default { export default {
props: { props: {
isGroupOpen: { isGroupOpen: {
...@@ -7,9 +9,12 @@ export default { ...@@ -7,9 +9,12 @@ export default {
default: false, default: false,
}, },
}, },
components: {
icon,
},
computed: { computed: {
iconClass() { iconClass() {
return this.isGroupOpen ? 'fa-caret-down' : 'fa-caret-right'; return this.isGroupOpen ? 'angle-down' : 'angle-right';
}, },
}, },
}; };
...@@ -17,9 +22,9 @@ export default { ...@@ -17,9 +22,9 @@ export default {
<template> <template>
<span class="folder-caret"> <span class="folder-caret">
<i <icon
:class="iconClass" :size="12"
class="fa" :name="iconClass"
aria-hidden="true"/> />
</span> </span>
</template> </template>
<script> <script>
import tooltip from '../../vue_shared/directives/tooltip'; import icon from '~/vue_shared/components/icon.vue';
import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { ITEM_TYPE, VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE, PROJECT_VISIBILITY_TYPE } from '../constants'; import { ITEM_TYPE, VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE, PROJECT_VISIBILITY_TYPE } from '../constants';
import itemStatsValue from './item_stats_value.vue';
export default { export default {
directives: { components: {
tooltip, icon,
timeAgoTooltip,
itemStatsValue,
}, },
props: { props: {
item: { item: {
...@@ -34,65 +38,47 @@ export default { ...@@ -34,65 +38,47 @@ export default {
<template> <template>
<div class="stats"> <div class="stats">
<span <item-stats-value
v-tooltip
v-if="isGroup" v-if="isGroup"
css-class="number-subgroups"
icon-name="folder"
:title="s__('Subgroups')" :title="s__('Subgroups')"
class="number-subgroups" :value=item.subgroupCount
data-placement="top" />
data-container="body"> <item-stats-value
<i
class="fa fa-folder"
aria-hidden="true"
/>
{{item.subgroupCount}}
</span>
<span
v-tooltip
v-if="isGroup" v-if="isGroup"
css-class="number-projects"
icon-name="bookmark"
:title="s__('Projects')" :title="s__('Projects')"
class="number-projects" :value=item.projectCount
data-placement="top" />
data-container="body"> <item-stats-value
<i
class="fa fa-bookmark"
aria-hidden="true"
/>
{{item.projectCount}}
</span>
<span
v-tooltip
v-if="isGroup" v-if="isGroup"
css-class="number-users"
icon-name="users"
:title="s__('Members')" :title="s__('Members')"
class="number-users" :value=item.memberCount
data-placement="top" />
data-container="body"> <item-stats-value
<i
class="fa fa-users"
aria-hidden="true"
/>
{{item.memberCount}}
</span>
<span
v-if="isProject" v-if="isProject"
class="project-stars"> css-class="project-stars"
<i icon-name="star"
class="fa fa-star" :value=item.starCount
aria-hidden="true" />
/> <item-stats-value
{{item.starCount}} css-class="item-visibility"
</span> tooltip-placement="left"
<span :icon-name="visibilityIcon"
v-tooltip
:title="visibilityTooltip" :title="visibilityTooltip"
data-placement="left" />
data-container="body" <div
class="item-visibility"> class="last-updated"
<i v-if="isProject"
:class="visibilityIcon" >
class="fa" <time-ago-tooltip
aria-hidden="true" tooltip-placement="bottom"
:time="item.updatedAt"
/> />
</span> </div>
</div> </div>
</template> </template>
<script>
import tooltip from '~/vue_shared/directives/tooltip';
import icon from '~/vue_shared/components/icon.vue';
export default {
props: {
title: {
type: String,
required: false,
default: '',
},
cssClass: {
type: String,
required: false,
default: '',
},
iconName: {
type: String,
required: true,
},
tooltipPlacement: {
type: String,
required: false,
default: 'bottom',
},
/**
* value could either be number or string
* as `memberCount` is always passed as string
* while `subgroupCount` & `projectCount`
* are always number
*/
value: {
type: [Number, String],
required: false,
default: '',
},
},
directives: {
tooltip,
},
components: {
icon,
},
computed: {
isValuePresent() {
return this.value !== '';
},
},
};
</script>
<template>
<span
v-tooltip
data-container="body"
:data-placement="tooltipPlacement"
:class="cssClass"
:title="title"
>
<icon :name="iconName"/>
<span
v-if="isValuePresent"
class="stat-value"
>
{{value}}
</span>
</span>
</template>
<script> <script>
import icon from '~/vue_shared/components/icon.vue';
import { ITEM_TYPE } from '../constants'; import { ITEM_TYPE } from '../constants';
export default { export default {
components: {
icon,
},
props: { props: {
itemType: { itemType: {
type: String, type: String,
...@@ -16,9 +20,9 @@ export default { ...@@ -16,9 +20,9 @@ export default {
computed: { computed: {
iconClass() { iconClass() {
if (this.itemType === ITEM_TYPE.GROUP) { if (this.itemType === ITEM_TYPE.GROUP) {
return this.isGroupOpen ? 'fa-folder-open' : 'fa-folder'; return this.isGroupOpen ? 'folder-open' : 'folder';
} }
return 'fa-bookmark'; return 'bookmark';
}, },
}, },
}; };
...@@ -26,9 +30,6 @@ export default { ...@@ -26,9 +30,6 @@ export default {
<template> <template>
<span class="item-type-icon"> <span class="item-type-icon">
<i <icon :name="iconClass"/>
:class="iconClass"
class="fa"
aria-hidden="true"/>
</span> </span>
</template> </template>
...@@ -29,7 +29,7 @@ export const PROJECT_VISIBILITY_TYPE = { ...@@ -29,7 +29,7 @@ export const PROJECT_VISIBILITY_TYPE = {
}; };
export const VISIBILITY_TYPE_ICON = { export const VISIBILITY_TYPE_ICON = {
public: 'fa-globe', public: 'earth',
internal: 'fa-shield', internal: 'shield',
private: 'fa-lock', private: 'lock',
}; };
...@@ -91,6 +91,7 @@ export default class GroupsStore { ...@@ -91,6 +91,7 @@ export default class GroupsStore {
subgroupCount: rawGroupItem.subgroup_count, subgroupCount: rawGroupItem.subgroup_count,
memberCount: rawGroupItem.number_users_with_delimiter, memberCount: rawGroupItem.number_users_with_delimiter,
starCount: rawGroupItem.star_count, starCount: rawGroupItem.star_count,
updatedAt: rawGroupItem.updated_at,
}; };
} }
......
...@@ -2,11 +2,18 @@ ...@@ -2,11 +2,18 @@
import { mapGetters, mapState, mapActions } from 'vuex'; import { mapGetters, mapState, mapActions } from 'vuex';
import repoCommitSection from './repo_commit_section.vue'; import repoCommitSection from './repo_commit_section.vue';
import icon from '../../vue_shared/components/icon.vue'; import icon from '../../vue_shared/components/icon.vue';
import panelResizer from '../../vue_shared/components/panel_resizer.vue';
export default { export default {
data() {
return {
width: 290,
};
},
components: { components: {
repoCommitSection, repoCommitSection,
icon, icon,
panelResizer,
}, },
computed: { computed: {
...mapState([ ...mapState([
...@@ -18,10 +25,20 @@ export default { ...@@ -18,10 +25,20 @@ export default {
currentIcon() { currentIcon() {
return this.rightPanelCollapsed ? 'angle-double-left' : 'angle-double-right'; return this.rightPanelCollapsed ? 'angle-double-left' : 'angle-double-right';
}, },
maxSize() {
return window.innerWidth / 2;
},
panelStyle() {
if (!this.rightPanelCollapsed) {
return { width: `${this.width}px` };
}
return {};
},
}, },
methods: { methods: {
...mapActions([ ...mapActions([
'setPanelCollapsedStatus', 'setPanelCollapsedStatus',
'setResizingStatus',
]), ]),
toggleCollapsed() { toggleCollapsed() {
this.setPanelCollapsedStatus({ this.setPanelCollapsedStatus({
...@@ -29,6 +46,12 @@ export default { ...@@ -29,6 +46,12 @@ export default {
collapsed: !this.rightPanelCollapsed, collapsed: !this.rightPanelCollapsed,
}); });
}, },
resizingStarted() {
this.setResizingStatus(true);
},
resizingEnded() {
this.setResizingStatus(false);
},
}, },
}; };
</script> </script>
...@@ -39,6 +62,7 @@ export default { ...@@ -39,6 +62,7 @@ export default {
:class="{ :class="{
'is-collapsed': rightPanelCollapsed, 'is-collapsed': rightPanelCollapsed,
}" }"
:style="panelStyle"
> >
<div <div
class="multi-file-commit-panel-section"> class="multi-file-commit-panel-section">
...@@ -71,5 +95,14 @@ export default { ...@@ -71,5 +95,14 @@ export default {
<repo-commit-section <repo-commit-section
class=""/> class=""/>
</div> </div>
<panel-resizer
:size.sync="width"
:enabled="!rightPanelCollapsed"
:start-size="290"
:min-size="200"
:max-size="maxSize"
@resize-start="resizingStarted"
@resize-end="resizingEnded"
side="left"/>
</div> </div>
</template> </template>
...@@ -2,11 +2,18 @@ ...@@ -2,11 +2,18 @@
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import projectTree from './ide_project_tree.vue'; import projectTree from './ide_project_tree.vue';
import icon from '../../vue_shared/components/icon.vue'; import icon from '../../vue_shared/components/icon.vue';
import panelResizer from '../../vue_shared/components/panel_resizer.vue';
export default { export default {
data() {
return {
width: 290,
};
},
components: { components: {
projectTree, projectTree,
icon, icon,
panelResizer,
}, },
computed: { computed: {
...mapState([ ...mapState([
...@@ -16,10 +23,20 @@ export default { ...@@ -16,10 +23,20 @@ export default {
currentIcon() { currentIcon() {
return this.leftPanelCollapsed ? 'angle-double-right' : 'angle-double-left'; return this.leftPanelCollapsed ? 'angle-double-right' : 'angle-double-left';
}, },
maxSize() {
return window.innerWidth / 2;
},
panelStyle() {
if (!this.leftPanelCollapsed) {
return { width: `${this.width}px` };
}
return {};
},
}, },
methods: { methods: {
...mapActions([ ...mapActions([
'setPanelCollapsedStatus', 'setPanelCollapsedStatus',
'setResizingStatus',
]), ]),
toggleCollapsed() { toggleCollapsed() {
this.setPanelCollapsedStatus({ this.setPanelCollapsedStatus({
...@@ -27,6 +44,12 @@ export default { ...@@ -27,6 +44,12 @@ export default {
collapsed: !this.leftPanelCollapsed, collapsed: !this.leftPanelCollapsed,
}); });
}, },
resizingStarted() {
this.setResizingStatus(true);
},
resizingEnded() {
this.setResizingStatus(false);
},
}, },
}; };
</script> </script>
...@@ -37,6 +60,7 @@ export default { ...@@ -37,6 +60,7 @@ export default {
:class="{ :class="{
'is-collapsed': leftPanelCollapsed, 'is-collapsed': leftPanelCollapsed,
}" }"
:style="panelStyle"
> >
<div class="multi-file-commit-panel-inner"> <div class="multi-file-commit-panel-inner">
<project-tree <project-tree
...@@ -58,5 +82,14 @@ export default { ...@@ -58,5 +82,14 @@ export default {
class="collapse-text" class="collapse-text"
>Collapse sidebar</span> >Collapse sidebar</span>
</button> </button>
<panel-resizer
:size.sync="width"
:enabled="!leftPanelCollapsed"
:start-size="290"
:min-size="200"
:max-size="maxSize"
@resize-start="resizingStarted"
@resize-end="resizingEnded"
side="right"/>
</div> </div>
</template> </template>
...@@ -90,6 +90,11 @@ export default { ...@@ -90,6 +90,11 @@ export default {
rightPanelCollapsed() { rightPanelCollapsed() {
this.editor.updateDimensions(); this.editor.updateDimensions();
}, },
panelResizing(isResizing) {
if (isResizing === false) {
this.editor.updateDimensions();
}
},
}, },
computed: { computed: {
...mapGetters([ ...mapGetters([
...@@ -99,6 +104,7 @@ export default { ...@@ -99,6 +104,7 @@ export default {
...mapState([ ...mapState([
'leftPanelCollapsed', 'leftPanelCollapsed',
'rightPanelCollapsed', 'rightPanelCollapsed',
'panelResizing',
]), ]),
shouldHideEditor() { shouldHideEditor() {
return this.activeFile && this.activeFile.binary && !this.activeFile.raw; return this.activeFile && this.activeFile.binary && !this.activeFile.raw;
......
...@@ -13,7 +13,10 @@ ...@@ -13,7 +13,10 @@
components: { components: {
skeletonLoadingContainer, skeletonLoadingContainer,
newDropdown, newDropdown,
<<<<<<< HEAD
fileStatusIcon, fileStatusIcon,
=======
>>>>>>> upstream/master
fileIcon, fileIcon,
}, },
props: { props: {
......
<script> <script>
import { mapActions } from 'vuex'; import { mapActions } from 'vuex';
<<<<<<< HEAD
import fileStatusIcon from './repo_file_status_icon.vue'; import fileStatusIcon from './repo_file_status_icon.vue';
=======
>>>>>>> upstream/master
import fileIcon from '../../vue_shared/components/file_icon.vue'; import fileIcon from '../../vue_shared/components/file_icon.vue';
export default { export default {
...@@ -10,12 +13,18 @@ export default { ...@@ -10,12 +13,18 @@ export default {
required: true, required: true,
}, },
}, },
<<<<<<< HEAD
components: { components: {
fileStatusIcon, fileStatusIcon,
fileIcon, fileIcon,
}, },
=======
components: {
fileIcon,
},
>>>>>>> upstream/master
computed: { computed: {
closeLabel() { closeLabel() {
if (this.tab.changed || this.tab.tempFile) { if (this.tab.changed || this.tab.tempFile) {
......
...@@ -63,6 +63,10 @@ export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => { ...@@ -63,6 +63,10 @@ export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => {
} }
}; };
export const setResizingStatus = ({ commit }, resizing) => {
commit(types.SET_RESIZING_STATUS, resizing);
};
export const checkCommitStatus = ({ state }) => export const checkCommitStatus = ({ state }) =>
service service
.getBranchData(state.currentProjectId, state.currentBranchId) .getBranchData(state.currentProjectId, state.currentBranchId)
......
...@@ -5,6 +5,7 @@ export const SET_ROOT = 'SET_ROOT'; ...@@ -5,6 +5,7 @@ export const SET_ROOT = 'SET_ROOT';
export const SET_LAST_COMMIT_DATA = 'SET_LAST_COMMIT_DATA'; export const SET_LAST_COMMIT_DATA = 'SET_LAST_COMMIT_DATA';
export const SET_LEFT_PANEL_COLLAPSED = 'SET_LEFT_PANEL_COLLAPSED'; export const SET_LEFT_PANEL_COLLAPSED = 'SET_LEFT_PANEL_COLLAPSED';
export const SET_RIGHT_PANEL_COLLAPSED = 'SET_RIGHT_PANEL_COLLAPSED'; export const SET_RIGHT_PANEL_COLLAPSED = 'SET_RIGHT_PANEL_COLLAPSED';
export const SET_RESIZING_STATUS = 'SET_RESIZING_STATUS';
// Project Mutation Types // Project Mutation Types
export const SET_PROJECT = 'SET_PROJECT'; export const SET_PROJECT = 'SET_PROJECT';
......
...@@ -49,6 +49,11 @@ export default { ...@@ -49,6 +49,11 @@ export default {
rightPanelCollapsed: collapsed, rightPanelCollapsed: collapsed,
}); });
}, },
[types.SET_RESIZING_STATUS](state, resizing) {
Object.assign(state, {
panelResizing: resizing,
});
},
[types.SET_LAST_COMMIT_DATA](state, { entry, lastCommit }) { [types.SET_LAST_COMMIT_DATA](state, { entry, lastCommit }) {
Object.assign(entry.lastCommit, { Object.assign(entry.lastCommit, {
id: lastCommit.commit.id, id: lastCommit.commit.id,
......
...@@ -19,4 +19,5 @@ export default () => ({ ...@@ -19,4 +19,5 @@ export default () => ({
projects: {}, projects: {},
leftPanelCollapsed: false, leftPanelCollapsed: false,
rightPanelCollapsed: true, rightPanelCollapsed: true,
panelResizing: false,
}); });
...@@ -96,14 +96,15 @@ export default class Job { ...@@ -96,14 +96,15 @@ export default class Job {
// eslint-disable-next-line class-methods-use-this // eslint-disable-next-line class-methods-use-this
canScroll() { canScroll() {
return this.$document.height() > this.$window.height(); return $(document).height() > $(window).height();
} }
toggleScroll() { toggleScroll() {
const currentPosition = this.$document.scrollTop(); const $document = $(document);
const scrollHeight = this.$document.height(); const currentPosition = $document.scrollTop();
const scrollHeight = $document.height();
const windowHeight = this.$window.height(); const windowHeight = $(window).height();
if (this.canScroll()) { if (this.canScroll()) {
if (currentPosition > 0 && if (currentPosition > 0 &&
(scrollHeight - currentPosition !== windowHeight)) { (scrollHeight - currentPosition !== windowHeight)) {
...@@ -127,18 +128,22 @@ export default class Job { ...@@ -127,18 +128,22 @@ export default class Job {
this.toggleDisableButton(this.$scrollBottomBtn, true); this.toggleDisableButton(this.$scrollBottomBtn, true);
} }
} }
// eslint-disable-next-line class-methods-use-this
isScrolledToBottom() { isScrolledToBottom() {
const currentPosition = this.$document.scrollTop(); const $document = $(document);
const scrollHeight = this.$document.height();
const currentPosition = $document.scrollTop();
const scrollHeight = $document.height();
const windowHeight = $(window).height();
const windowHeight = this.$window.height();
return scrollHeight - currentPosition === windowHeight; return scrollHeight - currentPosition === windowHeight;
} }
// eslint-disable-next-line class-methods-use-this // eslint-disable-next-line class-methods-use-this
scrollDown() { scrollDown() {
this.$document.scrollTop(this.$document.height()); const $document = $(document);
$document.scrollTop($document.height());
} }
scrollToBottom() { scrollToBottom() {
...@@ -148,7 +153,7 @@ export default class Job { ...@@ -148,7 +153,7 @@ export default class Job {
} }
scrollToTop() { scrollToTop() {
this.$document.scrollTop(0); $(document).scrollTop(0);
this.hasBeenScrolled = true; this.hasBeenScrolled = true;
this.toggleScroll(); this.toggleScroll();
} }
......
import { timeFormat as time } from 'd3-time-format'; import { timeFormat as time } from 'd3-time-format';
import { timeSecond, timeMinute, timeHour, timeDay, timeMonth, timeYear } from 'd3-time'; import { timeSecond, timeMinute, timeHour, timeDay, timeWeek, timeMonth, timeYear } from 'd3-time';
import { bisector } from 'd3-array'; import { bisector } from 'd3-array';
const d3 = { time, bisector, timeSecond, timeMinute, timeHour, timeDay, timeMonth, timeYear }; const d3 = {
time,
bisector,
timeSecond,
timeMinute,
timeHour,
timeDay,
timeWeek,
timeMonth,
timeYear,
};
export const dateFormat = d3.time('%b %-d, %Y'); export const dateFormat = d3.time('%b %-d, %Y');
export const timeFormat = d3.time('%-I:%M%p'); export const timeFormat = d3.time('%-I:%M%p');
......
/* eslint-disable comma-dangle, no-unused-vars, class-methods-use-this, quotes, consistent-return, func-names, prefer-arrow-callback, space-before-function-paren, max-len */ /* eslint-disable comma-dangle, no-unused-vars, class-methods-use-this, quotes, consistent-return, func-names, prefer-arrow-callback, space-before-function-paren, max-len */
import Cookies from 'js-cookie';
import Flash from '../flash'; import Flash from '../flash';
import { getPagePath } from '../lib/utils/common_utils'; import { getPagePath } from '../lib/utils/common_utils';
...@@ -7,6 +8,8 @@ import { getPagePath } from '../lib/utils/common_utils'; ...@@ -7,6 +8,8 @@ import { getPagePath } from '../lib/utils/common_utils';
constructor({ form } = {}) { constructor({ form } = {}) {
this.onSubmitForm = this.onSubmitForm.bind(this); this.onSubmitForm = this.onSubmitForm.bind(this);
this.form = form || $('.edit-user'); this.form = form || $('.edit-user');
this.newRepoActivated = Cookies.get('new_repo');
this.setRepoRadio();
this.bindEvents(); this.bindEvents();
this.initAvatarGlCrop(); this.initAvatarGlCrop();
} }
...@@ -25,6 +28,7 @@ import { getPagePath } from '../lib/utils/common_utils'; ...@@ -25,6 +28,7 @@ import { getPagePath } from '../lib/utils/common_utils';
bindEvents() { bindEvents() {
$('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm); $('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm);
$('input[name="user[multi_file]"]').on('change', this.setNewRepoCookie);
$('#user_notification_email').on('change', this.submitForm); $('#user_notification_email').on('change', this.submitForm);
$('#user_notified_of_own_activity').on('change', this.submitForm); $('#user_notified_of_own_activity').on('change', this.submitForm);
$('.update-username').on('ajax:before', this.beforeUpdateUsername); $('.update-username').on('ajax:before', this.beforeUpdateUsername);
...@@ -82,6 +86,23 @@ import { getPagePath } from '../lib/utils/common_utils'; ...@@ -82,6 +86,23 @@ import { getPagePath } from '../lib/utils/common_utils';
} }
}); });
} }
setNewRepoCookie() {
if (this.value === 'off') {
Cookies.remove('new_repo');
} else {
Cookies.set('new_repo', true, { expires_in: 365 });
}
}
setRepoRadio() {
const multiEditRadios = $('input[name="user[multi_file]"]');
if (this.newRepoActivated || this.newRepoActivated === 'true') {
multiEditRadios.filter('[value=on]').prop('checked', true);
} else {
multiEditRadios.filter('[value=off]').prop('checked', true);
}
}
} }
$(function() { $(function() {
......
<script>
export default {
props: {
startSize: {
type: Number,
required: true,
},
side: {
type: String,
required: true,
},
minSize: {
type: Number,
required: false,
default: 0,
},
maxSize: {
type: Number,
required: false,
default: Number.MAX_VALUE,
},
enabled: {
type: Boolean,
required: false,
default: true,
},
},
data() {
return {
size: this.startSize,
};
},
computed: {
className() {
return `drag${this.side}`;
},
cursorStyle() {
if (this.enabled) {
return { cursor: 'ew-resize' };
}
return {};
},
},
methods: {
resetSize(e) {
e.preventDefault();
this.size = this.startSize;
this.$emit('update:size', this.size);
},
startDrag(e) {
if (this.enabled) {
e.preventDefault();
this.startPos = e.clientX;
this.currentStartSize = this.size;
document.addEventListener('mousemove', this.drag);
document.addEventListener('mouseup', this.endDrag, { once: true });
this.$emit('resize-start', this.size);
}
},
drag(e) {
e.preventDefault();
let moved = e.clientX - this.startPos;
if (this.side === 'left') moved = -moved;
let newSize = this.currentStartSize + moved;
if (newSize < this.minSize) {
newSize = this.minSize;
} else if (newSize > this.maxSize) {
newSize = this.maxSize;
}
this.size = newSize;
this.$emit('update:size', newSize);
},
endDrag(e) {
e.preventDefault();
document.removeEventListener('mousemove', this.drag);
this.$emit('resize-end', this.size);
},
},
};
</script>
<template>
<div
class="dragHandle"
:class="className"
:style="cursorStyle"
@mousedown="startDrag"
@dblclick="resetSize"
></div>
</template>
...@@ -75,7 +75,7 @@ ...@@ -75,7 +75,7 @@
vertical-align: top; vertical-align: top;
&.s16 { font-size: 12px; line-height: 1.33; } &.s16 { font-size: 12px; line-height: 1.33; }
&.s24 { font-size: 14px; line-height: 1.8; } &.s24 { font-size: 13px; line-height: 1.8; }
&.s26 { font-size: 20px; line-height: 1.33; } &.s26 { font-size: 20px; line-height: 1.33; }
&.s32 { font-size: 20px; line-height: 30px; } &.s32 { font-size: 20px; line-height: 30px; }
&.s40 { font-size: 16px; line-height: 38px; } &.s40 { font-size: 16px; line-height: 38px; }
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
.context-header { .context-header {
position: relative; position: relative;
margin-right: 2px; margin-right: 2px;
width: $contextual-sidebar-width;
a { a {
transition: padding $sidebar-transition-duration; transition: padding $sidebar-transition-duration;
......
...@@ -520,7 +520,7 @@ ...@@ -520,7 +520,7 @@
.header-user { .header-user {
.dropdown-menu-nav { .dropdown-menu-nav {
width: auto; width: auto;
min-width: 140px; min-width: 160px;
margin-top: 4px; margin-top: 4px;
color: $gl-text-color; color: $gl-text-color;
left: auto; left: auto;
......
...@@ -126,10 +126,8 @@ ul.content-list { ...@@ -126,10 +126,8 @@ ul.content-list {
} }
.description { .description {
p { @include str-truncated;
@include str-truncated; color: $gl-text-color-secondary;
margin-bottom: 0;
}
} }
.controls { .controls {
...@@ -321,7 +319,7 @@ ul.indent-list { ...@@ -321,7 +319,7 @@ ul.indent-list {
border: 2px solid $white-normal; border: 2px solid $white-normal;
&.identicon { &.identicon {
line-height: 30px; line-height: 15px;
} }
} }
} }
...@@ -355,14 +353,19 @@ ul.indent-list { ...@@ -355,14 +353,19 @@ ul.indent-list {
.folder-caret { .folder-caret {
width: 15px; width: 15px;
svg {
margin-bottom: 2px;
}
} }
.item-type-icon { .item-type-icon {
margin-top: 2px;
width: 20px; width: 20px;
} }
> .group-row:not(.has-children) { > .group-row:not(.has-children) {
.folder-caret .fa { .folder-caret {
opacity: 0; opacity: 0;
} }
} }
...@@ -445,12 +448,61 @@ ul.indent-list { ...@@ -445,12 +448,61 @@ ul.indent-list {
.avatar-container > a { .avatar-container > a {
width: 100%; width: 100%;
text-decoration: none;
} }
&.has-more-items { &.has-more-items {
display: block; display: block;
padding: 20px 10px; padding: 20px 10px;
} }
.stats {
position: relative;
line-height: 46px;
> span {
display: inline-flex;
align-items: center;
height: 16px;
min-width: 30px;
}
> span:last-child {
margin-right: 0;
}
.stat-value {
margin: 2px 0 0 5px;
}
}
.controls {
margin-left: 5px;
> .btn {
margin-right: $btn-xs-side-margin;
}
}
}
.project-row-contents .stats {
line-height: inherit;
> span:first-child {
margin-left: 25px;
}
.item-visibility {
margin-right: 0;
}
.last-updated {
position: absolute;
right: 12px;
min-width: 250px;
text-align: right;
color: $gl-text-color-secondary;
}
} }
} }
...@@ -462,12 +514,12 @@ ul.indent-list { ...@@ -462,12 +514,12 @@ ul.indent-list {
ul.group-list-tree { ul.group-list-tree {
li.group-row { li.group-row {
&.has-description .title { > .group-row-contents .title {
line-height: inherit; line-height: $list-text-height;
} }
&:not(.has-description) .title { &.has-description > .group-row-contents .title {
line-height: $list-text-height; line-height: inherit;
} }
} }
} }
......
...@@ -178,6 +178,10 @@ ...@@ -178,6 +178,10 @@
font-weight: inherit; font-weight: inherit;
} }
dd {
margin-left: $gl-padding;
}
ul, ul,
ol { ol {
padding: 0; padding: 0;
......
...@@ -760,3 +760,8 @@ Popup ...@@ -760,3 +760,8 @@ Popup
$popup-triangle-size: 15px; $popup-triangle-size: 15px;
$popup-triangle-border-size: 1px; $popup-triangle-border-size: 1px;
$popup-box-shadow-color: rgba(90, 90, 90, 0.05); $popup-box-shadow-color: rgba(90, 90, 90, 0.05);
/*
Multi file editor
*/
$border-color-settings: #e1e1e1;
...@@ -20,6 +20,22 @@ ...@@ -20,6 +20,22 @@
} }
} }
.multi-file-editor-options {
label {
margin-right: 20px;
text-align: center;
}
.preview {
font-size: 0;
img {
border: 1px solid $border-color-settings;
border-radius: 4px;
}
}
}
.application-theme { .application-theme {
label { label {
margin-right: 20px; margin-right: 20px;
......
...@@ -41,10 +41,6 @@ ...@@ -41,10 +41,6 @@
} }
} }
.with-performance-bar .ide-view {
height: calc(100vh - #{$header-height});
}
.ide-file-list { .ide-file-list {
flex: 1; flex: 1;
...@@ -247,12 +243,13 @@ table.table tr td.multi-file-table-name { ...@@ -247,12 +243,13 @@ table.table tr td.multi-file-table-name {
.multi-file-commit-panel { .multi-file-commit-panel {
display: flex; display: flex;
position: relative;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
width: 290px; width: 290px;
padding: 0; padding: 0;
background-color: $gray-light; background-color: $gray-light;
border-left: 1px solid $white-dark; padding-right: 3px;
.projects-sidebar { .projects-sidebar {
display: flex; display: flex;
...@@ -508,3 +505,30 @@ table.table tr td.multi-file-table-name { ...@@ -508,3 +505,30 @@ table.table tr td.multi-file-table-name {
margin-top: $header-height; margin-top: $header-height;
margin-bottom: 0; margin-bottom: 0;
} }
.with-performance-bar {
.ide-flash-container.flash-container {
margin-top: $header-height + $performance-bar-height;
}
.ide-view {
height: calc(100vh - #{$header-height + $performance-bar-height});
}
}
.dragHandle {
position: absolute;
top: 0;
bottom: 0;
width: 3px;
background-color: $white-dark;
&.dragright {
right: 0;
}
&.dragleft {
left: 0;
}
}
...@@ -410,7 +410,7 @@ module ProjectsHelper ...@@ -410,7 +410,7 @@ module ProjectsHelper
end end
def add_special_file_path(project, file_name:, commit_message: nil, branch_name: nil, context: nil) def add_special_file_path(project, file_name:, commit_message: nil, branch_name: nil, context: nil)
commit_message ||= s_("CommitMessage|Add %{file_name}") % { file_name: file_name.downcase } commit_message ||= s_("CommitMessage|Add %{file_name}") % { file_name: file_name }
project_new_blob_path( project_new_blob_path(
project, project,
project.default_branch || 'master', project.default_branch || 'master',
......
module DeploymentPlatform
def deployment_platform
@deployment_platform ||= find_cluster_platform_kubernetes
@deployment_platform ||= find_kubernetes_service_integration
@deployment_platform ||= build_cluster_and_deployment_platform
end
private
def find_cluster_platform_kubernetes
clusters.find_by(enabled: true)&.platform_kubernetes
end
def find_kubernetes_service_integration
services.deployment.reorder(nil).find_by(active: true)
end
def build_cluster_and_deployment_platform
return unless kubernetes_service_template
cluster = ::Clusters::Cluster.create(cluster_attributes_from_service_template)
cluster.platform_kubernetes if cluster.persisted?
end
def kubernetes_service_template
@kubernetes_service_template ||= KubernetesService.active.find_by_template
end
def cluster_attributes_from_service_template
{
name: 'kubernetes-template',
projects: [self],
provider_type: :user,
platform_type: :kubernetes,
platform_kubernetes_attributes: platform_kubernetes_attributes_from_service_template
}
end
def platform_kubernetes_attributes_from_service_template
{
api_url: kubernetes_service_template.api_url,
ca_pem: kubernetes_service_template.ca_pem,
token: kubernetes_service_template.token,
namespace: kubernetes_service_template.namespace
}
end
end
...@@ -48,7 +48,18 @@ class Event < ActiveRecord::Base ...@@ -48,7 +48,18 @@ class Event < ActiveRecord::Base
belongs_to :author, class_name: "User" belongs_to :author, class_name: "User"
belongs_to :project belongs_to :project
belongs_to :target, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
belongs_to :target, -> {
# If the association for "target" defines an "author" association we want to
# eager-load this so Banzai & friends don't end up performing N+1 queries to
# get the authors of notes, issues, etc.
if reflections['events'].active_record.reflect_on_association(:author)
includes(:author)
else
self
end
}, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
has_one :push_event_payload has_one :push_event_payload
# Callbacks # Callbacks
......
...@@ -19,6 +19,7 @@ class Project < ActiveRecord::Base ...@@ -19,6 +19,7 @@ class Project < ActiveRecord::Base
include Routable include Routable
include GroupDescendant include GroupDescendant
include Gitlab::SQL::Pattern include Gitlab::SQL::Pattern
include DeploymentPlatform
# EE specific modules # EE specific modules
prepend EE::Project prepend EE::Project
...@@ -649,7 +650,7 @@ class Project < ActiveRecord::Base ...@@ -649,7 +650,7 @@ class Project < ActiveRecord::Base
end end
def import? def import?
external_import? || forked? || gitlab_project_import? external_import? || forked? || gitlab_project_import? || bare_repository_import?
end end
def no_import? def no_import?
...@@ -689,6 +690,10 @@ class Project < ActiveRecord::Base ...@@ -689,6 +690,10 @@ class Project < ActiveRecord::Base
Gitlab::UrlSanitizer.new(import_url).masked_url Gitlab::UrlSanitizer.new(import_url).masked_url
end end
def bare_repository_import?
import_type == 'bare_repository'
end
def gitlab_project_import? def gitlab_project_import?
import_type == 'gitlab_project' import_type == 'gitlab_project'
end end
...@@ -910,11 +915,14 @@ class Project < ActiveRecord::Base ...@@ -910,11 +915,14 @@ class Project < ActiveRecord::Base
@ci_service ||= ci_services.reorder(nil).find_by(active: true) @ci_service ||= ci_services.reorder(nil).find_by(active: true)
end end
<<<<<<< HEAD
def deployment_platform(environment: nil) def deployment_platform(environment: nil)
@deployment_platform ||= clusters.find_by(enabled: true)&.platform_kubernetes @deployment_platform ||= clusters.find_by(enabled: true)&.platform_kubernetes
@deployment_platform ||= services.where(category: :deployment).reorder(nil).find_by(active: true) @deployment_platform ||= services.where(category: :deployment).reorder(nil).find_by(active: true)
end end
=======
>>>>>>> upstream/master
def monitoring_services def monitoring_services
services.where(category: :monitoring) services.where(category: :monitoring)
end end
......
...@@ -44,6 +44,7 @@ class Service < ActiveRecord::Base ...@@ -44,6 +44,7 @@ class Service < ActiveRecord::Base
scope :pipeline_hooks, -> { where(pipeline_events: true, active: true) } scope :pipeline_hooks, -> { where(pipeline_events: true, active: true) }
scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) } scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) }
scope :external_issue_trackers, -> { issue_trackers.active.without_defaults } scope :external_issue_trackers, -> { issue_trackers.active.without_defaults }
scope :deployment, -> { where(category: 'deployment') }
default_value_for :category, 'common' default_value_for :category, 'common'
...@@ -277,6 +278,13 @@ class Service < ActiveRecord::Base ...@@ -277,6 +278,13 @@ class Service < ActiveRecord::Base
nil nil
end end
<<<<<<< HEAD
=======
def self.find_by_template
find_by(template: true)
end
>>>>>>> upstream/master
private private
def cache_project_has_external_issue_tracker def cache_project_has_external_issue_tracker
......
...@@ -19,6 +19,7 @@ class MergeRequestWidgetEntity < IssuableEntity ...@@ -19,6 +19,7 @@ class MergeRequestWidgetEntity < IssuableEntity
merge_request.project.merge_requests_ff_only_enabled merge_request.project.merge_requests_ff_only_enabled
end end
<<<<<<< HEAD
# EE-specific # EE-specific
expose :approvals_before_merge expose :approvals_before_merge
expose :squash expose :squash
...@@ -35,6 +36,8 @@ class MergeRequestWidgetEntity < IssuableEntity ...@@ -35,6 +36,8 @@ class MergeRequestWidgetEntity < IssuableEntity
presenter(merge_request).approvals_path presenter(merge_request).approvals_path
end end
=======
>>>>>>> upstream/master
expose :metrics do |merge_request| expose :metrics do |merge_request|
metrics = build_metrics(merge_request) metrics = build_metrics(merge_request)
......
...@@ -58,11 +58,7 @@ module Projects ...@@ -58,11 +58,7 @@ module Projects
after_create_actions if @project.persisted? after_create_actions if @project.persisted?
if @project.errors.empty? import_schedule
@project.import_schedule if @project.import?
else
fail(error: @project.errors.full_messages.join(', '))
end
@project @project
rescue ActiveRecord::RecordInvalid => e rescue ActiveRecord::RecordInvalid => e
...@@ -167,5 +163,15 @@ module Projects ...@@ -167,5 +163,15 @@ module Projects
@project.path = @project.name.dup.parameterize @project.path = @project.name.dup.parameterize
end end
end end
private
def import_schedule
if @project.errors.empty?
@project.import_schedule if @project.import? && !@project.bare_repository_import?
else
fail(error: @project.errors.full_messages.join(', '))
end
end
end end
end end
...@@ -56,6 +56,8 @@ ...@@ -56,6 +56,8 @@
= link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username } = link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username }
%li %li
= link_to "Settings", profile_path = link_to "Settings", profile_path
%li
= link_to "Turn on multi edit", profile_preferences_path
- if current_user - if current_user
%li %li
= link_to "Help", help_path = link_to "Help", help_path
......
...@@ -17,10 +17,6 @@ ...@@ -17,10 +17,6 @@
Status: #{current_user.two_factor_enabled? ? 'Enabled' : 'Disabled'} Status: #{current_user.two_factor_enabled? ? 'Enabled' : 'Disabled'}
- if current_user.two_factor_enabled? - if current_user.two_factor_enabled?
= link_to 'Manage two-factor authentication', profile_two_factor_auth_path, class: 'btn btn-info' = link_to 'Manage two-factor authentication', profile_two_factor_auth_path, class: 'btn btn-info'
= link_to 'Disable', profile_two_factor_auth_path,
method: :delete,
data: { confirm: "Are you sure? This will invalidate your registered applications and U2F devices." },
class: 'btn btn-danger'
- else - else
.append-bottom-10 .append-bottom-10
= link_to 'Enable two-factor authentication', profile_two_factor_auth_path, class: 'btn btn-success' = link_to 'Enable two-factor authentication', profile_two_factor_auth_path, class: 'btn btn-success'
......
...@@ -3,6 +3,23 @@ ...@@ -3,6 +3,23 @@
= render 'profiles/head' = render 'profiles/head'
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f| = form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f|
.col-lg-4
%h4.prepend-top-0
GitLab multi file editor
%p Unlock an additional editing experience which makes it possible to edit and commit multiple files
.col-lg-8.multi-file-editor-options
= label_tag do
.preview.append-bottom-10= image_tag "multi-editor-off.png"
= f.radio_button :multi_file, "off", checked: true
Off
= label_tag do
.preview.append-bottom-10= image_tag "multi-editor-on.png"
= f.radio_button :multi_file, "on", checked: false
On
.col-sm-12
%hr
.col-lg-4.application-theme .col-lg-4.application-theme
%h4.prepend-top-0 %h4.prepend-top-0
GitLab navigation theme GitLab navigation theme
......
- page_title 'Two-Factor Authentication', 'Account' - page_title 'Two-Factor Authentication', 'Account'
- add_to_breadcrumbs("Account", profile_account_path) - add_to_breadcrumbs("Two-Factor Authentication", profile_account_path)
- @content_class = "limit-container-width" unless fluid_layout - @content_class = "limit-container-width" unless fluid_layout
= render 'profiles/head' = render 'profiles/head'
...@@ -18,7 +18,12 @@ ...@@ -18,7 +18,12 @@
Use an app on your mobile device to enable two-factor authentication (2FA). Use an app on your mobile device to enable two-factor authentication (2FA).
.col-lg-8 .col-lg-8
- if current_user.two_factor_otp_enabled? - if current_user.two_factor_otp_enabled?
= icon "check inverse", base: "circle", class: "text-success", text: "You've already enabled two-factor authentication using mobile authenticator applications. You can disable it from your account settings page." %p
You've already enabled two-factor authentication using mobile authenticator applications. In order to register a different device, you must first disable two-factor authentication.
= link_to 'Disable two-factor authentication', profile_two_factor_auth_path,
method: :delete,
data: { confirm: "Are you sure? This will invalidate your registered applications and U2F devices." },
class: 'btn btn-danger'
- else - else
%p %p
Download the Google Authenticator application from App Store or Google Play Store and scan this code. Download the Google Authenticator application from App Store or Google Play Store and scan this code.
......
...@@ -5,22 +5,24 @@ ...@@ -5,22 +5,24 @@
= link_to icon('exchange'), { from: params[:to], to: params[:from] }, class: 'commits-compare-switch has-tooltip btn btn-white', title: 'Swap revisions' = link_to icon('exchange'), { from: params[:to], to: params[:from] }, class: 'commits-compare-switch has-tooltip btn btn-white', title: 'Swap revisions'
.form-group.dropdown.compare-form-group.to.js-compare-to-dropdown .form-group.dropdown.compare-form-group.to.js-compare-to-dropdown
.input-group.inline-input-group .input-group.inline-input-group
%span.input-group-addon Source %span.input-group-addon
= s_("CompareBranches|Source")
= hidden_field_tag :to, params[:to] = hidden_field_tag :to, params[:to]
= button_tag type: 'button', title: params[:to], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do = button_tag type: 'button', title: params[:to], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do
.dropdown-toggle-text.str-truncated= params[:to] || 'Select branch/tag' .dropdown-toggle-text.str-truncated= params[:to] || _("Select branch/tag")
= render 'shared/ref_dropdown' = render 'shared/ref_dropdown'
.compare-ellipsis.inline ... .compare-ellipsis.inline ...
.form-group.dropdown.compare-form-group.from.js-compare-from-dropdown .form-group.dropdown.compare-form-group.from.js-compare-from-dropdown
.input-group.inline-input-group .input-group.inline-input-group
%span.input-group-addon Target %span.input-group-addon
= s_("CompareBranches|Target")
= hidden_field_tag :from, params[:from] = hidden_field_tag :from, params[:from]
= button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do = button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do
.dropdown-toggle-text.str-truncated= params[:from] || 'Select branch/tag' .dropdown-toggle-text.str-truncated= params[:from] || _("Select branch/tag")
= render 'shared/ref_dropdown' = render 'shared/ref_dropdown'
&nbsp; &nbsp;
= button_tag "Compare", class: "btn btn-create commits-compare-btn" = button_tag s_("CompareBranches|Compare"), class: "btn btn-create commits-compare-btn"
- if @merge_request.present? - if @merge_request.present?
= link_to "View open merge request", project_merge_request_path(@project, @merge_request), class: 'prepend-left-10 btn' = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'prepend-left-10 btn'
- elsif create_mr_button? - elsif create_mr_button?
= link_to "Create merge request", create_mr_path, class: 'prepend-left-10 btn' = link_to _("Create merge request"), create_mr_path, class: 'prepend-left-10 btn'
...@@ -3,22 +3,16 @@ ...@@ -3,22 +3,16 @@
- page_title "Compare" - page_title "Compare"
%div{ class: container_class } %div{ class: container_class }
%h3.page-title
= _("Compare Git revisions")
.sub-header-block .sub-header-block
Compare Git revisions. - example_master = capture do
%br
Choose a branch/tag (e.g.
= succeed ')' do
%code.ref-name master %code.ref-name master
or enter a commit SHA (e.g. - example_sha = capture do
= succeed ')' do
%code.ref-name 4eedf23 %code.ref-name 4eedf23
to see what's changed or to create a merge request. = (_("Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request.") % { master: example_master, sha: example_sha }).html_safe
%br %br
Changes are shown as if the = (_("Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision.")).html_safe
%b source
revision was being merged into the
%b target
revision.
.prepend-top-20 .prepend-top-20
= render "form" = render "form"
- @no_container = true - @no_container = true
- add_to_breadcrumbs "Compare Revisions", project_compare_index_path(@project) - add_to_breadcrumbs _("Compare Revisions"), project_compare_index_path(@project)
- page_title "#{params[:from]}...#{params[:to]}" - page_title "#{params[:from]}...#{params[:to]}"
%div{ class: container_class } %div{ class: container_class }
...@@ -13,12 +13,13 @@ ...@@ -13,12 +13,13 @@
.light-well .light-well
.center .center
%h4 %h4
There isn't anything to compare. = s_("CompareBranches|There isn't anything to compare.")
%p.slead %p.slead
- if params[:to] == params[:from] - if params[:to] == params[:from]
%span.ref-name= params[:from] - source_branch = capture do
and %span.ref-name= params[:from]
%span.ref-name= params[:to] - target_branch = capture do
are the same. %span.ref-name= params[:to]
= (s_("CompareBranches|%{source_branch} and %{target_branch} are the same.") % { source_branch: source_branch, target_branch: target_branch }).html_safe
- else - else
You'll need to use different branch names to get a valid comparison. = _("You'll need to use different branch names to get a valid comparison.")
---
title: Fix ANSI 256 bold colors in pipelines job output
merge_request:
author:
type: fixed
---
title: Fix API endpoints to edit wiki pages where project belongs to a group
merge_request: 16170
author:
type: fixed
---
title: Update groups tree to use GitLab SVG icons, add last updated at information
for projects
merge_request: 15980
author:
type: changed
---
title: Allow automatic creation of Kubernetes Integration from template
merge_request: 16104
author:
type: added
---
title: Fix gitlab-rake gitlab:import:repos import schedule
merge_request: 16115
author:
type: fixed
---
title: Fix viewing merge request diffs where the underlying blobs are unavailable
merge_request:
author:
type: fixed
---
title: Use a background migration for issues.closed_at
merge_request:
author:
type: other
---
title: Eager load event target authors whenever possible
merge_request:
author:
type: performance
---
title: Move 2FA disable button
merge_request: 16177
author: George Tsiolis
type: fixed
---
title: Added option to user preferences to enable the multi file editor
merge_request: 16056
author:
type: added
---
title: Add i18n helpers to branch comparison view
merge_request: 16031
author: James Ramsay
type: added
---
title: Fix inconsistent downcase of filenames in prefilled `Add` commit messages
merge_request: 16232
author: James Ramsay
type: fixed
---
title: Prevent excessive DB load due to faulty DeleteConflictingRedirectRoutes background
migration
merge_request: 16205
author:
type: fixed
---
title: Speed up generation of commit stats by using Rugged native methods
merge_request:
author:
type: performance
---
title: Avoid leaving a push event empty if payload cannot be created
merge_request:
author:
type: fixed
...@@ -2,36 +2,12 @@ ...@@ -2,36 +2,12 @@
# for more information on how to write migrations for GitLab. # for more information on how to write migrations for GitLab.
class DeleteConflictingRedirectRoutes < ActiveRecord::Migration class DeleteConflictingRedirectRoutes < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
MIGRATION = 'DeleteConflictingRedirectRoutesRange'.freeze
BATCH_SIZE = 200 # At 200, I expect under 20s per batch, which is under our query timeout of 60s.
DELAY_INTERVAL = 12.seconds
disable_ddl_transaction!
class Route < ActiveRecord::Base
include EachBatch
self.table_name = 'routes'
end
def up def up
say opening_message # No-op.
# See https://gitlab.com/gitlab-com/infrastructure/issues/3460#note_53223252
queue_background_migration_jobs_by_range_at_intervals(Route, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE)
end end
def down def down
# nothing # nothing
end end
def opening_message
<<~MSG
Clean up redirect routes that conflict with regular routes.
See initial bug fix:
https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13357
MSG
end
end end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
# rubocop:disable Migration/Datetime
class ScheduleIssuesClosedAtTypeChange < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
class Issue < ActiveRecord::Base
self.table_name = 'issues'
include EachBatch
def self.to_migrate
where('closed_at IS NOT NULL')
end
end
def up
return unless migrate_column_type?
change_column_type_using_background_migration(
Issue.to_migrate,
:closed_at,
:datetime_with_timezone
)
end
def down
return if migrate_column_type?
change_column_type_using_background_migration(
Issue.to_migrate,
:closed_at,
:datetime
)
end
def migrate_column_type?
# Some environments may have already executed the previous version of this
# migration, thus we don't need to migrate those environments again.
column_for('issues', 'closed_at').type == :datetime
end
end
...@@ -195,6 +195,63 @@ end ...@@ -195,6 +195,63 @@ end
And that's it, we're done! And that's it, we're done!
## Changing Column Types For Large Tables
While `change_column_type_concurrently` can be used for changing the type of a
column without downtime it doesn't work very well for large tables. Because all
of the work happens in sequence the migration can take a very long time to
complete, preventing a deployment from proceeding.
`change_column_type_concurrently` can also produce a lot of pressure on the
database due to it rapidly updating many rows in sequence.
To reduce database pressure you should instead use
`change_column_type_using_background_migration` when migrating a column in a
large table (e.g. `issues`). This method works similar to
`change_column_type_concurrently` but uses background migration to spread the
work / load over a longer time period, without slowing down deployments.
Usage of this method is fairly simple:
```ruby
class ExampleMigration < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
class Issue < ActiveRecord::Base
self.table_name = 'issues'
include EachBatch
def self.to_migrate
where('closed_at IS NOT NULL')
end
end
def up
change_column_type_using_background_migration(
Issue.to_migrate,
:closed_at,
:datetime_with_timezone
)
end
def down
change_column_type_using_background_migration(
Issue.to_migrate,
:closed_at,
:datetime
)
end
end
```
This would change the type of `issues.closed_at` to `timestamp with time zone`.
Keep in mind that the relation passed to
`change_column_type_using_background_migration` _must_ include `EachBatch`,
otherwise it will raise a `TypeError`.
## Adding Indexes ## Adding Indexes
Adding indexes is an expensive process that blocks INSERT and UPDATE queries for Adding indexes is an expensive process that blocks INSERT and UPDATE queries for
......
...@@ -9,11 +9,11 @@ should be deployed, upgraded, and configured. ...@@ -9,11 +9,11 @@ should be deployed, upgraded, and configured.
## Chart Overview ## Chart Overview
* **[GitLab-Omnibus](gitlab_omnibus.md)**: The best way to run GitLab on Kubernetes today, suited for small to medium deployments. The chart is in beta and will be deprecated by the [cloud native GitLab chart](#cloud-native-gitlab-chart). * **[GitLab-Omnibus](gitlab_omnibus.md)**: The best way to run GitLab on Kubernetes today, suited for small deployments. The chart is in beta and will be deprecated by the [cloud native GitLab chart](#cloud-native-gitlab-chart).
* **[Cloud Native GitLab Chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md)**: The next generation GitLab chart, currently in development. Will support large deployments with horizontal scaling of individual GitLab components. * **[Cloud Native GitLab Chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md)**: The next generation GitLab chart, currently in development. Will support large deployments with horizontal scaling of individual GitLab components.
* Other Charts * Other Charts
* [GitLab Runner Chart](gitlab_runner_chart.md): For deploying just the GitLab Runner. * [GitLab Runner Chart](gitlab_runner_chart.md): For deploying just the GitLab Runner.
* [Advanced GitLab Installation](gitlab_chart.md): Deprecated, being replaced by the [cloud native GitLab chart](#cloud-native-gitlab-chart). Provides additional deployment options, but provides less functionality out-of-the-box. * [Advanced GitLab Installation](gitlab_chart.md): Deprecated, being replaced by the [cloud native GitLab chart](#cloud-native-gitlab-chart). Provides additional deployment options, but provides less functionality out-of-the-box.
* [Community Contributed Charts](#community-contributed-charts): Community contributed charts, deprecated by the official GitLab chart. * [Community Contributed Charts](#community-contributed-charts): Community contributed charts, deprecated by the official GitLab chart.
## GitLab-Omnibus Chart (Recommended) ## GitLab-Omnibus Chart (Recommended)
......
...@@ -20,6 +20,7 @@ project in an easy and automatic way: ...@@ -20,6 +20,7 @@ project in an easy and automatic way:
1. [Auto Test](#auto-test) 1. [Auto Test](#auto-test)
1. [Auto Code Quality](#auto-code-quality) 1. [Auto Code Quality](#auto-code-quality)
1. [Auto SAST (Static Application Security Testing)](#auto-sast) 1. [Auto SAST (Static Application Security Testing)](#auto-sast)
1. [Auto Browser Performance Testing](#auto-browser-performance-testing)
1. [Auto Review Apps](#auto-review-apps) 1. [Auto Review Apps](#auto-review-apps)
1. [Auto Deploy](#auto-deploy) 1. [Auto Deploy](#auto-deploy)
1. [Auto Monitoring](#auto-monitoring) 1. [Auto Monitoring](#auto-monitoring)
...@@ -220,6 +221,20 @@ check out. ...@@ -220,6 +221,20 @@ check out.
Any security warnings are also [shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/sast.html). Any security warnings are also [shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/sast.html).
### Auto Browser Performance Testing
> Introduced in [GitLab Enterprise Edition Premium][ee] 10.4.
Auto Browser Performance Testing utilizes the [Sitespeed.io container](https://hub.docker.com/r/sitespeedio/sitespeed.io/) to measure the performance of a web page. A JSON report is created and uploaded as an artifact, which includes the overall performance score for each page. By default, the root page of Review and Production environments will be tested. If you would like to add additional URL's to test, simply add the paths to a file named `.gitlab-urls.txt` in the root directory, one per line. For example:
```
/
/features
/direction
```
In GitLab Enterprise Edition Premium, performance differences between the source and target branches are [shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html).
### Auto Review Apps ### Auto Review Apps
NOTE: **Note:** NOTE: **Note:**
......
...@@ -104,6 +104,7 @@ added directly to your configured cluster. Those applications are needed for ...@@ -104,6 +104,7 @@ added directly to your configured cluster. Those applications are needed for
| ----------- | :------------: | ----------- | | ----------- | :------------: | ----------- |
| [Helm Tiller](https://docs.helm.sh/) | 10.2+ | Helm is a package manager for Kubernetes and is required to install all the other applications. It will be automatically installed as a dependency when you try to install a different app. It is installed in its own pod inside the cluster which can run the `helm` CLI in a safe environment. | | [Helm Tiller](https://docs.helm.sh/) | 10.2+ | Helm is a package manager for Kubernetes and is required to install all the other applications. It will be automatically installed as a dependency when you try to install a different app. It is installed in its own pod inside the cluster which can run the `helm` CLI in a safe environment. |
| [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps](../../../topics/autodevops/index.md) or deploy your own web apps. | | [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps](../../../topics/autodevops/index.md) or deploy your own web apps. |
| [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications |
## Enabling or disabling the Cluster integration ## Enabling or disabling the Cluster integration
......
--- ---
last_updated: 2017-09-25 last_updated: 2017-12-28
--- ---
CAUTION: **Warning:** CAUTION: **Warning:**
......
...@@ -72,7 +72,7 @@ module API ...@@ -72,7 +72,7 @@ module API
end end
def wiki_page def wiki_page
page = user_project.wiki.find_page(params[:slug]) page = ProjectWiki.new(user_project, current_user).find_page(params[:slug])
page || not_found!('Wiki Page') page || not_found!('Wiki Page')
end end
......
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# Background migration for cleaning up a concurrent column rename.
class CleanupConcurrentTypeChange
include Database::MigrationHelpers
RESCHEDULE_DELAY = 10.minutes
# table - The name of the table the migration is performed for.
# old_column - The name of the old (to drop) column.
# new_column - The name of the new column.
def perform(table, old_column, new_column)
return unless column_exists?(:issues, new_column)
rows_to_migrate = define_model_for(table)
.where(new_column => nil)
.where
.not(old_column => nil)
if rows_to_migrate.any?
BackgroundMigrationWorker.perform_in(
RESCHEDULE_DELAY,
'CleanupConcurrentTypeChange',
[table, old_column, new_column]
)
else
cleanup_concurrent_column_type_change(table, old_column)
end
end
# These methods are necessary so we can re-use the migration helpers in
# this class.
def connection
ActiveRecord::Base.connection
end
def method_missing(name, *args, &block)
connection.__send__(name, *args, &block) # rubocop: disable GitlabSecurity/PublicSend
end
def respond_to_missing?(*args)
connection.respond_to?(*args) || super
end
def define_model_for(table)
Class.new(ActiveRecord::Base) do
self.table_name = table
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# CopyColumn is a simple (reusable) background migration that can be used to
# update the value of a column based on the value of another column in the
# same table.
#
# For this background migration to work the table that is migrated _has_ to
# have an `id` column as the primary key.
class CopyColumn
# table - The name of the table that contains the columns.
# copy_from - The column containing the data to copy.
# copy_to - The column to copy the data to.
# start_id - The start ID of the range of rows to update.
# end_id - The end ID of the range of rows to update.
def perform(table, copy_from, copy_to, start_id, end_id)
return unless connection.column_exists?(table, copy_to)
quoted_table = connection.quote_table_name(table)
quoted_copy_from = connection.quote_column_name(copy_from)
quoted_copy_to = connection.quote_column_name(copy_to)
# We're using raw SQL here since this job may be frequently executed. As
# a result dynamically defining models would lead to many unnecessary
# schema information queries.
connection.execute <<-SQL.strip_heredoc
UPDATE #{quoted_table}
SET #{quoted_copy_to} = #{quoted_copy_from}
WHERE id BETWEEN #{start_id} AND #{end_id}
SQL
end
def connection
ActiveRecord::Base.connection
end
end
end
end
# frozen_string_literal: true # frozen_string_literal: true
# rubocop:disable Metrics/LineLength
# rubocop:disable Style/Documentation # rubocop:disable Style/Documentation
module Gitlab module Gitlab
module BackgroundMigration module BackgroundMigration
class DeleteConflictingRedirectRoutesRange class DeleteConflictingRedirectRoutesRange
class Route < ActiveRecord::Base
self.table_name = 'routes'
end
class RedirectRoute < ActiveRecord::Base
self.table_name = 'redirect_routes'
end
# start_id - The start ID of the range of events to process
# end_id - The end ID of the range to process.
def perform(start_id, end_id) def perform(start_id, end_id)
return unless migrate? # No-op.
# See https://gitlab.com/gitlab-com/infrastructure/issues/3460#note_53223252
conflicts = RedirectRoute.where(routes_match_redirects_clause(start_id, end_id))
num_rows = conflicts.delete_all
Rails.logger.info("Gitlab::BackgroundMigration::DeleteConflictingRedirectRoutesRange [#{start_id}, #{end_id}] - Deleted #{num_rows} redirect routes that were conflicting with routes.")
end
def migrate?
Route.table_exists? && RedirectRoute.table_exists?
end
def routes_match_redirects_clause(start_id, end_id)
<<~ROUTES_MATCH_REDIRECTS
EXISTS (
SELECT 1 FROM routes
WHERE (
LOWER(redirect_routes.path) = LOWER(routes.path)
OR LOWER(redirect_routes.path) LIKE LOWER(CONCAT(routes.path, '/%'))
)
AND routes.id BETWEEN #{start_id} AND #{end_id}
)
ROUTES_MATCH_REDIRECTS
end end
end end
end end
......
...@@ -128,8 +128,14 @@ module Gitlab ...@@ -128,8 +128,14 @@ module Gitlab
end end
def process_event(event) def process_event(event)
replicate_event(event) ActiveRecord::Base.transaction do
create_push_event_payload(event) if event.push_event? replicate_event(event)
create_push_event_payload(event) if event.push_event?
end
rescue ActiveRecord::InvalidForeignKey => e
# A foreign key error means the associated event was removed. In this
# case we'll just skip migrating the event.
Rails.logger.error("Unable to migrate event #{event.id}: #{e}")
end end
def replicate_event(event) def replicate_event(event)
...@@ -137,9 +143,6 @@ module Gitlab ...@@ -137,9 +143,6 @@ module Gitlab
.with_indifferent_access.except(:title, :data) .with_indifferent_access.except(:title, :data)
EventForMigration.create!(new_attributes) EventForMigration.create!(new_attributes)
rescue ActiveRecord::InvalidForeignKey
# A foreign key error means the associated event was removed. In this
# case we'll just skip migrating the event.
end end
def create_push_event_payload(event) def create_push_event_payload(event)
...@@ -156,9 +159,6 @@ module Gitlab ...@@ -156,9 +159,6 @@ module Gitlab
ref: event.trimmed_ref_name, ref: event.trimmed_ref_name,
commit_title: event.commit_title commit_title: event.commit_title
) )
rescue ActiveRecord::InvalidForeignKey
# A foreign key error means the associated event was removed. In this
# case we'll just skip migrating the event.
end end
def find_events(start_id, end_id) def find_events(start_id, end_id)
......
...@@ -55,7 +55,8 @@ module Gitlab ...@@ -55,7 +55,8 @@ module Gitlab
name: project_name, name: project_name,
path: project_name, path: project_name,
skip_disk_validation: true, skip_disk_validation: true,
import_type: 'gitlab_project', skip_wiki: bare_repo.wiki_exists?,
import_type: 'bare_repository',
namespace_id: group&.id).execute namespace_id: group&.id).execute
if project.persisted? && mv_repo(project) if project.persisted? && mv_repo(project)
......
...@@ -234,7 +234,7 @@ module Gitlab ...@@ -234,7 +234,7 @@ module Gitlab
# Most terminals show bold colored text in the light color variant # Most terminals show bold colored text in the light color variant
# Let's mimic that here # Let's mimic that here
if @style_mask & STYLE_SWITCHES[:bold] != 0 if @style_mask & STYLE_SWITCHES[:bold] != 0
fg_color.sub!(/fg-(\w{2,}+)/, 'fg-l-\1') fg_color.sub!(/fg-([a-z]{2,}+)/, 'fg-l-\1')
end end
css_classes << fg_color css_classes << fg_color
end end
......
...@@ -385,10 +385,27 @@ module Gitlab ...@@ -385,10 +385,27 @@ module Gitlab
# necessary since we copy over old values further down. # necessary since we copy over old values further down.
change_column_default(table, new, old_col.default) if old_col.default change_column_default(table, new, old_col.default) if old_col.default
trigger_name = rename_trigger_name(table, old, new) install_rename_triggers(table, old, new)
update_column_in_batches(table, new, Arel::Table.new(table)[old])
change_column_null(table, new, false) unless old_col.null
copy_indexes(table, old, new)
copy_foreign_keys(table, old, new)
end
# Installs triggers in a table that keep a new column in sync with an old
# one.
#
# table - The name of the table to install the trigger in.
# old_column - The name of the old column.
# new_column - The name of the new column.
def install_rename_triggers(table, old_column, new_column)
trigger_name = rename_trigger_name(table, old_column, new_column)
quoted_table = quote_table_name(table) quoted_table = quote_table_name(table)
quoted_old = quote_column_name(old) quoted_old = quote_column_name(old_column)
quoted_new = quote_column_name(new) quoted_new = quote_column_name(new_column)
if Database.postgresql? if Database.postgresql?
install_rename_triggers_for_postgresql(trigger_name, quoted_table, install_rename_triggers_for_postgresql(trigger_name, quoted_table,
...@@ -397,13 +414,6 @@ module Gitlab ...@@ -397,13 +414,6 @@ module Gitlab
install_rename_triggers_for_mysql(trigger_name, quoted_table, install_rename_triggers_for_mysql(trigger_name, quoted_table,
quoted_old, quoted_new) quoted_old, quoted_new)
end end
update_column_in_batches(table, new, Arel::Table.new(table)[old])
change_column_null(table, new, false) unless old_col.null
copy_indexes(table, old, new)
copy_foreign_keys(table, old, new)
end end
# Changes the type of a column concurrently. # Changes the type of a column concurrently.
...@@ -455,6 +465,97 @@ module Gitlab ...@@ -455,6 +465,97 @@ module Gitlab
remove_column(table, old) remove_column(table, old)
end end
# Changes the column type of a table using a background migration.
#
# Because this method uses a background migration it's more suitable for
# large tables. For small tables it's better to use
# `change_column_type_concurrently` since it can complete its work in a
# much shorter amount of time and doesn't rely on Sidekiq.
#
# Example usage:
#
# class Issue < ActiveRecord::Base
# self.table_name = 'issues'
#
# include EachBatch
#
# def self.to_migrate
# where('closed_at IS NOT NULL')
# end
# end
#
# change_column_type_using_background_migration(
# Issue.to_migrate,
# :closed_at,
# :datetime_with_timezone
# )
#
# Reverting a migration like this is done exactly the same way, just with
# a different type to migrate to (e.g. `:datetime` in the above example).
#
# relation - An ActiveRecord relation to use for scheduling jobs and
# figuring out what table we're modifying. This relation _must_
# have the EachBatch module included.
#
# column - The name of the column for which the type will be changed.
#
# new_type - The new type of the column.
#
# batch_size - The number of rows to schedule in a single background
# migration.
#
# interval - The time interval between every background migration.
def change_column_type_using_background_migration(
relation,
column,
new_type,
batch_size: 10_000,
interval: 10.minutes
)
unless relation.model < EachBatch
raise TypeError, 'The relation must include the EachBatch module'
end
temp_column = "#{column}_for_type_change"
table = relation.table_name
max_index = 0
add_column(table, temp_column, new_type)
install_rename_triggers(table, column, temp_column)
# Schedule the jobs that will copy the data from the old column to the
# new one.
relation.each_batch(of: batch_size) do |batch, index|
start_id, end_id = batch.pluck('MIN(id), MAX(id)').first
max_index = index
BackgroundMigrationWorker.perform_in(
index * interval,
'CopyColumn',
[table, column, temp_column, start_id, end_id]
)
end
# Schedule the renaming of the column to happen (initially) 1 hour after
# the last batch finished.
BackgroundMigrationWorker.perform_in(
(max_index * interval) + 1.hour,
'CleanupConcurrentTypeChange',
[table, column, temp_column]
)
if perform_background_migration_inline?
# To ensure the schema is up to date immediately we perform the
# migration inline in dev / test environments.
Gitlab::BackgroundMigration.steal('CopyColumn')
Gitlab::BackgroundMigration.steal('CleanupConcurrentTypeChange')
end
end
def perform_background_migration_inline?
Rails.env.test? || Rails.env.development?
end
# Performs a concurrent column rename when using PostgreSQL. # Performs a concurrent column rename when using PostgreSQL.
def install_rename_triggers_for_postgresql(trigger, table, old, new) def install_rename_triggers_for_postgresql(trigger, table, old, new)
execute <<-EOF.strip_heredoc execute <<-EOF.strip_heredoc
......
...@@ -116,8 +116,10 @@ module Gitlab ...@@ -116,8 +116,10 @@ module Gitlab
new_content_sha || old_content_sha new_content_sha || old_content_sha
end end
# Use #itself to check the value wrapped by a BatchLoader instance, rather
# than if the BatchLoader instance itself is falsey.
def blob def blob
new_blob || old_blob new_blob&.itself || old_blob&.itself
end end
attr_writer :highlighted_diff_lines attr_writer :highlighted_diff_lines
...@@ -173,7 +175,7 @@ module Gitlab ...@@ -173,7 +175,7 @@ module Gitlab
end end
def binary? def binary?
has_binary_notice? || old_blob&.binary? || new_blob&.binary? has_binary_notice? || try_blobs(:binary?)
end end
def text? def text?
...@@ -181,15 +183,15 @@ module Gitlab ...@@ -181,15 +183,15 @@ module Gitlab
end end
def external_storage_error? def external_storage_error?
old_blob&.external_storage_error? || new_blob&.external_storage_error? try_blobs(:external_storage_error?)
end end
def stored_externally? def stored_externally?
old_blob&.stored_externally? || new_blob&.stored_externally? try_blobs(:stored_externally?)
end end
def external_storage def external_storage
old_blob&.external_storage || new_blob&.external_storage try_blobs(:external_storage)
end end
def content_changed? def content_changed?
...@@ -204,15 +206,15 @@ module Gitlab ...@@ -204,15 +206,15 @@ module Gitlab
end end
def size def size
[old_blob&.size, new_blob&.size].compact.sum valid_blobs.map(&:size).sum
end end
def raw_size def raw_size
[old_blob&.raw_size, new_blob&.raw_size].compact.sum valid_blobs.map(&:raw_size).sum
end end
def raw_binary? def raw_binary?
old_blob&.raw_binary? || new_blob&.raw_binary? try_blobs(:raw_binary?)
end end
def raw_text? def raw_text?
...@@ -235,6 +237,19 @@ module Gitlab ...@@ -235,6 +237,19 @@ module Gitlab
private private
# The blob instances are instances of BatchLoader, which means calling
# &. directly on them won't work. Object#try also won't work, because Blob
# doesn't inherit from Object, but from BasicObject (via SimpleDelegator).
def try_blobs(meth)
old_blob&.itself&.public_send(meth) || new_blob&.itself&.public_send(meth)
end
# We can't use #compact for the same reason we can't use &., but calling
# #nil? explicitly does work because it is proxied to the blob itself.
def valid_blobs
[old_blob, new_blob].reject(&:nil?)
end
def text_position_properties(line) def text_position_properties(line)
{ old_line: line.old_line, new_line: line.new_line } { old_line: line.old_line, new_line: line.new_line }
end end
......
...@@ -34,13 +34,8 @@ module Gitlab ...@@ -34,13 +34,8 @@ module Gitlab
def rugged_stats(commit) def rugged_stats(commit)
diff = commit.rugged_diff_from_parent diff = commit.rugged_diff_from_parent
_files_changed, @additions, @deletions = diff.stat
diff.each_patch do |p| @total = @additions + @deletions
# TODO: Use the new Rugged convenience methods when they're released
@additions += p.stat[0]
@deletions += p.stat[1]
@total += p.changes
end
end end
end end
end end
......
...@@ -2,6 +2,9 @@ module Gitlab ...@@ -2,6 +2,9 @@ module Gitlab
module Git module Git
class GitlabProjects class GitlabProjects
include Gitlab::Git::Popen include Gitlab::Git::Popen
include Gitlab::Utils::StrongMemoize
ShardNameNotFoundError = Class.new(StandardError)
# Absolute path to directory where repositories are stored. # Absolute path to directory where repositories are stored.
# Example: /home/git/repositories # Example: /home/git/repositories
...@@ -97,22 +100,13 @@ module Gitlab ...@@ -97,22 +100,13 @@ module Gitlab
end end
def fork_repository(new_shard_path, new_repository_relative_path) def fork_repository(new_shard_path, new_repository_relative_path)
from_path = repository_absolute_path Gitlab::GitalyClient.migrate(:fork_repository) do |is_enabled|
to_path = File.join(new_shard_path, new_repository_relative_path) if is_enabled
gitaly_fork_repository(new_shard_path, new_repository_relative_path)
# The repository cannot already exist else
if File.exist?(to_path) git_fork_repository(new_shard_path, new_repository_relative_path)
logger.error "fork-repository failed: destination repository <#{to_path}> already exists." end
return false
end end
# Ensure the namepsace / hashed storage directory exists
FileUtils.mkdir_p(File.dirname(to_path), mode: 0770)
logger.info "Forking repository from <#{from_path}> to <#{to_path}>."
cmd = %W(git clone --bare --no-local -- #{from_path} #{to_path})
run(cmd, nil) && Gitlab::Git::Repository.create_hooks(to_path, global_hooks_path)
end end
def fetch_remote(name, timeout, force:, tags:, ssh_key: nil, known_hosts: nil) def fetch_remote(name, timeout, force:, tags:, ssh_key: nil, known_hosts: nil)
...@@ -253,6 +247,48 @@ module Gitlab ...@@ -253,6 +247,48 @@ module Gitlab
known_hosts_file&.close! known_hosts_file&.close!
script&.close! script&.close!
end end
private
def shard_name
strong_memoize(:shard_name) do
shard_name_from_shard_path(shard_path)
end
end
def shard_name_from_shard_path(shard_path)
Gitlab.config.repositories.storages.find { |_, info| info['path'] == shard_path }&.first ||
raise(ShardNameNotFoundError, "no shard found for path '#{shard_path}'")
end
def git_fork_repository(new_shard_path, new_repository_relative_path)
from_path = repository_absolute_path
to_path = File.join(new_shard_path, new_repository_relative_path)
# The repository cannot already exist
if File.exist?(to_path)
logger.error "fork-repository failed: destination repository <#{to_path}> already exists."
return false
end
# Ensure the namepsace / hashed storage directory exists
FileUtils.mkdir_p(File.dirname(to_path), mode: 0770)
logger.info "Forking repository from <#{from_path}> to <#{to_path}>."
cmd = %W(git clone --bare --no-local -- #{from_path} #{to_path})
run(cmd, nil) && Gitlab::Git::Repository.create_hooks(to_path, global_hooks_path)
end
def gitaly_fork_repository(new_shard_path, new_repository_relative_path)
target_repository = Gitlab::Git::Repository.new(shard_name_from_shard_path(new_shard_path), new_repository_relative_path, nil)
raw_repository = Gitlab::Git::Repository.new(shard_name, repository_relative_path, nil)
Gitlab::GitalyClient::RepositoryService.new(target_repository).fork_repository(raw_repository)
rescue GRPC::BadStatus => e
logger.error "fork-repository failed: #{e.message}"
false
end
end end
end end
end end
...@@ -1292,6 +1292,15 @@ module Gitlab ...@@ -1292,6 +1292,15 @@ module Gitlab
success || gitlab_projects_error success || gitlab_projects_error
end end
<<<<<<< HEAD
=======
def delete_remote_branches(remote_name, branch_names)
success = @gitlab_projects.delete_remote_branches(remote_name, branch_names)
success || gitlab_projects_error
end
>>>>>>> upstream/master
def gitaly_repository def gitaly_repository
Gitlab::GitalyClient::Util.repository(@storage, @relative_path, @gl_repository) Gitlab::GitalyClient::Util.repository(@storage, @relative_path, @gl_repository)
end end
...@@ -1691,6 +1700,7 @@ module Gitlab ...@@ -1691,6 +1700,7 @@ module Gitlab
cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} rev-list] cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} rev-list]
cmd << "--after=#{options[:after].iso8601}" if options[:after] cmd << "--after=#{options[:after].iso8601}" if options[:after]
cmd << "--before=#{options[:before].iso8601}" if options[:before] cmd << "--before=#{options[:before].iso8601}" if options[:before]
cmd << "--max-count=#{options[:max_count]}" if options[:max_count]
cmd += %W[--count #{options[:ref]}] cmd += %W[--count #{options[:ref]}]
cmd += %W[-- #{options[:path]}] if options[:path].present? cmd += %W[-- #{options[:path]}] if options[:path].present?
......
...@@ -130,6 +130,7 @@ module Gitlab ...@@ -130,6 +130,7 @@ module Gitlab
request.after = Google::Protobuf::Timestamp.new(seconds: options[:after].to_i) if options[:after].present? request.after = Google::Protobuf::Timestamp.new(seconds: options[:after].to_i) if options[:after].present?
request.before = Google::Protobuf::Timestamp.new(seconds: options[:before].to_i) if options[:before].present? request.before = Google::Protobuf::Timestamp.new(seconds: options[:before].to_i) if options[:before].present?
request.path = options[:path] if options[:path].present? request.path = options[:path] if options[:path].present?
request.max_count = options[:max_count] if options[:max_count].present?
GitalyClient.call(@repository.storage, :commit_service, :count_commits, request, timeout: GitalyClient.medium_timeout).count GitalyClient.call(@repository.storage, :commit_service, :count_commits, request, timeout: GitalyClient.medium_timeout).count
end end
......
...@@ -101,6 +101,7 @@ module Gitlab ...@@ -101,6 +101,7 @@ module Gitlab
request_enum.push(Gitaly::UserMergeBranchRequest.new(apply: true)) request_enum.push(Gitaly::UserMergeBranchRequest.new(apply: true))
branch_update = response_enum.next.branch_update branch_update = response_enum.next.branch_update
return if branch_update.nil?
raise Gitlab::Git::CommitError.new('failed to apply merge to branch') unless branch_update.commit_id.present? raise Gitlab::Git::CommitError.new('failed to apply merge to branch') unless branch_update.commit_id.present?
Gitlab::Git::OperationService::BranchUpdate.from_gitaly(branch_update) Gitlab::Git::OperationService::BranchUpdate.from_gitaly(branch_update)
......
...@@ -81,6 +81,22 @@ module Gitlab ...@@ -81,6 +81,22 @@ module Gitlab
response.base.presence response.base.presence
end end
def fork_repository(source_repository)
request = Gitaly::CreateForkRequest.new(
repository: @gitaly_repo,
source_repository: source_repository.gitaly_repository
)
GitalyClient.call(
@storage,
:repository_service,
:create_fork,
request,
remote_storage: source_repository.storage,
timeout: GitalyClient.default_timeout
)
end
def fetch_source_branch(source_repository, source_branch, local_ref) def fetch_source_branch(source_repository, source_branch, local_ref)
request = Gitaly::FetchSourceBranchRequest.new( request = Gitaly::FetchSourceBranchRequest.new(
repository: @gitaly_repo, repository: @gitaly_repo,
......
...@@ -12,7 +12,7 @@ module Gitlab ...@@ -12,7 +12,7 @@ module Gitlab
Gitaly::Repository.new( Gitaly::Repository.new(
storage_name: repository_storage, storage_name: repository_storage,
relative_path: relative_path, relative_path: relative_path,
gl_repository: gl_repository, gl_repository: gl_repository.to_s,
git_object_directory: git_object_directory.to_s, git_object_directory: git_object_directory.to_s,
git_alternate_object_directories: git_alternate_object_directories git_alternate_object_directories: git_alternate_object_directories
) )
......
...@@ -7,6 +7,7 @@ module Gitlab ...@@ -7,6 +7,7 @@ module Gitlab
module ImportSources module ImportSources
ImportSource = Struct.new(:name, :title, :importer) ImportSource = Struct.new(:name, :title, :importer)
# We exclude `bare_repository` here as it has no import class associated
ImportTable = [ ImportTable = [
ImportSource.new('github', 'GitHub', Gitlab::GithubImport::ParallelImporter), ImportSource.new('github', 'GitHub', Gitlab::GithubImport::ParallelImporter),
ImportSource.new('bitbucket', 'Bitbucket', Gitlab::BitbucketImport::Importer), ImportSource.new('bitbucket', 'Bitbucket', Gitlab::BitbucketImport::Importer),
......
...@@ -8,7 +8,7 @@ end ...@@ -8,7 +8,7 @@ end
module Gitlab module Gitlab
class Seeder class Seeder
def self.quiet def self.quiet
mute_mailer unless Rails.env.test? mute_mailer
SeedFu.quiet = true SeedFu.quiet = true
......
...@@ -12,13 +12,13 @@ module QA ...@@ -12,13 +12,13 @@ module QA
end end
def ssh_key def ssh_key
<<~KEY.tr("\n", '') <<~KEY.delete("\n")
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFf6RYK3qu/RKF/3ndJmL5xgMLp3O9 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFf6RYK3qu/RKF/3ndJmL5xgMLp3O9
6x8lTay+QGZ0+9FnnAXMdUqBq/ZU6d/gyMB4IaW3nHzM1w049++yAB6UPCzMB8Uo27K5 6x8lTay+QGZ0+9FnnAXMdUqBq/ZU6d/gyMB4IaW3nHzM1w049++yAB6UPCzMB8Uo27K5
/jyZCtj7Vm9PFNjF/8am1kp46c/SeYicQgQaSBdzIW3UDEa1Ef68qroOlvpi9PYZ/tA7 /jyZCtj7Vm9PFNjF/8am1kp46c/SeYicQgQaSBdzIW3UDEa1Ef68qroOlvpi9PYZ/tA7
M0YP0K5PXX+E36zaIRnJVMPT3f2k+GnrxtjafZrwFdpOP/Fol5BQLBgcsyiU+LM1SuaC M0YP0K5PXX+E36zaIRnJVMPT3f2k+GnrxtjafZrwFdpOP/Fol5BQLBgcsyiU+LM1SuaC
rzd8c9vyaTA1CxrkxaZh+buAi0PmdDtaDrHd42gqZkXCKavyvgM5o2CkQ5LJHCgzpXy0 rzd8c9vyaTA1CxrkxaZh+buAi0PmdDtaDrHd42gqZkXCKavyvgM5o2CkQ5LJHCgzpXy0
5qNFzmThBSkb+XtoxbyagBiGbVZtSVow6Xa7qewz= dummy@gitlab.com 5qNFzmThBSkb+XtoxbyagBiGbVZtSVow6Xa7qewz= dummy@gitlab.com
KEY KEY
end end
end end
......
#!/bin/sh
# Check if file exists with -f. Check if in in the gdk rook directory.
if [ ! -f ../GDK_ROOT ]; then
echo "Please run script from gitlab (e.g. gitlab-development-kit/gitlab) root directory."
exit 1
fi
PRECOMMIT=$(git rev-parse --git-dir)/hooks/pre-commit
# Check if symlink exists with -L. Check if script was already installed.
if [ -L $PRECOMMIT ]; then
echo "Pre-commit script already installed."
exit 1
fi
ln -s ./pre-commit $PRECOMMIT
echo "Pre-commit script installed successfully"
#!/bin/bash #!/bin/bash
mysql --user=root --host=mysql <<EOF mysql --user=root --host=mysql <<EOF
CREATE DATABASE IF NOT EXISTS gitlabhq_test; CREATE DATABASE IF NOT EXISTS gitlabhq_test DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER IF NOT EXISTS 'gitlab'@'%'; CREATE USER IF NOT EXISTS 'gitlab'@'%';
GRANT ALL PRIVILEGES ON gitlabhq_test.* TO 'gitlab'@'%'; GRANT ALL PRIVILEGES ON gitlabhq_test.* TO 'gitlab'@'%';
FLUSH PRIVILEGES; FLUSH PRIVILEGES;
......
#!/bin/sh
# Check if file exists with -f. Check if in in the gdk rook directory.
if [ ! -f ../GDK_ROOT ]; then
echo "Please run pre-commit from gitlab (e.g. gitlab-development-kit/gitlab) root directory."
exit 1
fi
jsfiles=$(git diff --cached --name-only --diff-filter=ACM "*.js" | tr '\n' ' ')
[ -z "$jsfiles" ] && exit 0
# Prettify all staged .js files
echo "$jsfiles" | xargs ./node_modules/.bin/prettier --write
# Add back the modified/prettified files to staging
echo "$jsfiles" | xargs git add
exit 0
...@@ -115,6 +115,7 @@ describe Projects::ArtifactsController do ...@@ -115,6 +115,7 @@ describe Projects::ArtifactsController do
context 'when the file exists' do context 'when the file exists' do
let(:path) { 'ci_artifacts.txt' } let(:path) { 'ci_artifacts.txt' }
<<<<<<< HEAD
shared_examples 'a valid file' do shared_examples 'a valid file' do
it 'serves the file using workhorse' do it 'serves the file using workhorse' do
...@@ -142,12 +143,42 @@ describe Projects::ArtifactsController do ...@@ -142,12 +143,42 @@ describe Projects::ArtifactsController do
end end
end end
=======
shared_examples 'a valid file' do
it 'serves the file using workhorse' do
subject
expect(response).to have_gitlab_http_status(200)
expect(send_data).to start_with('artifacts-entry:')
expect(params.keys).to eq(%w(Archive Entry))
expect(params['Archive']).to start_with(archive_path)
# On object storage, the URL can end with a query string
expect(params['Archive']).to match(/build_artifacts.zip(\?[^?]+)?$/)
expect(params['Entry']).to eq(Base64.encode64('ci_artifacts.txt'))
end
def send_data
response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]
end
def params
@params ||= begin
base64_params = send_data.sub(/\Aartifacts\-entry:/, '')
JSON.parse(Base64.urlsafe_decode64(base64_params))
end
end
end
>>>>>>> upstream/master
context 'when using local file storage' do context 'when using local file storage' do
it_behaves_like 'a valid file' do it_behaves_like 'a valid file' do
let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
let(:store) { ObjectStoreUploader::LOCAL_STORE } let(:store) { ObjectStoreUploader::LOCAL_STORE }
let(:archive_path) { JobArtifactUploader.local_store_path } let(:archive_path) { JobArtifactUploader.local_store_path }
end end
<<<<<<< HEAD
end end
## EE specific begins ## EE specific begins
...@@ -162,6 +193,8 @@ describe Projects::ArtifactsController do ...@@ -162,6 +193,8 @@ describe Projects::ArtifactsController do
let(:store) { ObjectStoreUploader::REMOTE_STORE } let(:store) { ObjectStoreUploader::REMOTE_STORE }
let(:archive_path) { 'https://' } let(:archive_path) { 'https://' }
end end
=======
>>>>>>> upstream/master
end end
## EE specific ends ## EE specific ends
end end
......
...@@ -21,12 +21,14 @@ FactoryBot.define do ...@@ -21,12 +21,14 @@ FactoryBot.define do
factory :rsa_key_2048 do factory :rsa_key_2048 do
key do key do
'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFf6RYK3qu/RKF/3ndJmL5xgMLp3O9' \ <<~KEY.delete("\n")
'6x8lTay+QGZ0+9FnnAXMdUqBq/ZU6d/gyMB4IaW3nHzM1w049++yAB6UPCzMB8Uo27K5' \ ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFf6RYK3qu/RKF/3ndJmL5xgMLp3O9
'/jyZCtj7Vm9PFNjF/8am1kp46c/SeYicQgQaSBdzIW3UDEa1Ef68qroOlvpi9PYZ/tA7' \ 6x8lTay+QGZ0+9FnnAXMdUqBq/ZU6d/gyMB4IaW3nHzM1w049++yAB6UPCzMB8Uo27K5
'M0YP0K5PXX+E36zaIRnJVMPT3f2k+GnrxtjafZrwFdpOP/Fol5BQLBgcsyiU+LM1SuaC' \ /jyZCtj7Vm9PFNjF/8am1kp46c/SeYicQgQaSBdzIW3UDEa1Ef68qroOlvpi9PYZ/tA7
'rzd8c9vyaTA1CxrkxaZh+buAi0PmdDtaDrHd42gqZkXCKavyvgM5o2CkQ5LJHCgzpXy0' \ M0YP0K5PXX+E36zaIRnJVMPT3f2k+GnrxtjafZrwFdpOP/Fol5BQLBgcsyiU+LM1SuaC
'5qNFzmThBSkb+XtoxbyagBiGbVZtSVow6Xa7qewz= dummy@gitlab.com' rzd8c9vyaTA1CxrkxaZh+buAi0PmdDtaDrHd42gqZkXCKavyvgM5o2CkQ5LJHCgzpXy0
5qNFzmThBSkb+XtoxbyagBiGbVZtSVow6Xa7qewz= dummy@gitlab.com
KEY
end end
factory :rsa_deploy_key_2048, class: 'DeployKey' factory :rsa_deploy_key_2048, class: 'DeployKey'
...@@ -34,37 +36,44 @@ FactoryBot.define do ...@@ -34,37 +36,44 @@ FactoryBot.define do
factory :dsa_key_2048 do factory :dsa_key_2048 do
key do key do
'ssh-dss AAAAB3NzaC1kc3MAAAEBAO/3/NPLA/zSFkMOCaTtGo+uos1flfQ5f038Uk+G' \ <<~KEY.delete("\n")
'Y9AeLGzX+Srhw59GdVXmOQLYBrOt5HdGwqYcmLnE2VurUGmhtfeO5H+3p5pGJbkS0Gxp' \ ssh-dss AAAAB3NzaC1kc3MAAAEBAO/3/NPLA/zSFkMOCaTtGo+uos1flfQ5f038Uk+G
'YH1HRO9lWsncF3Hh1w4lYsDjkclDiSTdfTuN8F4Kb3DXNnVSCieeonp+B25F/CXagyTQ' \ Y9AeLGzX+Srhw59GdVXmOQLYBrOt5HdGwqYcmLnE2VurUGmhtfeO5H+3p5pGJbkS0Gxp
'/pvNmHFeYgGCVdnBtFdi+xfxaZ8NKdPrGggzokbKHElDZQ4Xo5EpdcyLajgM7nB2r2Rz' \ YH1HRO9lWsncF3Hh1w4lYsDjkclDiSTdfTuN8F4Kb3DXNnVSCieeonp+B25F/CXagyTQ
'OrmeaevKi5lV68ehRa9Yyrb7vxvwiwBwOgqR/mnN7Gnaq1jUdmJY+ct04Qwx37f5jvhv' \ /pvNmHFeYgGCVdnBtFdi+xfxaZ8NKdPrGggzokbKHElDZQ4Xo5EpdcyLajgM7nB2r2Rz
'5gA4U40SGMoiHM8RFIN7Ksz0jsyX73MAAAAVALRWOfjfzHpK7KLz4iqDvvTUAevJAAAB' \ OrmeaevKi5lV68ehRa9Yyrb7vxvwiwBwOgqR/mnN7Gnaq1jUdmJY+ct04Qwx37f5jvhv
'AEa9NZ+6y9iQ5erGsdfLTXFrhSefTG0NhghoO/5IFkSGfd8V7kzTvCHaFrcfpEA5kP8t' \ 5gA4U40SGMoiHM8RFIN7Ksz0jsyX73MAAAAVALRWOfjfzHpK7KLz4iqDvvTUAevJAAAB
'poeOG0TASB6tgGOxm1Bq4Wncry5RORBPJlAVpDGRcvZ931ddH7IgltEInS6za2uH6F/1' \ AEa9NZ+6y9iQ5erGsdfLTXFrhSefTG0NhghoO/5IFkSGfd8V7kzTvCHaFrcfpEA5kP8t
'M1QfKePSLr6xJ1ZLYfP0Og5KTp1x6yMQvfwV0a+XdA+EPgaJWLWp/pWwKWa0oLUgjsIH' \ poeOG0TASB6tgGOxm1Bq4Wncry5RORBPJlAVpDGRcvZ931ddH7IgltEInS6za2uH6F/1
'MYzuOGh5c708uZrmkzqvgtW2NgXhcIroRgynT3IfI2lP2rqqb3uuuE/qH5UCUFO+Dc3H' \ M1QfKePSLr6xJ1ZLYfP0Og5KTp1x6yMQvfwV0a+XdA+EPgaJWLWp/pWwKWa0oLUgjsIH
'nAFNeQDT/M25AERdPYBAY5a+iPjIgO+jT7BfmfByT+AZTqZySrCyc7nNZL3YgGLK0l6A' \ MYzuOGh5c708uZrmkzqvgtW2NgXhcIroRgynT3IfI2lP2rqqb3uuuE/qH5UCUFO+Dc3H
'1GgAAAEBAN9FpFOdIXE+YEZhKl1vPmbcn+b1y5zOl6N4x1B7Q8pD/pLMziWROIS8uLzb' \ nAFNeQDT/M25AERdPYBAY5a+iPjIgO+jT7BfmfByT+AZTqZySrCyc7nNZL3YgGLK0l6A
'aZ0sMIWezHIkxuo1iROMeT+jtCubn7ragaN6AX7nMpxYUH9+mYZZs/fyElt6wCviVhTI' \ 1GgAAAEBAN9FpFOdIXE+YEZhKl1vPmbcn+b1y5zOl6N4x1B7Q8pD/pLMziWROIS8uLzb
'zM+u7VdQsnZttOOlQfogHdL+SpeAft0DsfJjlcgQnsLlHQKv6aPqCPYUST2nE7RyW/Ex' \ aZ0sMIWezHIkxuo1iROMeT+jtCubn7ragaN6AX7nMpxYUH9+mYZZs/fyElt6wCviVhTI
'PrMxLtOWt0/j8RYHbwwqvyeZqBz3ESBgrS9c5tBdBfauwYUV/E7gPLOU3OZFw9ue7o+z' \ zM+u7VdQsnZttOOlQfogHdL+SpeAft0DsfJjlcgQnsLlHQKv6aPqCPYUST2nE7RyW/Ex
'wzoTZqW6Xouy5wtWvSLQSLT5XwOslmQz8QMBxD0AQyDfEFGsBCWzmbTgKv9uqrBjubsS' \ PrMxLtOWt0/j8RYHbwwqvyeZqBz3ESBgrS9c5tBdBfauwYUV/E7gPLOU3OZFw9ue7o+z
'Taja+Cf9kMo== dummy@gitlab.com' wzoTZqW6Xouy5wtWvSLQSLT5XwOslmQz8QMBxD0AQyDfEFGsBCWzmbTgKv9uqrBjubsS
Taja+Cf9kMo== dummy@gitlab.com
KEY
end end
end end
factory :ecdsa_key_256 do factory :ecdsa_key_256 do
key do key do
'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYA' \ <<~KEY.delete("\n")
'AABBBJZmkzTgY0fiCQ+DVReyH/fFwTFz0XoR3RUO0u+199H19KFw7mNPxRSMOVS7tEtO' \ ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYA
'Nj3Q7FcZXfqthHvgAzDiHsc= dummy@gitlab.com' AABBBJZmkzTgY0fiCQ+DVReyH/fFwTFz0XoR3RUO0u+199H19KFw7mNPxRSMOVS7tEtO
Nj3Q7FcZXfqthHvgAzDiHsc= dummy@gitlab.com
KEY
end end
end end
factory :ed25519_key_256 do factory :ed25519_key_256 do
key do key do
'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETnVTgzqC1gatgSlC4zH6aYt2CAQzgJOhDRvf59ohL6 dummy@gitlab.com' <<~KEY.delete("\n")
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETnVTgzqC1gatgSlC4zH6aYt2CAQzgJ
OhDRvf59ohL6 dummy@gitlab.com
KEY
end end
end end
end end
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment