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

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2017-11-29

# Conflicts:
#	app/assets/javascripts/repo/components/repo_tab.vue
#	app/models/application_setting.rb
#	app/models/ci/runner.rb
#	app/services/merge_requests/build_service.rb
#	app/services/projects/hashed_storage/migrate_attachments_service.rb
#	app/services/projects/hashed_storage/migrate_repository_service.rb
#	app/services/projects/update_service.rb
#	db/schema.rb
#	lib/gitlab/gitaly_client.rb
#	spec/services/projects/update_service_spec.rb

[ci skip]
parents 891acf9d 7bdcd6f3
...@@ -1191,7 +1191,7 @@ DEPENDENCIES ...@@ -1191,7 +1191,7 @@ DEPENDENCIES
sanitize (~> 2.0) sanitize (~> 2.0)
sass-rails (~> 5.0.6) sass-rails (~> 5.0.6)
scss_lint (~> 0.54.0) scss_lint (~> 0.54.0)
seed-fu (~> 2.3.5) seed-fu (~> 2.3.7)
select2-rails (~> 3.5.9) select2-rails (~> 3.5.9)
selenium-webdriver (~> 3.5) selenium-webdriver (~> 3.5)
sentry-raven (~> 2.5.3) sentry-raven (~> 2.5.3)
......
...@@ -424,6 +424,7 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -424,6 +424,7 @@ import initGroupAnalytics from './init_group_analytics';
projectImport(); projectImport();
break; break;
case 'projects:pipelines:new': case 'projects:pipelines:new':
case 'projects:pipelines:create':
new NewBranchForm($('.js-new-pipeline-form')); new NewBranchForm($('.js-new-pipeline-form'));
break; break;
case 'projects:pipelines:builds': case 'projects:pipelines:builds':
......
...@@ -190,7 +190,7 @@ export const insertText = (target, text) => { ...@@ -190,7 +190,7 @@ export const insertText = (target, text) => {
target.selectionStart = target.selectionEnd = selectionStart + insertedText.length; target.selectionStart = target.selectionEnd = selectionStart + insertedText.length;
// Trigger autosave // Trigger autosave
$(target).trigger('input'); target.dispatchEvent(new Event('input'));
// Trigger autosize // Trigger autosize
const event = document.createEvent('Event'); const event = document.createEvent('Event');
......
...@@ -48,6 +48,27 @@ export default { ...@@ -48,6 +48,27 @@ export default {
} }
return this.projectName; return this.projectName;
}, },
/**
* Smartly truncates project namespace by doing two things;
* 1. Only include Group names in path by removing project name
* 2. Only include first and last group names in the path
* when namespace has more than 2 groups present
*
* First part (removal of project name from namespace) can be
* done from backend but doing so involves migration of
* existing project namespaces which is not wise thing to do.
*/
truncatedNamespace() {
const namespaceArr = this.namespace.split(' / ');
namespaceArr.splice(-1, 1);
let namespace = namespaceArr.join(' / ');
if (namespaceArr.length > 2) {
namespace = `${namespaceArr[0]} / ... / ${namespaceArr.pop()}`;
}
return namespace;
},
}, },
}; };
</script> </script>
...@@ -87,9 +108,7 @@ export default { ...@@ -87,9 +108,7 @@ export default {
<div <div
class="project-namespace" class="project-namespace"
:title="namespace" :title="namespace"
> >{{truncatedNamespace}}</div>
{{namespace}}
</div>
</div> </div>
</a> </a>
</li> </li>
......
<script>
import icon from '../../../vue_shared/components/icon.vue';
import listItem from './list_item.vue';
import listCollapsed from './list_collapsed.vue';
export default {
components: {
icon,
listItem,
listCollapsed,
},
props: {
title: {
type: String,
required: true,
},
fileList: {
type: Array,
required: true,
},
collapsed: {
type: Boolean,
required: true,
},
},
methods: {
toggleCollapsed() {
this.$emit('toggleCollapsed');
},
},
};
</script>
<template>
<div class="multi-file-commit-panel-section">
<header
class="multi-file-commit-panel-header"
:class="{
'is-collapsed': collapsed,
}"
>
<icon
name="list-bulleted"
:size="18"
css-classes="append-right-default"
/>
<template v-if="!collapsed">
{{ title }}
<button
type="button"
class="btn btn-transparent multi-file-commit-panel-collapse-btn"
@click="toggleCollapsed"
>
<i
aria-hidden="true"
class="fa fa-angle-double-right"
>
</i>
</button>
</template>
</header>
<div class="multi-file-commit-list">
<list-collapsed
v-if="collapsed"
/>
<template v-else>
<ul
v-if="fileList.length"
class="list-unstyled append-bottom-0"
>
<li
v-for="file in fileList"
:key="file.key"
>
<list-item
:file="file"
/>
</li>
</ul>
<div
v-else
class="help-block prepend-top-0"
>
No changes
</div>
</template>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import icon from '../../../vue_shared/components/icon.vue';
export default {
components: {
icon,
},
computed: {
...mapGetters([
'addedFiles',
'modifiedFiles',
]),
},
};
</script>
<template>
<div
class="multi-file-commit-list-collapsed text-center"
>
<icon
name="file-addition"
:size="18"
css-classes="multi-file-addition append-bottom-10"
/>
{{ addedFiles.length }}
<icon
name="file-modified"
:size="18"
css-classes="multi-file-modified prepend-top-10 append-bottom-10"
/>
{{ modifiedFiles.length }}
</div>
</template>
<script>
import icon from '../../../vue_shared/components/icon.vue';
export default {
components: {
icon,
},
props: {
file: {
type: Object,
required: true,
},
},
computed: {
iconName() {
return this.file.tempFile ? 'file-addition' : 'file-modified';
},
iconClass() {
return `multi-file-${this.file.tempFile ? 'addition' : 'modified'} append-right-8`;
},
},
};
</script>
<template>
<div class="multi-file-commit-list-item">
<icon
:name="iconName"
:size="16"
:css-classes="iconClass"
/>
<span class="multi-file-commit-list-path">
{{ file.path }}
</span>
</div>
</template>
...@@ -40,20 +40,24 @@ export default { ...@@ -40,20 +40,24 @@ export default {
</script> </script>
<template> <template>
<div class="repository-view"> <div
<div class="tree-content-holder" :class="{'tree-content-holder-mini' : isCollapsed}"> class="multi-file"
<repo-sidebar/> :class="{
<div 'is-collapsed': isCollapsed
v-if="isCollapsed" }"
class="panel-right" >
> <repo-sidebar/>
<repo-tabs/> <div
<component v-if="isCollapsed"
:is="currentBlobView" class="multi-file-edit-pane"
/> >
<repo-file-buttons/> <repo-tabs />
</div> <component
class="multi-file-edit-pane-content"
:is="currentBlobView"
/>
<repo-file-buttons />
</div> </div>
<repo-commit-section v-if="changedFiles.length" /> <repo-commit-section />
</div> </div>
</template> </template>
<script> <script>
import { mapGetters, mapState, mapActions } from 'vuex'; import { mapGetters, mapState, mapActions } from 'vuex';
import tooltip from '../../vue_shared/directives/tooltip';
import icon from '../../vue_shared/components/icon.vue';
import PopupDialog from '../../vue_shared/components/popup_dialog.vue'; import PopupDialog from '../../vue_shared/components/popup_dialog.vue';
import { n__ } from '../../locale'; import commitFilesList from './commit_sidebar/list.vue';
export default { export default {
components: { components: {
PopupDialog, PopupDialog,
icon,
commitFilesList,
},
directives: {
tooltip,
}, },
data() { data() {
return { return {
...@@ -13,6 +20,7 @@ export default { ...@@ -13,6 +20,7 @@ export default {
submitCommitsLoading: false, submitCommitsLoading: false,
startNewMR: false, startNewMR: false,
commitMessage: '', commitMessage: '',
collapsed: true,
}; };
}, },
computed: { computed: {
...@@ -23,10 +31,10 @@ export default { ...@@ -23,10 +31,10 @@ export default {
'changedFiles', 'changedFiles',
]), ]),
commitButtonDisabled() { commitButtonDisabled() {
return !this.commitMessage || this.submitCommitsLoading; return this.commitMessage === '' || this.submitCommitsLoading || !this.changedFiles.length;
}, },
commitButtonText() { commitMessageCount() {
return n__('Commit %d file', 'Commit %d files', this.changedFiles.length); return this.commitMessage.length;
}, },
}, },
methods: { methods: {
...@@ -77,12 +85,20 @@ export default { ...@@ -77,12 +85,20 @@ export default {
this.submitCommitsLoading = false; this.submitCommitsLoading = false;
}); });
}, },
toggleCollapsed() {
this.collapsed = !this.collapsed;
},
}, },
}; };
</script> </script>
<template> <template>
<div id="commit-area"> <div
class="multi-file-commit-panel"
:class="{
'is-collapsed': collapsed,
}"
>
<popup-dialog <popup-dialog
v-if="showNewBranchDialog" v-if="showNewBranchDialog"
:primary-button-label="__('Create new branch')" :primary-button-label="__('Create new branch')"
...@@ -92,78 +108,71 @@ export default { ...@@ -92,78 +108,71 @@ export default {
@toggle="showNewBranchDialog = false" @toggle="showNewBranchDialog = false"
@submit="makeCommit(true)" @submit="makeCommit(true)"
/> />
<button
v-if="collapsed"
type="button"
class="btn btn-transparent multi-file-commit-panel-collapse-btn is-collapsed prepend-top-10 append-bottom-10"
@click="toggleCollapsed"
>
<i
aria-hidden="true"
class="fa fa-angle-double-left"
>
</i>
</button>
<commit-files-list
title="Staged"
:file-list="changedFiles"
:collapsed="collapsed"
@toggleCollapsed="toggleCollapsed"
/>
<form <form
class="form-horizontal" class="form-horizontal multi-file-commit-form"
@submit.prevent="tryCommit()"> @submit.prevent="tryCommit"
<fieldset> v-if="!collapsed"
<div class="form-group"> >
<label class="col-md-4 control-label staged-files"> <div class="multi-file-commit-fieldset">
Staged files ({{changedFiles.length}}) <textarea
</label> class="form-control multi-file-commit-message"
<div class="col-md-6"> name="commit-message"
<ul class="list-unstyled changed-files"> v-model="commitMessage"
<li placeholder="Commit message"
v-for="(file, index) in changedFiles" >
:key="index"> </textarea>
<span class="help-block"> </div>
{{ file.path }} <div class="multi-file-commit-fieldset">
</span> <label
</li> v-tooltip
</ul> title="Create a new merge request with these changes"
</div> data-container="body"
</div> data-placement="top"
<div class="form-group"> >
<label <input
class="col-md-4 control-label" type="checkbox"
for="commit-message"> v-model="startNewMR"
Commit message />
</label> Merge Request
<div class="col-md-6"> </label>
<textarea <button
id="commit-message" type="submit"
class="form-control" :disabled="commitButtonDisabled"
name="commit-message" class="btn btn-default btn-sm append-right-10 prepend-left-10"
v-model="commitMessage"> >
</textarea> <i
</div> v-if="submitCommitsLoading"
</div> class="js-commit-loading-icon fa fa-spinner fa-spin"
<div class="form-group target-branch"> aria-hidden="true"
<label aria-label="loading"
class="col-md-4 control-label" >
for="target-branch"> </i>
Target branch Commit
</label> </button>
<div class="col-md-6"> <div
<span class="help-block"> class="multi-file-commit-message-count"
{{currentBranch}} >
</span> {{ commitMessageCount }}
</div>
</div>
<div class="col-md-offset-4 col-md-6">
<button
type="submit"
:disabled="commitButtonDisabled"
class="btn btn-success">
<i
v-if="submitCommitsLoading"
class="js-commit-loading-icon fa fa-spinner fa-spin"
aria-hidden="true"
aria-label="loading">
</i>
<span class="commit-summary">
{{ commitButtonText }}
</span>
</button>
</div>
<div class="col-md-offset-4 col-md-6">
<div class="checkbox">
<label>
<input type="checkbox" v-model="startNewMR">
<span>Start a <strong>new merge request</strong> with these changes</span>
</label>
</div>
</div> </div>
</fieldset> </div>
</form> </form>
</div> </div>
</template> </template>
...@@ -57,7 +57,7 @@ ...@@ -57,7 +57,7 @@
class="file" class="file"
@click.prevent="clickedTreeRow(file)"> @click.prevent="clickedTreeRow(file)">
<td <td
class="multi-file-table-col-name" class="multi-file-table-name"
:colspan="submoduleColSpan" :colspan="submoduleColSpan"
> >
<i <i
...@@ -90,12 +90,11 @@ ...@@ -90,12 +90,11 @@
</td> </td>
<template v-if="!isCollapsed && !isSubmodule"> <template v-if="!isCollapsed && !isSubmodule">
<td class="hidden-sm hidden-xs"> <td class="multi-file-table-col-commit-message hidden-sm hidden-xs">
<a <a
v-if="file.lastCommit.message" v-if="file.lastCommit.message"
@click.stop @click.stop
:href="file.lastCommit.url" :href="file.lastCommit.url"
class="commit-message"
> >
{{ file.lastCommit.message }} {{ file.lastCommit.message }}
</a> </a>
......
...@@ -22,12 +22,12 @@ export default { ...@@ -22,12 +22,12 @@ export default {
<template> <template>
<div <div
v-if="showButtons" v-if="showButtons"
class="repo-file-buttons" class="multi-file-editor-btn-group"
> >
<a <a
:href="activeFile.rawPath" :href="activeFile.rawPath"
target="_blank" target="_blank"
class="btn btn-default raw" class="btn btn-default btn-sm raw"
rel="noopener noreferrer"> rel="noopener noreferrer">
{{ rawDownloadButtonLabel }} {{ rawDownloadButtonLabel }}
</a> </a>
...@@ -38,17 +38,17 @@ export default { ...@@ -38,17 +38,17 @@ export default {
aria-label="File actions"> aria-label="File actions">
<a <a
:href="activeFile.blamePath" :href="activeFile.blamePath"
class="btn btn-default blame"> class="btn btn-default btn-sm blame">
Blame Blame
</a> </a>
<a <a
:href="activeFile.commitsPath" :href="activeFile.commitsPath"
class="btn btn-default history"> class="btn btn-default btn-sm history">
History History
</a> </a>
<a <a
:href="activeFile.permalink" :href="activeFile.permalink"
class="btn btn-default permalink"> class="btn btn-default btn-sm permalink">
Permalink Permalink
</a> </a>
</div> </div>
......
...@@ -32,10 +32,12 @@ export default { ...@@ -32,10 +32,12 @@ export default {
</script> </script>
<template> <template>
<div class="blob-viewer-container"> <div>
<div <div
v-if="!activeFile.renderError" v-if="!activeFile.renderError"
v-html="activeFile.html"> v-html="activeFile.html"
class="multi-file-preview-holder"
>
</div> </div>
<div <div
v-else-if="activeFile.tempFile" v-else-if="activeFile.tempFile"
......
...@@ -44,20 +44,16 @@ export default { ...@@ -44,20 +44,16 @@ export default {
</script> </script>
<template> <template>
<div id="sidebar" :class="{'sidebar-mini' : isCollapsed}"> <div class="ide-file-list">
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th <th
v-if="isCollapsed" v-if="isCollapsed"
class="repo-file-options title"
> >
<strong class="clgray">
{{ projectName }}
</strong>
</th> </th>
<template v-else> <template v-else>
<th class="name multi-file-table-col-name"> <th class="name multi-file-table-name">
Name Name
</th> </th>
<th class="hidden-sm hidden-xs last-commit"> <th class="hidden-sm hidden-xs last-commit">
...@@ -79,7 +75,7 @@ export default { ...@@ -79,7 +75,7 @@ export default {
:key="n" :key="n"
/> />
<repo-file <repo-file
v-for="(file, index) in treeList" v-for="file in treeList"
:key="file.key" :key="file.key"
:file="file" :file="file"
/> />
......
...@@ -41,30 +41,41 @@ export default { ...@@ -41,30 +41,41 @@ export default {
<template> <template>
<li <li
:class="{ active : tab.active }"
@click="setFileActive(tab)" @click="setFileActive(tab)"
> >
<button <button
type="button" type="button"
class="close-btn" class="multi-file-tab-close"
@click.stop.prevent="closeFile({ file: tab })" @click.stop.prevent="closeFile({ file: tab })"
:aria-label="closeLabel"> :aria-label="closeLabel"
:class="{
'modified': tab.changed,
}"
:disabled="tab.changed"
>
<i <i
class="fa" class="fa"
:class="changedClass" :class="changedClass"
aria-hidden="true"> aria-hidden="true"
>
</i> </i>
</button> </button>
<a <div
href="#" class="multi-file-tab"
class="repo-tab" :class="{active : tab.active }"
:title="tab.url" :title="tab.url"
<<<<<<< HEAD
@click.prevent.stop="setFileActive(tab)"> @click.prevent.stop="setFileActive(tab)">
{{tab.name}} {{tab.name}}
<fileStatusIcon <fileStatusIcon
:file="tab"> :file="tab">
</fileStatusIcon> </fileStatusIcon>
</a> </a>
=======
>
{{ tab.name }}
</div>
>>>>>>> upstream/master
</li> </li>
</template> </template>
...@@ -16,14 +16,12 @@ ...@@ -16,14 +16,12 @@
<template> <template>
<ul <ul
id="tabs" class="multi-file-tabs list-unstyled append-bottom-0"
class="list-unstyled"
> >
<repo-tab <repo-tab
v-for="tab in openFiles" v-for="tab in openFiles"
:key="tab.id" :key="tab.id"
:tab="tab" :tab="tab"
/> />
<li class="tabs-divider" />
</ul> </ul>
</template> </template>
...@@ -34,3 +34,7 @@ export const canEditFile = (state) => { ...@@ -34,3 +34,7 @@ export const canEditFile = (state) => {
openedFiles.length && openedFiles.length &&
(currentActiveFile && !currentActiveFile.renderError && !currentActiveFile.binary); (currentActiveFile && !currentActiveFile.renderError && !currentActiveFile.binary);
}; };
export const addedFiles = state => changedFiles(state).filter(f => f.tempFile);
export const modifiedFiles = state => changedFiles(state).filter(f => !f.tempFile);
...@@ -2,14 +2,43 @@ ...@@ -2,14 +2,43 @@
.cgray { color: $common-gray; } .cgray { color: $common-gray; }
.clgray { color: $common-gray-light; } .clgray { color: $common-gray-light; }
.cred { color: $common-red; } .cred { color: $common-red; }
svg.cred { fill: $common-red; }
.cgreen { color: $common-green; } .cgreen { color: $common-green; }
svg.cgreen { fill: $common-green; }
.cdark { color: $common-gray-dark; } .cdark { color: $common-gray-dark; }
.text-plain,
.text-plain:hover {
color: $gl-text-color;
}
.text-secondary { .text-secondary {
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
} }
.text-primary,
.text-primary:hover {
color: $brand-primary;
}
.text-success,
.text-success:hover {
color: $brand-success;
}
.text-danger,
.text-danger:hover {
color: $brand-danger;
}
.text-warning,
.text-warning:hover {
color: $brand-warning;
}
.text-info,
.text-info:hover {
color: $brand-info;
}
.underlined-link { text-decoration: underline; } .underlined-link { text-decoration: underline; }
.hint { font-style: italic; color: $hint-color; } .hint { font-style: italic; color: $hint-color; }
.light { color: $common-gray; } .light { color: $common-gray; }
......
...@@ -1002,6 +1002,7 @@ header.header-content .dropdown-menu.projects-dropdown-menu { ...@@ -1002,6 +1002,7 @@ header.header-content .dropdown-menu.projects-dropdown-menu {
max-width: 250px; max-width: 250px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap;
} }
&:hover { &:hover {
......
.ci-status-icon-success, .ci-status-icon-success,
.ci-status-icon-passed { .ci-status-icon-passed {
color: $green-500; &,
&:hover,
&:focus {
color: $green-500;
}
} }
.ci-status-icon-failed { .ci-status-icon-failed {
color: $gl-danger; &,
&:hover,
&:focus {
color: $gl-danger;
}
} }
.ci-status-icon-pending, .ci-status-icon-pending,
.ci-status-icon-failed_with_warnings, .ci-status-icon-failed_with_warnings,
.ci-status-icon-success_with_warnings { .ci-status-icon-success_with_warnings {
color: $orange-500; &,
&:hover,
&:focus {
color: $orange-500;
}
} }
.ci-status-icon-running { .ci-status-icon-running {
color: $blue-400; &,
&:hover,
&:focus {
color: $blue-400;
}
} }
.ci-status-icon-canceled, .ci-status-icon-canceled,
.ci-status-icon-disabled, .ci-status-icon-disabled,
.ci-status-icon-not-found { .ci-status-icon-not-found {
color: $gl-text-color; &,
&:hover,
&:focus {
color: $gl-text-color;
}
} }
.ci-status-icon-created, .ci-status-icon-created,
.ci-status-icon-skipped { .ci-status-icon-skipped {
color: $gray-darkest; &,
&:hover,
&:focus {
color: $gray-darkest;
}
} }
.ci-status-icon-manual { .ci-status-icon-manual {
color: $gl-text-color; &,
&:hover,
&:focus {
color: $gl-text-color;
}
} }
.icon-link { .icon-link {
......
...@@ -195,33 +195,6 @@ summary { ...@@ -195,33 +195,6 @@ summary {
} }
} }
// Typography =================================================================
.text-primary,
.text-primary:hover {
color: $brand-primary;
}
.text-success,
.text-success:hover {
color: $brand-success;
}
.text-danger,
.text-danger:hover {
color: $brand-danger;
}
.text-warning,
.text-warning:hover {
color: $brand-warning;
}
.text-info,
.text-info:hover {
color: $brand-info;
}
// Prevent datetimes on tooltips to break into two lines // Prevent datetimes on tooltips to break into two lines
.local-timeago { .local-timeago {
white-space: nowrap; white-space: nowrap;
......
...@@ -777,12 +777,6 @@ ul.notes { ...@@ -777,12 +777,6 @@ ul.notes {
} }
} }
svg {
fill: currentColor;
height: 16px;
width: 16px;
}
.loading { .loading {
margin: 0; margin: 0;
height: auto; height: auto;
......
...@@ -299,14 +299,7 @@ ...@@ -299,14 +299,7 @@
} }
svg { svg {
fill: $layout-link-gray;
path {
fill: $layout-link-gray;
}
use {
stroke: $layout-link-gray;
}
} }
.fa-caret-down { .fa-caret-down {
...@@ -893,10 +886,6 @@ pre.light-well { ...@@ -893,10 +886,6 @@ pre.light-well {
font-size: $gl-font-size; font-size: $gl-font-size;
} }
a {
color: $gl-text-color;
}
.avatar-container, .avatar-container,
.controls { .controls {
flex: 0 0 auto; flex: 0 0 auto;
......
...@@ -35,259 +35,220 @@ ...@@ -35,259 +35,220 @@
} }
} }
.repository-view { .multi-file {
border: 1px solid $border-color; display: flex;
border-radius: $border-radius-default; height: calc(100vh - 145px);
color: $almost-black; border-top: 1px solid $white-dark;
border-bottom: 1px solid $white-dark;
&.is-collapsed {
.ide-file-list {
max-width: 250px;
}
}
}
.code.white pre .hll { .ide-file-list {
background-color: $well-light-border !important; flex: 1;
overflow: scroll;
.file {
cursor: pointer;
} }
.tree-content-holder { a {
display: -webkit-flex; color: $gl-text-color;
display: flex;
min-height: 300px;
} }
.tree-content-holder-mini { th {
height: 100vh; position: sticky;
top: 0;
} }
}
.panel-right { .multi-file-table-name,
display: -webkit-flex; .multi-file-table-col-commit-message {
display: flex; white-space: nowrap;
-webkit-flex-direction: column; overflow: hidden;
flex-direction: column; text-overflow: ellipsis;
width: 80%; max-width: 0;
height: 100%; }
.monaco-editor.vs { .multi-file-table-name {
.current-line { width: 350px;
border: 0; }
background: $well-light-border;
}
.line-numbers { .multi-file-table-col-commit-message {
cursor: pointer; width: 50%;
}
&:hover { .multi-file-edit-pane {
text-decoration: underline; display: flex;
} flex-direction: column;
} flex: 1;
} border-left: 1px solid $white-dark;
overflow: hidden;
}
.blob-no-preview { .multi-file-tabs {
.vertical-center { display: flex;
justify-content: center; overflow: scroll;
width: 100%; background-color: $white-normal;
} box-shadow: inset 0 -1px $white-dark;
}
&.blob-editor-container { > li {
overflow: hidden; position: relative;
} }
}
.blob-viewer-container { .multi-file-tab {
-webkit-flex: 1; @include str-truncated(150px);
flex: 1; padding: ($gl-padding / 2) ($gl-padding + 12) ($gl-padding / 2) $gl-padding;
overflow: auto; background-color: $gray-normal;
border-right: 1px solid $white-dark;
> div, border-bottom: 1px solid $white-dark;
.file-content:not(.wiki) { cursor: pointer;
display: flex;
} &.active {
background-color: $white-light;
> div, border-bottom-color: $white-light;
.file-content, }
.blob-viewer, }
.line-number,
.blob-content,
.code {
min-height: 100%;
width: 100%;
}
.line-numbers {
min-width: 44px;
}
.blob-content {
flex: 1;
overflow-x: auto;
}
}
#tabs { .multi-file-tab-close {
position: relative; position: absolute;
flex-shrink: 0; right: 8px;
display: flex; top: 50%;
width: 100%; padding: 0;
padding-left: 0; background: none;
margin-bottom: 0; border: 0;
white-space: nowrap; font-size: $gl-font-size;
overflow-y: hidden; color: $gray-darkest;
overflow-x: auto; transform: translateY(-50%);
li { &:not(.modified):hover,
position: relative; &:not(.modified):focus {
background: $gray-normal; color: $hint-color;
padding: #{$gl-padding / 2} $gl-padding; }
border-right: 1px solid $white-dark;
border-bottom: 1px solid $white-dark;
cursor: pointer;
&.active {
background: $white-light;
border-bottom: 0;
}
a {
@include str-truncated(100px);
color: $gl-text-color;
vertical-align: middle;
text-decoration: none;
margin-right: 12px;
&:focus {
outline: none;
}
}
.close-btn {
position: absolute;
right: 8px;
top: 50%;
padding: 0;
background: none;
border: 0;
font-size: $gl-font-size;
transform: translateY(-50%);
}
.close-icon:hover {
color: $hint-color;
}
.close-icon,
.unsaved-icon {
color: $gray-darkest;
}
.unsaved-icon {
color: $brand-success;
}
&.tabs-divider {
width: 100%;
background-color: $white-light;
border-right: 0;
border-top-right-radius: 2px;
}
}
}
.repo-file-buttons { &.modified {
background-color: $white-light; color: $indigo-700;
padding: 5px 10px; }
border-top: 1px solid $white-normal; }
}
#binary-viewer { .multi-file-edit-pane-content {
height: 80vh; flex: 1;
overflow: auto; height: 0;
margin: 0; }
.blob-viewer { .multi-file-editor-btn-group {
padding-top: 20px; padding: $grid-size;
padding-left: 20px; border-top: 1px solid $white-dark;
} }
.binary-unknown { // Not great, but this is to deal with our current output
text-align: center; .multi-file-preview-holder {
padding-top: 100px; height: 100%;
background: $gray-light; overflow: scroll;
height: 100%;
font-size: 17px; .blob-viewer {
height: 100%;
span {
display: block;
}
}
}
} }
#commit-area { .file-content.code {
background: $gray-light; display: flex;
padding: 20px;
.help-block { i {
padding-top: 7px; margin-left: -10px;
margin-top: 0;
} }
} }
#view-toggler { .line-numbers {
height: 41px; min-width: 50px;
position: relative;
display: block;
border-bottom: 1px solid $white-normal;
background: $white-light;
margin-top: -5px;
} }
#binary-viewer { .file-content,
img { .line-numbers,
max-width: 100%; .blob-content,
} .code {
min-height: 100%;
} }
}
#sidebar { .multi-file-commit-panel {
flex: 1; display: flex;
height: 100%; flex-direction: column;
height: 100%;
width: 290px;
padding: $gl-padding;
background-color: $gray-light;
border-left: 1px solid $white-dark;
&.is-collapsed {
width: 60px;
padding: 0;
}
}
&.sidebar-mini { .multi-file-commit-panel-section {
width: 20%; display: flex;
border-right: 1px solid $white-normal; flex-direction: column;
overflow: auto; flex: 1;
} }
.table { .multi-file-commit-panel-header {
margin-bottom: 0; display: flex;
} align-items: center;
padding: 0 0 12px;
margin-bottom: 12px;
border-bottom: 1px solid $white-dark;
tr { &.is-collapsed {
.repo-file-options { border-bottom: 1px solid $white-dark;
padding: 2px 16px;
width: 100%;
}
.title {
font-size: 10px;
text-transform: uppercase;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
}
.file-icon {
margin-right: 5px;
}
td {
white-space: nowrap;
}
}
.file { svg {
cursor: pointer; margin-left: auto;
margin-right: auto;
} }
}
}
a { .multi-file-commit-panel-collapse-btn {
@include str-truncated(250px); padding-top: 0;
color: $almost-black; padding-bottom: 0;
} margin-left: auto;
font-size: 20px;
&.is-collapsed {
margin-right: auto;
}
}
.multi-file-commit-list {
flex: 1;
overflow: scroll;
}
.multi-file-commit-list-item {
display: flex;
align-items: center;
}
.multi-file-addition {
fill: $green-500;
}
.multi-file-modified {
fill: $orange-500;
}
.multi-file-commit-list-collapsed {
display: flex;
flex-direction: column;
> svg {
margin-left: auto;
margin-right: auto;
} }
.file-status-icon { .file-status-icon {
...@@ -298,14 +259,26 @@ ...@@ -298,14 +259,26 @@
} }
.render-error { .multi-file-commit-list-path {
min-height: calc(100vh - 62px); @include str-truncated(100%);
}
.multi-file-commit-form {
padding-top: 12px;
border-top: 1px solid $white-dark;
}
.multi-file-commit-fieldset {
display: flex;
align-items: center;
padding-bottom: 12px;
p { .btn {
width: 100%; flex: 1;
} }
} }
.multi-file-table-col-name { .multi-file-commit-message.form-control {
width: 350px; height: 80px;
resize: none;
} }
...@@ -67,7 +67,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap ...@@ -67,7 +67,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
if params[:ref].present? if params[:ref].present?
@ref = params[:ref] @ref = params[:ref]
@commit = @repository.commit("refs/heads/#{@ref}") @commit = @repository.commit(Gitlab::Git::BRANCH_REF_PREFIX + @ref)
end end
render layout: false render layout: false
...@@ -78,7 +78,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap ...@@ -78,7 +78,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
if params[:ref].present? if params[:ref].present?
@ref = params[:ref] @ref = params[:ref]
@commit = @target_project.commit("refs/heads/#{@ref}") @commit = @target_project.commit(Gitlab::Git::BRANCH_REF_PREFIX + @ref)
end end
render layout: false render layout: false
......
...@@ -27,7 +27,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic ...@@ -27,7 +27,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
@merge_request.merge_request_diff @merge_request.merge_request_diff
end end
@merge_request_diffs = @merge_request.merge_request_diffs.viewable.select_without_diff.order_id_desc @merge_request_diffs = @merge_request.merge_request_diffs.viewable.order_id_desc
@comparable_diffs = @merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id } @comparable_diffs = @merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id }
if params[:start_sha].present? if params[:start_sha].present?
......
...@@ -104,8 +104,7 @@ class NotesFinder ...@@ -104,8 +104,7 @@ class NotesFinder
query = @params[:search] query = @params[:search]
return notes unless query return notes unless query
pattern = "%#{query}%" notes.search(query)
notes.where(Note.arel_table[:note].matches(pattern))
end end
# Notes changed since last fetch # Notes changed since last fetch
......
...@@ -343,10 +343,13 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -343,10 +343,13 @@ class ApplicationSetting < ActiveRecord::Base
user_default_external: false, user_default_external: false,
polling_interval_multiplier: 1, polling_interval_multiplier: 1,
usage_ping_enabled: Settings.gitlab['usage_ping_enabled'], usage_ping_enabled: Settings.gitlab['usage_ping_enabled'],
<<<<<<< HEAD
slack_app_enabled: false, slack_app_enabled: false,
slack_app_id: nil, slack_app_id: nil,
slack_app_secret: nil, slack_app_secret: nil,
slack_app_verification_token: nil, slack_app_verification_token: nil,
=======
>>>>>>> upstream/master
gitaly_timeout_fast: 10, gitaly_timeout_fast: 10,
gitaly_timeout_medium: 30, gitaly_timeout_medium: 30,
gitaly_timeout_default: 55 gitaly_timeout_default: 55
......
module Ci module Ci
class Runner < ActiveRecord::Base class Runner < ActiveRecord::Base
extend Gitlab::Ci::Model extend Gitlab::Ci::Model
<<<<<<< HEAD
prepend EE::Ci::Runner prepend EE::Ci::Runner
=======
include Gitlab::SQL::Pattern
>>>>>>> upstream/master
RUNNER_QUEUE_EXPIRY_TIME = 60.minutes RUNNER_QUEUE_EXPIRY_TIME = 60.minutes
ONLINE_CONTACT_TIMEOUT = 1.hour ONLINE_CONTACT_TIMEOUT = 1.hour
...@@ -60,10 +64,7 @@ module Ci ...@@ -60,10 +64,7 @@ module Ci
# #
# Returns an ActiveRecord::Relation. # Returns an ActiveRecord::Relation.
def self.search(query) def self.search(query)
t = arel_table fuzzy_search(query, [:token, :description])
pattern = "%#{query}%"
where(t[:token].matches(pattern).or(t[:description].matches(pattern)))
end end
def self.contact_time_deadline def self.contact_time_deadline
......
...@@ -121,9 +121,7 @@ module Issuable ...@@ -121,9 +121,7 @@ module Issuable
# #
# Returns an ActiveRecord::Relation. # Returns an ActiveRecord::Relation.
def search(query) def search(query)
title = to_fuzzy_arel(:title, query) fuzzy_search(query, [:title])
where(title)
end end
# Searches for records with a matching title or description. # Searches for records with a matching title or description.
...@@ -134,10 +132,7 @@ module Issuable ...@@ -134,10 +132,7 @@ module Issuable
# #
# Returns an ActiveRecord::Relation. # Returns an ActiveRecord::Relation.
def full_search(query) def full_search(query)
title = to_fuzzy_arel(:title, query) fuzzy_search(query, [:title, :description])
description = to_fuzzy_arel(:description, query)
where(title&.or(description))
end end
def sort(method, excluded_labels: []) def sort(method, excluded_labels: [])
......
class Email < ActiveRecord::Base class Email < ActiveRecord::Base
include Sortable include Sortable
include Gitlab::SQL::Pattern
belongs_to :user belongs_to :user
......
...@@ -68,20 +68,6 @@ class Group < Namespace ...@@ -68,20 +68,6 @@ class Group < Namespace
Gitlab::Database.postgresql? Gitlab::Database.postgresql?
end end
# Searches for groups matching the given query.
#
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
#
# query - The search query as a String
#
# Returns an ActiveRecord::Relation.
def search(query)
table = Namespace.arel_table
pattern = "%#{query}%"
where(table[:name].matches(pattern).or(table[:path].matches(pattern)))
end
def sort(method) def sort(method)
if method == 'storage_size_desc' if method == 'storage_size_desc'
# storage_size is a virtual column so we need to # storage_size is a virtual column so we need to
......
...@@ -292,9 +292,9 @@ class MergeRequest < ActiveRecord::Base ...@@ -292,9 +292,9 @@ class MergeRequest < ActiveRecord::Base
if persisted? if persisted?
merge_request_diff.commit_shas merge_request_diff.commit_shas
elsif compare_commits elsif compare_commits
compare_commits.reverse.map(&:sha) compare_commits.to_a.reverse.map(&:sha)
else else
[] Array(diff_head_sha)
end end
end end
...@@ -373,16 +373,28 @@ class MergeRequest < ActiveRecord::Base ...@@ -373,16 +373,28 @@ class MergeRequest < ActiveRecord::Base
# We use these attributes to force these to the intended values. # We use these attributes to force these to the intended values.
attr_writer :target_branch_sha, :source_branch_sha attr_writer :target_branch_sha, :source_branch_sha
def source_branch_ref
return @source_branch_sha if @source_branch_sha
return unless source_branch
Gitlab::Git::BRANCH_REF_PREFIX + source_branch
end
def target_branch_ref
return @target_branch_sha if @target_branch_sha
return unless target_branch
Gitlab::Git::BRANCH_REF_PREFIX + target_branch
end
def source_branch_head def source_branch_head
return unless source_project return unless source_project
source_branch_ref = @source_branch_sha || source_branch
source_project.repository.commit(source_branch_ref) if source_branch_ref source_project.repository.commit(source_branch_ref) if source_branch_ref
end end
def target_branch_head def target_branch_head
target_branch_ref = @target_branch_sha || target_branch target_project.repository.commit(target_branch_ref)
target_project.repository.commit(target_branch_ref) if target_branch_ref
end end
def branch_merge_base_commit def branch_merge_base_commit
...@@ -507,7 +519,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -507,7 +519,7 @@ class MergeRequest < ActiveRecord::Base
def merge_request_diff_for(diff_refs_or_sha) def merge_request_diff_for(diff_refs_or_sha)
@merge_request_diffs_by_diff_refs_or_sha ||= Hash.new do |h, diff_refs_or_sha| @merge_request_diffs_by_diff_refs_or_sha ||= Hash.new do |h, diff_refs_or_sha|
diffs = merge_request_diffs.viewable.select_without_diff diffs = merge_request_diffs.viewable
h[diff_refs_or_sha] = h[diff_refs_or_sha] =
if diff_refs_or_sha.is_a?(Gitlab::Diff::DiffRefs) if diff_refs_or_sha.is_a?(Gitlab::Diff::DiffRefs)
diffs.find_by_diff_refs(diff_refs_or_sha) diffs.find_by_diff_refs(diff_refs_or_sha)
...@@ -932,28 +944,18 @@ class MergeRequest < ActiveRecord::Base ...@@ -932,28 +944,18 @@ class MergeRequest < ActiveRecord::Base
# Note that this could also return SHA from now dangling commits # Note that this could also return SHA from now dangling commits
# #
def all_commit_shas def all_commit_shas
if persisted? return commit_shas unless persisted?
# MySQL doesn't support LIMIT in a subquery.
diffs_relation =
if Gitlab::Database.postgresql?
merge_request_diffs.order(id: :desc).limit(100)
else
merge_request_diffs
end
column_shas = MergeRequestDiffCommit diffs_relation = merge_request_diffs
.where(merge_request_diff: diffs_relation)
.limit(10_000)
.pluck('sha')
serialised_shas = merge_request_diffs.where.not(st_commits: nil).flat_map(&:commit_shas) # MySQL doesn't support LIMIT in a subquery.
diffs_relation = diffs_relation.recent if Gitlab::Database.postgresql?
(column_shas + serialised_shas).uniq MergeRequestDiffCommit
elsif compare_commits .where(merge_request_diff: diffs_relation)
compare_commits.to_a.reverse.map(&:id) .limit(10_000)
else .pluck('sha')
[diff_head_sha] .uniq
end
end end
def merge_commit def merge_commit
......
class MergeRequestDiff < ActiveRecord::Base class MergeRequestDiff < ActiveRecord::Base
include Sortable include Sortable
include Importable include Importable
include Gitlab::EncodingHelper
include ManualInverseAssociation include ManualInverseAssociation
include IgnorableColumn
# Prevent store of diff if commits amount more then 500 # Don't display more than 100 commits at once
COMMITS_SAFE_SIZE = 100 COMMITS_SAFE_SIZE = 100
# Valid types of serialized diffs allowed by Gitlab::Git::Diff ignore_column :st_commits,
VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta].freeze :st_diffs
belongs_to :merge_request belongs_to :merge_request
manual_inverse_association :merge_request, :merge_request_diff manual_inverse_association :merge_request, :merge_request_diff
...@@ -16,9 +16,6 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -16,9 +16,6 @@ class MergeRequestDiff < ActiveRecord::Base
has_many :merge_request_diff_files, -> { order(:merge_request_diff_id, :relative_order) } has_many :merge_request_diff_files, -> { order(:merge_request_diff_id, :relative_order) }
has_many :merge_request_diff_commits, -> { order(:merge_request_diff_id, :relative_order) } has_many :merge_request_diff_commits, -> { order(:merge_request_diff_id, :relative_order) }
serialize :st_commits # rubocop:disable Cop/ActiveRecordSerialize
serialize :st_diffs # rubocop:disable Cop/ActiveRecordSerialize
state_machine :state, initial: :empty do state_machine :state, initial: :empty do
state :collected state :collected
state :overflow state :overflow
...@@ -32,6 +29,8 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -32,6 +29,8 @@ class MergeRequestDiff < ActiveRecord::Base
scope :viewable, -> { without_state(:empty) } scope :viewable, -> { without_state(:empty) }
scope :recent, -> { order(id: :desc).limit(100) }
# All diff information is collected from repository after object is created. # All diff information is collected from repository after object is created.
# It allows you to override variables like head_commit_sha before getting diff. # It allows you to override variables like head_commit_sha before getting diff.
after_create :save_git_content, unless: :importing? after_create :save_git_content, unless: :importing?
...@@ -40,14 +39,6 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -40,14 +39,6 @@ class MergeRequestDiff < ActiveRecord::Base
find_by(start_commit_sha: diff_refs.start_sha, head_commit_sha: diff_refs.head_sha, base_commit_sha: diff_refs.base_sha) find_by(start_commit_sha: diff_refs.start_sha, head_commit_sha: diff_refs.head_sha, base_commit_sha: diff_refs.base_sha)
end end
def self.select_without_diff
select(column_names - ['st_diffs'])
end
def st_commits
super || []
end
# Collect information about commits and diff from repository # Collect information about commits and diff from repository
# and save it to the database as serialized data # and save it to the database as serialized data
def save_git_content def save_git_content
...@@ -129,11 +120,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -129,11 +120,7 @@ class MergeRequestDiff < ActiveRecord::Base
end end
def commit_shas def commit_shas
if st_commits.present? merge_request_diff_commits.map(&:sha)
st_commits.map { |commit| commit[:id] }
else
merge_request_diff_commits.map(&:sha)
end
end end
def diff_refs=(new_diff_refs) def diff_refs=(new_diff_refs)
...@@ -208,34 +195,11 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -208,34 +195,11 @@ class MergeRequestDiff < ActiveRecord::Base
end end
def commits_count def commits_count
if st_commits.present? merge_request_diff_commits.size
st_commits.size
else
merge_request_diff_commits.size
end
end
def utf8_st_diffs
return [] if st_diffs.blank?
st_diffs.map do |diff|
diff.each do |k, v|
diff[k] = encode_utf8(v) if v.respond_to?(:encoding)
end
end
end end
private private
# Old GitLab implementations may have generated diffs as ["--broken-diff"].
# Avoid an error 500 by ignoring bad elements. See:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/20776
def valid_raw_diff?(raw)
return false unless raw.respond_to?(:each)
raw.any? { |element| VALID_CLASSES.include?(element.class) }
end
def create_merge_request_diff_files(diffs) def create_merge_request_diff_files(diffs)
rows = diffs.map.with_index do |diff, index| rows = diffs.map.with_index do |diff, index|
diff_hash = diff.to_hash.merge( diff_hash = diff.to_hash.merge(
...@@ -259,9 +223,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -259,9 +223,7 @@ class MergeRequestDiff < ActiveRecord::Base
end end
def load_diffs(options) def load_diffs(options)
return Gitlab::Git::DiffCollection.new([]) unless diffs_from_database raw = merge_request_diff_files.map(&:to_hash)
raw = diffs_from_database
if paths = options[:paths] if paths = options[:paths]
raw = raw.select do |diff| raw = raw.select do |diff|
...@@ -272,22 +234,8 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -272,22 +234,8 @@ class MergeRequestDiff < ActiveRecord::Base
Gitlab::Git::DiffCollection.new(raw, options) Gitlab::Git::DiffCollection.new(raw, options)
end end
def diffs_from_database
return @diffs_from_database if defined?(@diffs_from_database)
@diffs_from_database =
if st_diffs.present?
if valid_raw_diff?(st_diffs)
st_diffs
end
elsif merge_request_diff_files.present?
merge_request_diff_files.map(&:to_hash)
end
end
def load_commits def load_commits
commits = st_commits.presence || merge_request_diff_commits commits = merge_request_diff_commits.map { |commit| Commit.from_hash(commit.to_hash, project) }
commits = commits.map { |commit| Commit.from_hash(commit.to_hash, project) }
CommitCollection CommitCollection
.new(merge_request.source_project, commits, merge_request.source_branch) .new(merge_request.source_project, commits, merge_request.source_branch)
......
...@@ -14,6 +14,7 @@ class Milestone < ActiveRecord::Base ...@@ -14,6 +14,7 @@ class Milestone < ActiveRecord::Base
include StripAttribute include StripAttribute
include Elastic::MilestonesSearch include Elastic::MilestonesSearch
include Milestoneish include Milestoneish
include Gitlab::SQL::Pattern
include ::EE::Milestone include ::EE::Milestone
...@@ -77,10 +78,7 @@ class Milestone < ActiveRecord::Base ...@@ -77,10 +78,7 @@ class Milestone < ActiveRecord::Base
# #
# Returns an ActiveRecord::Relation. # Returns an ActiveRecord::Relation.
def search(query) def search(query)
t = arel_table fuzzy_search(query, [:title, :description])
pattern = "%#{query}%"
where(t[:title].matches(pattern).or(t[:description].matches(pattern)))
end end
def filter_by_state(milestones, state) def filter_by_state(milestones, state)
......
...@@ -10,6 +10,7 @@ class Namespace < ActiveRecord::Base ...@@ -10,6 +10,7 @@ class Namespace < ActiveRecord::Base
include Routable include Routable
include AfterCommitQueue include AfterCommitQueue
include Storage::LegacyNamespace include Storage::LegacyNamespace
include Gitlab::SQL::Pattern
# Prevent users from creating unreasonably deep level of nesting. # Prevent users from creating unreasonably deep level of nesting.
# The number 20 was taken based on maximum nesting level of # The number 20 was taken based on maximum nesting level of
...@@ -87,10 +88,7 @@ class Namespace < ActiveRecord::Base ...@@ -87,10 +88,7 @@ class Namespace < ActiveRecord::Base
# #
# Returns an ActiveRecord::Relation # Returns an ActiveRecord::Relation
def search(query) def search(query)
t = arel_table fuzzy_search(query, [:name, :path])
pattern = "%#{query}%"
where(t[:name].matches(pattern).or(t[:path].matches(pattern)))
end end
def clean_path(path) def clean_path(path)
......
...@@ -17,6 +17,7 @@ class Note < ActiveRecord::Base ...@@ -17,6 +17,7 @@ class Note < ActiveRecord::Base
include ResolvableNote include ResolvableNote
include IgnorableColumn include IgnorableColumn
include Editable include Editable
include Gitlab::SQL::Pattern
module SpecialRole module SpecialRole
FIRST_TIME_CONTRIBUTOR = :first_time_contributor FIRST_TIME_CONTRIBUTOR = :first_time_contributor
...@@ -171,6 +172,10 @@ class Note < ActiveRecord::Base ...@@ -171,6 +172,10 @@ class Note < ActiveRecord::Base
def has_special_role?(role, note) def has_special_role?(role, note)
note.special_role == role note.special_role == role
end end
def search(query)
fuzzy_search(query, [:note])
end
end end
def searchable? def searchable?
......
...@@ -425,17 +425,11 @@ class Project < ActiveRecord::Base ...@@ -425,17 +425,11 @@ class Project < ActiveRecord::Base
# #
# query - The search query as a String. # query - The search query as a String.
def search(query) def search(query)
pattern = to_pattern(query) fuzzy_search(query, [:path, :name, :description])
where(
arel_table[:path].matches(pattern)
.or(arel_table[:name].matches(pattern))
.or(arel_table[:description].matches(pattern))
)
end end
def search_by_title(query) def search_by_title(query)
non_archived.where(arel_table[:name].matches(to_pattern(query))) non_archived.fuzzy_search(query, [:name])
end end
def visibility_levels def visibility_levels
......
...@@ -10,6 +10,7 @@ class Snippet < ActiveRecord::Base ...@@ -10,6 +10,7 @@ class Snippet < ActiveRecord::Base
include Mentionable include Mentionable
include Spammable include Spammable
include Editable include Editable
include Gitlab::SQL::Pattern
extend Gitlab::CurrentSettings extend Gitlab::CurrentSettings
...@@ -136,10 +137,7 @@ class Snippet < ActiveRecord::Base ...@@ -136,10 +137,7 @@ class Snippet < ActiveRecord::Base
# #
# Returns an ActiveRecord::Relation. # Returns an ActiveRecord::Relation.
def search(query) def search(query)
t = arel_table fuzzy_search(query, [:title, :file_name])
pattern = "%#{query}%"
where(t[:title].matches(pattern).or(t[:file_name].matches(pattern)))
end end
# Searches for snippets with matching content. # Searches for snippets with matching content.
...@@ -150,10 +148,7 @@ class Snippet < ActiveRecord::Base ...@@ -150,10 +148,7 @@ class Snippet < ActiveRecord::Base
# #
# Returns an ActiveRecord::Relation. # Returns an ActiveRecord::Relation.
def search_code(query) def search_code(query)
table = Snippet.arel_table fuzzy_search(query, [:content])
pattern = "%#{query}%"
where(table[:content].matches(pattern))
end end
end end
end end
...@@ -327,9 +327,6 @@ class User < ActiveRecord::Base ...@@ -327,9 +327,6 @@ class User < ActiveRecord::Base
# #
# Returns an ActiveRecord::Relation. # Returns an ActiveRecord::Relation.
def search(query) def search(query)
table = arel_table
pattern = User.to_pattern(query)
order = <<~SQL order = <<~SQL
CASE CASE
WHEN users.name = %{query} THEN 0 WHEN users.name = %{query} THEN 0
...@@ -339,11 +336,8 @@ class User < ActiveRecord::Base ...@@ -339,11 +336,8 @@ class User < ActiveRecord::Base
END END
SQL SQL
where( fuzzy_search(query, [:name, :email, :username])
table[:name].matches(pattern) .reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name)
.or(table[:email].matches(pattern))
.or(table[:username].matches(pattern))
).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name)
end end
# searches user by given pattern # searches user by given pattern
...@@ -351,16 +345,16 @@ class User < ActiveRecord::Base ...@@ -351,16 +345,16 @@ class User < ActiveRecord::Base
# This method uses ILIKE on PostgreSQL and LIKE on MySQL. # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
def search_with_secondary_emails(query) def search_with_secondary_emails(query)
table = arel_table
email_table = Email.arel_table email_table = Email.arel_table
pattern = "%#{query}%" matched_by_emails_user_ids = email_table
matched_by_emails_user_ids = email_table.project(email_table[:user_id]).where(email_table[:email].matches(pattern)) .project(email_table[:user_id])
.where(Email.fuzzy_arel_match(:email, query))
where( where(
table[:name].matches(pattern) fuzzy_arel_match(:name, query)
.or(table[:email].matches(pattern)) .or(fuzzy_arel_match(:email, query))
.or(table[:username].matches(pattern)) .or(fuzzy_arel_match(:username, query))
.or(table[:id].in(matched_by_emails_user_ids)) .or(arel_table[:id].in(matched_by_emails_user_ids))
) )
end end
......
require 'securerandom' require 'securerandom'
# Compare 2 branches for one repo or between repositories # Compare 2 refs for one repo or between repositories
# and return Gitlab::Git::Compare object that responds to commits and diffs # and return Gitlab::Git::Compare object that responds to commits and diffs
class CompareService class CompareService
attr_reader :start_project, :start_branch_name attr_reader :start_project, :start_ref_name
def initialize(new_start_project, new_start_branch_name) def initialize(new_start_project, new_start_ref_name)
@start_project = new_start_project @start_project = new_start_project
@start_branch_name = new_start_branch_name @start_ref_name = new_start_ref_name
end end
def execute(target_project, target_branch, straight: false) def execute(target_project, target_ref, straight: false)
raw_compare = target_project.repository.compare_source_branch(target_branch, start_project.repository, start_branch_name, straight: straight) raw_compare = target_project.repository.compare_source_branch(target_ref, start_project.repository, start_ref_name, straight: straight)
Compare.new(raw_compare, target_project, straight: straight) if raw_compare Compare.new(raw_compare, target_project, straight: straight) if raw_compare
end end
......
...@@ -22,7 +22,17 @@ module MergeRequests ...@@ -22,7 +22,17 @@ module MergeRequests
attr_accessor :merge_request attr_accessor :merge_request
delegate :target_branch, :source_branch, :source_project, :target_project, :compare_commits, :wip_title, :description, :errors, to: :merge_request delegate :target_branch,
:target_branch_ref,
:target_project,
:source_branch,
:source_branch_ref,
:source_project,
:compare_commits,
:wip_title,
:description,
:errors,
to: :merge_request
def find_source_project def find_source_project
return source_project if source_project.present? && can?(current_user, :read_project, source_project) return source_project if source_project.present? && can?(current_user, :read_project, source_project)
...@@ -58,10 +68,10 @@ module MergeRequests ...@@ -58,10 +68,10 @@ module MergeRequests
def compare_branches def compare_branches
compare = CompareService.new( compare = CompareService.new(
source_project, source_project,
source_branch source_branch_ref
).execute( ).execute(
target_project, target_project,
target_branch target_branch_ref
) )
if compare if compare
...@@ -130,6 +140,7 @@ module MergeRequests ...@@ -130,6 +140,7 @@ module MergeRequests
merge_request.description = closes_issue merge_request.description = closes_issue
end end
end end
<<<<<<< HEAD
def assign_title_and_description_from_single_commit def assign_title_and_description_from_single_commit
commits = compare_commits commits = compare_commits
...@@ -141,6 +152,19 @@ module MergeRequests ...@@ -141,6 +152,19 @@ module MergeRequests
merge_request.description ||= commit.description.try(:strip) merge_request.description ||= commit.description.try(:strip)
end end
=======
def assign_title_and_description_from_single_commit
commits = compare_commits
return unless commits&.count == 1
commit = commits.first
merge_request.title ||= commit.title
merge_request.description ||= commit.description.try(:strip)
end
>>>>>>> upstream/master
def assign_title_from_issue def assign_title_from_issue
return unless issue return unless issue
......
...@@ -5,8 +5,11 @@ module Projects ...@@ -5,8 +5,11 @@ module Projects
class MigrateAttachmentsService < BaseService class MigrateAttachmentsService < BaseService
attr_reader :logger, :old_path, :new_path attr_reader :logger, :old_path, :new_path
<<<<<<< HEAD
prepend ::EE::Projects::HashedStorage::MigrateAttachmentsService prepend ::EE::Projects::HashedStorage::MigrateAttachmentsService
=======
>>>>>>> upstream/master
def initialize(project, logger = nil) def initialize(project, logger = nil)
@project = project @project = project
@logger = logger || Rails.logger @logger = logger || Rails.logger
......
...@@ -3,8 +3,11 @@ module Projects ...@@ -3,8 +3,11 @@ module Projects
class MigrateRepositoryService < BaseService class MigrateRepositoryService < BaseService
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
<<<<<<< HEAD
prepend ::EE::Projects::HashedStorage::MigrateRepositoryService prepend ::EE::Projects::HashedStorage::MigrateRepositoryService
=======
>>>>>>> upstream/master
attr_reader :old_disk_path, :new_disk_path, :old_wiki_disk_path, :old_storage_version, :logger attr_reader :old_disk_path, :new_disk_path, :old_wiki_disk_path, :old_storage_version, :logger
def initialize(project, logger = nil) def initialize(project, logger = nil)
......
...@@ -51,6 +51,7 @@ module Projects ...@@ -51,6 +51,7 @@ module Projects
params.except(:default_branch, :run_auto_devops_pipeline_explicit, :run_auto_devops_pipeline_implicit) params.except(:default_branch, :run_auto_devops_pipeline_explicit, :run_auto_devops_pipeline_implicit)
end end
<<<<<<< HEAD
def changing_storage_size? def changing_storage_size?
new_repository_storage = params[:repository_storage] new_repository_storage = params[:repository_storage]
...@@ -58,6 +59,8 @@ module Projects ...@@ -58,6 +59,8 @@ module Projects
can?(current_user, :change_repository_storage, project) can?(current_user, :change_repository_storage, project)
end end
=======
>>>>>>> upstream/master
def renaming_project_with_container_registry_tags? def renaming_project_with_container_registry_tags?
new_path = params[:path] new_path = params[:path]
......
...@@ -5,7 +5,12 @@ xml.entry do ...@@ -5,7 +5,12 @@ xml.entry do
xml.link href: event_feed_url(event) xml.link href: event_feed_url(event)
xml.title truncate(event_feed_title(event), length: 80) xml.title truncate(event_feed_title(event), length: 80)
xml.updated event.updated_at.xmlschema xml.updated event.updated_at.xmlschema
xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(event.author_email))
# We're deliberately re-using "event.author" here since this data is
# eager-loaded. This allows us to re-use the user object's Email address,
# instead of having to run additional queries to figure out what Email to use
# for the avatar.
xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(event.author))
xml.author do xml.author do
xml.username event.author_username xml.username event.author_username
......
...@@ -67,8 +67,8 @@ ...@@ -67,8 +67,8 @@
- if @commit.last_pipeline - if @commit.last_pipeline
- last_pipeline = @commit.last_pipeline - last_pipeline = @commit.last_pipeline
.well-segment.pipeline-info .well-segment.pipeline-info
.status-icon-container{ class: "ci-status-icon-#{last_pipeline.status}" } .status-icon-container
= link_to project_pipeline_path(@project, last_pipeline.id) do = link_to project_pipeline_path(@project, last_pipeline.id), class: "ci-status-icon-#{last_pipeline.status}" do
= ci_icon_for_status(last_pipeline.status) = ci_icon_for_status(last_pipeline.status)
#{ _('Pipeline') } #{ _('Pipeline') }
= link_to "##{last_pipeline.id}", project_pipeline_path(@project, last_pipeline.id) = link_to "##{last_pipeline.id}", project_pipeline_path(@project, last_pipeline.id)
......
...@@ -11,6 +11,6 @@ ...@@ -11,6 +11,6 @@
= webpack_bundle_tag 'common_vue' = webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'repo' = webpack_bundle_tag 'repo'
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } %div{ class: [(container_class unless show_new_repo?), ("limit-container-width" unless fluid_layout)] }
= render 'projects/last_push' = render 'projects/last_push'
= render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id) = render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id)
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
= project_icon(project, alt: '', class: 'avatar project-avatar s40') = project_icon(project, alt: '', class: 'avatar project-avatar s40')
.project-details .project-details
%h3.prepend-top-0.append-bottom-0 %h3.prepend-top-0.append-bottom-0
= link_to project_path(project), class: dom_class(project) do = link_to project_path(project), class: 'text-plain' do
%span.project-full-name %span.project-full-name
%span.namespace-name %span.namespace-name
- if project.namespace && !skip_namespace - if project.namespace && !skip_namespace
......
- @no_container = true;
#repo{ data: { root: @path.empty?.to_s, #repo{ data: { root: @path.empty?.to_s,
root_url: project_tree_path(project), root_url: project_tree_path(project),
url: content_url, url: content_url,
......
---
title: Initializes the branches dropdown when the 'Start new pipeline' failed due to validation errors
merge_request: 15588
author: Christiaan Van den Poel
type: fixed
---
title: Fix item name and namespace text overflow in Projects dropdown
merge_request: 15451
author:
type: fixed
---
title: Fix Issue comment submit button being disabled when pasting content from another
GFM note
merge_request: 15530
author:
type: fixed
---
title: Fix merge requests where the source or target branch name matches a tag name
merge_request: 15591
author:
type: fixed
---
title: Create a fork network for forks with a deleted source
merge_request: 15595
author:
type: fixed
---
title: Fix defaults for MR states and merge statuses
merge_request:
author:
type: fixed
---
title: Use fuzzy search with minimum length of 3 characters where appropriate
merge_request:
author:
type: performance
---
title: Reuse authors when rendering event Atom feeds
merge_request:
author:
type: performance
class CleanUpFromMergeRequestDiffsAndCommits < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
class MergeRequestDiff < ActiveRecord::Base
self.table_name = 'merge_request_diffs'
include ::EachBatch
end
disable_ddl_transaction!
def up
Gitlab::BackgroundMigration.steal('DeserializeMergeRequestDiffsAndCommits')
# The literal '--- []\n' value is created by the import process and treated
# as null by the application, so we can ignore those - even if we were
# migrating, it wouldn't create any rows.
literal_prefix = Gitlab::Database.postgresql? ? 'E' : ''
non_empty = "
(st_commits IS NOT NULL AND st_commits != #{literal_prefix}'--- []\n')
OR
(st_diffs IS NOT NULL AND st_diffs != #{literal_prefix}'--- []\n')
".squish
MergeRequestDiff.where(non_empty).each_batch(of: 500) do |relation, index|
range = relation.pluck('MIN(id)', 'MAX(id)').first
Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits.new.perform(*range)
end
end
def down
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddDefaultValuesToMergeRequestStates < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def up
change_column_default :merge_requests, :state, :opened
change_column_default :merge_requests, :merge_status, :unchecked
end
def down
change_column_default :merge_requests, :state, nil
change_column_default :merge_requests, :merge_status, nil
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class PopulateMissingMergeRequestStatuses < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
disable_ddl_transaction!
class MergeRequest < ActiveRecord::Base
include EachBatch
self.table_name = 'merge_requests'
end
def up
say 'Populating missing merge_requests.state values'
# GitLab.com has no rows where "state" is NULL, and technically this should
# never happen. However it doesn't hurt to be 100% certain.
MergeRequest.where(state: nil).each_batch do |batch|
batch.update_all(state: 'opened')
end
say 'Populating missing merge_requests.merge_status values. ' \
'This will take a few minutes...'
# GitLab.com has 66 880 rows where "merge_status" is NULL, dating back all
# the way to 2011.
MergeRequest.where(merge_status: nil).each_batch(of: 10_000) do |batch|
batch.update_all(merge_status: 'unchecked')
# We want to give PostgreSQL some time to vacuum any dead tuples. In
# production we see it takes roughly 1 minute for a vacuuming run to clear
# out 10-20k dead tuples, so we'll wait for 90 seconds between every
# batch.
sleep(90) if sleep?
end
end
def down
# Reverting this makes no sense.
end
def sleep?
Rails.env.staging? || Rails.env.production?
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class MakeMergeRequestStatusesNotNull < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
change_column_null :merge_requests, :state, false
change_column_null :merge_requests, :merge_status, false
end
end
...@@ -3,8 +3,19 @@ class LimitsToMysql < ActiveRecord::Migration ...@@ -3,8 +3,19 @@ class LimitsToMysql < ActiveRecord::Migration
def up def up
return unless ActiveRecord::Base.configurations[Rails.env]['adapter'] =~ /^mysql/ return unless ActiveRecord::Base.configurations[Rails.env]['adapter'] =~ /^mysql/
change_column :merge_request_diffs, :st_commits, :text, limit: 2147483647 # These columns were removed in 10.3, but this is called from two places:
change_column :merge_request_diffs, :st_diffs, :text, limit: 2147483647 # 1. A migration run after they were added, but before they were removed.
# 2. A rake task which can be run at any time.
#
# Because of item 2, we need these checks.
if column_exists?(:merge_request_diffs, :st_commits)
change_column :merge_request_diffs, :st_commits, :text, limit: 2147483647
end
if column_exists?(:merge_request_diffs, :st_diffs)
change_column :merge_request_diffs, :st_diffs, :text, limit: 2147483647
end
change_column :snippets, :content, :text, limit: 2147483647 change_column :snippets, :content, :text, limit: 2147483647
change_column :notes, :st_diff, :text, limit: 2147483647 change_column :notes, :st_diff, :text, limit: 2147483647
end end
......
class RemoveMergeRequestDiffStCommitsAndStDiffs < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
remove_column :merge_request_diffs, :st_commits, :text
remove_column :merge_request_diffs, :st_diffs, :text
end
end
class AddIndexOnMergeRequestDiffsMergeRequestIdAndId < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index(:merge_request_diffs, [:merge_request_id, :id])
end
def down
if index_exists?(:merge_request_diffs, [:merge_request_id, :id])
remove_concurrent_index(:merge_request_diffs, [:merge_request_id, :id])
end
end
end
class RemoveIndexOnMergeRequestDiffsMergeRequestDiffId < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
if index_exists?(:merge_request_diffs, :merge_request_id)
remove_concurrent_index(:merge_request_diffs, :merge_request_id)
end
end
def down
add_concurrent_index(:merge_request_diffs, :merge_request_id)
end
end
class RescheduleForkNetworkCreation < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
MIGRATION = 'PopulateForkNetworksRange'.freeze
BATCH_SIZE = 100
DELAY_INTERVAL = 15.seconds
disable_ddl_transaction!
class ForkedProjectLink < ActiveRecord::Base
include EachBatch
self.table_name = 'forked_project_links'
end
def up
say 'Populating the `fork_networks` based on existing `forked_project_links`'
queue_background_migration_jobs_by_range_at_intervals(ForkedProjectLink, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE)
end
def down
# nothing
end
end
...@@ -11,7 +11,11 @@ ...@@ -11,7 +11,11 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
<<<<<<< HEAD
ActiveRecord::Schema.define(version: 20171124070437) do ActiveRecord::Schema.define(version: 20171124070437) do
=======
ActiveRecord::Schema.define(version: 20171124150326) do
>>>>>>> upstream/master
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -177,6 +181,7 @@ ActiveRecord::Schema.define(version: 20171124070437) do ...@@ -177,6 +181,7 @@ ActiveRecord::Schema.define(version: 20171124070437) do
t.integer "gitaly_timeout_default", default: 55, null: false t.integer "gitaly_timeout_default", default: 55, null: false
t.integer "gitaly_timeout_medium", default: 30, null: false t.integer "gitaly_timeout_medium", default: 30, null: false
t.integer "gitaly_timeout_fast", default: 10, null: false t.integer "gitaly_timeout_fast", default: 10, null: false
<<<<<<< HEAD
end end
create_table "approvals", force: :cascade do |t| create_table "approvals", force: :cascade do |t|
...@@ -194,6 +199,8 @@ ActiveRecord::Schema.define(version: 20171124070437) do ...@@ -194,6 +199,8 @@ ActiveRecord::Schema.define(version: 20171124070437) do
t.integer "group_id", null: false t.integer "group_id", null: false
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
=======
>>>>>>> upstream/master
end end
add_index "approver_groups", ["group_id"], name: "index_approver_groups_on_group_id", using: :btree add_index "approver_groups", ["group_id"], name: "index_approver_groups_on_group_id", using: :btree
...@@ -1358,8 +1365,6 @@ ActiveRecord::Schema.define(version: 20171124070437) do ...@@ -1358,8 +1365,6 @@ ActiveRecord::Schema.define(version: 20171124070437) do
create_table "merge_request_diffs", force: :cascade do |t| create_table "merge_request_diffs", force: :cascade do |t|
t.string "state" t.string "state"
t.text "st_commits"
t.text "st_diffs"
t.integer "merge_request_id", null: false t.integer "merge_request_id", null: false
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
...@@ -1369,7 +1374,7 @@ ActiveRecord::Schema.define(version: 20171124070437) do ...@@ -1369,7 +1374,7 @@ ActiveRecord::Schema.define(version: 20171124070437) do
t.string "start_commit_sha" t.string "start_commit_sha"
end end
add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", using: :btree add_index "merge_request_diffs", ["merge_request_id", "id"], name: "index_merge_request_diffs_on_merge_request_id_and_id", using: :btree
create_table "merge_request_metrics", force: :cascade do |t| create_table "merge_request_metrics", force: :cascade do |t|
t.integer "merge_request_id", null: false t.integer "merge_request_id", null: false
...@@ -1396,8 +1401,8 @@ ActiveRecord::Schema.define(version: 20171124070437) do ...@@ -1396,8 +1401,8 @@ ActiveRecord::Schema.define(version: 20171124070437) do
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.integer "milestone_id" t.integer "milestone_id"
t.string "state" t.string "state", default: "opened", null: false
t.string "merge_status" t.string "merge_status", default: "unchecked", null: false
t.integer "target_project_id", null: false t.integer "target_project_id", null: false
t.integer "iid" t.integer "iid"
t.text "description" t.text "description"
......
...@@ -11,7 +11,7 @@ troubleshooting steps that will help you diagnose the bottleneck. ...@@ -11,7 +11,7 @@ troubleshooting steps that will help you diagnose the bottleneck.
debug steps with GitLab Support so the backtraces can be analyzed by our team. debug steps with GitLab Support so the backtraces can be analyzed by our team.
It may reveal a bug or necessary improvement in GitLab. It may reveal a bug or necessary improvement in GitLab.
> **Note:** In any of the backtraces, be weary of suspecting cases where every > **Note:** In any of the backtraces, be wary of suspecting cases where every
thread appears to be waiting in the database, Redis, or waiting to acquire thread appears to be waiting in the database, Redis, or waiting to acquire
a mutex. This **may** mean there's contention in the database, for example, a mutex. This **may** mean there's contention in the database, for example,
but look for one thread that is different than the rest. This other thread but look for one thread that is different than the rest. This other thread
......
...@@ -163,7 +163,7 @@ Starting a project from a template needs this project to be exported. On a ...@@ -163,7 +163,7 @@ Starting a project from a template needs this project to be exported. On a
up to date master branch with run: up to date master branch with run:
``` ```
gdk run db gdk run
# In a new terminal window # In a new terminal window
bundle exec rake gitlab:update_project_templates bundle exec rake gitlab:update_project_templates
git checkout -b update-project-templates git checkout -b update-project-templates
......
...@@ -37,7 +37,7 @@ when using the migration helper method ...@@ -37,7 +37,7 @@ when using the migration helper method
`Gitlab::Database::MigrationHelpers#add_column_with_default`. This method works `Gitlab::Database::MigrationHelpers#add_column_with_default`. This method works
similar to `add_column` except it updates existing rows in batches without similar to `add_column` except it updates existing rows in batches without
blocking access to the table being modified. See ["Adding Columns With Default blocking access to the table being modified. See ["Adding Columns With Default
Values"](migration_style_guide.html#adding-columns-with-default-values) for more Values"](migration_style_guide.md#adding-columns-with-default-values) for more
information on how to use this method. information on how to use this method.
## Dropping Columns ## Dropping Columns
......
...@@ -756,8 +756,6 @@ X-Gitlab-Event: Merge Request Hook ...@@ -756,8 +756,6 @@ X-Gitlab-Event: Merge Request Hook
"title": "MS-Viewport", "title": "MS-Viewport",
"created_at": "2013-12-03T17:23:34Z", "created_at": "2013-12-03T17:23:34Z",
"updated_at": "2013-12-03T17:23:34Z", "updated_at": "2013-12-03T17:23:34Z",
"st_commits": null,
"st_diffs": null,
"milestone_id": null, "milestone_id": null,
"state": "opened", "state": "opened",
"merge_status": "unchecked", "merge_status": "unchecked",
......
...@@ -30,7 +30,8 @@ with all their related data and be moved into a new GitLab instance. ...@@ -30,7 +30,8 @@ with all their related data and be moved into a new GitLab instance.
| GitLab version | Import/Export version | | GitLab version | Import/Export version |
| ---------------- | --------------------- | | ---------------- | --------------------- |
| 10.0 to current | 0.2.0 | | 10.3 to current | 0.2.1 |
| 10.0 | 0.2.0 |
| 9.4.0 | 0.1.8 | | 9.4.0 | 0.1.8 |
| 9.2.0 | 0.1.7 | | 9.2.0 | 0.1.7 |
| 8.17.0 | 0.1.6 | | 8.17.0 | 0.1.6 |
......
...@@ -2,8 +2,8 @@ module API ...@@ -2,8 +2,8 @@ module API
module Helpers module Helpers
module InternalHelpers module InternalHelpers
SSH_GITALY_FEATURES = { SSH_GITALY_FEATURES = {
'git-receive-pack' => :ssh_receive_pack, 'git-receive-pack' => [:ssh_receive_pack, Gitlab::GitalyClient::MigrationStatus::OPT_IN],
'git-upload-pack' => :ssh_upload_pack 'git-upload-pack' => [:ssh_upload_pack, Gitlab::GitalyClient::MigrationStatus::OPT_OUT]
}.freeze }.freeze
def wiki? def wiki?
...@@ -102,8 +102,8 @@ module API ...@@ -102,8 +102,8 @@ module API
# Return the Gitaly Address if it is enabled # Return the Gitaly Address if it is enabled
def gitaly_payload(action) def gitaly_payload(action)
feature = SSH_GITALY_FEATURES[action] feature, status = SSH_GITALY_FEATURES[action]
return unless feature && Gitlab::GitalyClient.feature_enabled?(feature) return unless feature && Gitlab::GitalyClient.feature_enabled?(feature, status: status)
{ {
repository: repository.gitaly_repository, repository: repository.gitaly_repository,
......
# frozen_string_literal: true # frozen_string_literal: true
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/LineLength
# rubocop:disable Style/Documentation
module Gitlab module Gitlab
module BackgroundMigration module BackgroundMigration
# This background migration is going to create all `fork_networks` and
# the `fork_network_members` for the roots of fork networks based on the
# existing `forked_project_links`.
#
# When the source of a fork is deleted, we will create the fork with the
# target project as the root. This way, when there are forks of the target
# project, they will be joined into the same fork network.
#
# When the `fork_networks` and memberships for the root projects are created
# the `CreateForkNetworkMembershipsRange` migration is scheduled. This
# migration will create the memberships for all remaining forks-of-forks
class PopulateForkNetworksRange class PopulateForkNetworksRange
def perform(start_id, end_id) def perform(start_id, end_id)
log("Creating fork networks for forked project links: #{start_id} - #{end_id}") create_fork_networks_for_existing_projects(start_id, end_id)
create_fork_networks_for_missing_projects(start_id, end_id)
create_fork_networks_memberships_for_root_projects(start_id, end_id)
delay = BackgroundMigration::CreateForkNetworkMembershipsRange::RESCHEDULE_DELAY # rubocop:disable Metrics/LineLength
BackgroundMigrationWorker.perform_in(
delay, "CreateForkNetworkMembershipsRange", [start_id, end_id]
)
end
def create_fork_networks_for_existing_projects(start_id, end_id)
log("Creating fork networks: #{start_id} - #{end_id}")
ActiveRecord::Base.connection.execute <<~INSERT_NETWORKS ActiveRecord::Base.connection.execute <<~INSERT_NETWORKS
INSERT INTO fork_networks (root_project_id) INSERT INTO fork_networks (root_project_id)
SELECT DISTINCT forked_project_links.forked_from_project_id SELECT DISTINCT forked_project_links.forked_from_project_id
FROM forked_project_links FROM forked_project_links
-- Exclude the forks that are not the first level fork of a project
WHERE NOT EXISTS ( WHERE NOT EXISTS (
SELECT true SELECT true
FROM forked_project_links inner_links FROM forked_project_links inner_links
WHERE inner_links.forked_to_project_id = forked_project_links.forked_from_project_id WHERE inner_links.forked_to_project_id = forked_project_links.forked_from_project_id
) )
/* Exclude the ones that are already created, in case the fork network
was already created for another fork of the project.
*/
AND NOT EXISTS ( AND NOT EXISTS (
SELECT true SELECT true
FROM fork_networks FROM fork_networks
WHERE forked_project_links.forked_from_project_id = fork_networks.root_project_id WHERE forked_project_links.forked_from_project_id = fork_networks.root_project_id
) )
-- Only create a fork network for a root project that still exists
AND EXISTS ( AND EXISTS (
SELECT true SELECT true
FROM projects FROM projects
...@@ -32,7 +57,45 @@ module Gitlab ...@@ -32,7 +57,45 @@ module Gitlab
) )
AND forked_project_links.id BETWEEN #{start_id} AND #{end_id} AND forked_project_links.id BETWEEN #{start_id} AND #{end_id}
INSERT_NETWORKS INSERT_NETWORKS
end
def create_fork_networks_for_missing_projects(start_id, end_id)
log("Creating fork networks with missing root: #{start_id} - #{end_id}")
ActiveRecord::Base.connection.execute <<~INSERT_NETWORKS
INSERT INTO fork_networks (root_project_id)
SELECT DISTINCT forked_project_links.forked_to_project_id
FROM forked_project_links
-- Exclude forks that are not the root forks
WHERE NOT EXISTS (
SELECT true
FROM forked_project_links inner_links
WHERE inner_links.forked_to_project_id = forked_project_links.forked_from_project_id
)
/* Exclude the ones that are already created, in case this migration is
re-run
*/
AND NOT EXISTS (
SELECT true
FROM fork_networks
WHERE forked_project_links.forked_to_project_id = fork_networks.root_project_id
)
/* Exclude projects for which the project still exists, those are
Processed in the previous step of this migration
*/
AND NOT EXISTS (
SELECT true
FROM projects
WHERE projects.id = forked_project_links.forked_from_project_id
)
AND forked_project_links.id BETWEEN #{start_id} AND #{end_id}
INSERT_NETWORKS
end
def create_fork_networks_memberships_for_root_projects(start_id, end_id)
log("Creating memberships for root projects: #{start_id} - #{end_id}") log("Creating memberships for root projects: #{start_id} - #{end_id}")
ActiveRecord::Base.connection.execute <<~INSERT_ROOT ActiveRecord::Base.connection.execute <<~INSERT_ROOT
...@@ -41,8 +104,12 @@ module Gitlab ...@@ -41,8 +104,12 @@ module Gitlab
FROM fork_networks FROM fork_networks
/* Joining both on forked_from- and forked_to- so we could create the
memberships for forks for which the source was deleted
*/
INNER JOIN forked_project_links INNER JOIN forked_project_links
ON forked_project_links.forked_from_project_id = fork_networks.root_project_id ON forked_project_links.forked_from_project_id = fork_networks.root_project_id
OR forked_project_links.forked_to_project_id = fork_networks.root_project_id
WHERE NOT EXISTS ( WHERE NOT EXISTS (
SELECT true SELECT true
...@@ -51,9 +118,6 @@ module Gitlab ...@@ -51,9 +118,6 @@ module Gitlab
) )
AND forked_project_links.id BETWEEN #{start_id} AND #{end_id} AND forked_project_links.id BETWEEN #{start_id} AND #{end_id}
INSERT_ROOT INSERT_ROOT
delay = BackgroundMigration::CreateForkNetworkMembershipsRange::RESCHEDULE_DELAY
BackgroundMigrationWorker.perform_in(delay, "CreateForkNetworkMembershipsRange", [start_id, end_id])
end end
def log(message) def log(message)
......
...@@ -3,7 +3,6 @@ module Gitlab ...@@ -3,7 +3,6 @@ module Gitlab
class PlanEventFetcher < BaseEventFetcher class PlanEventFetcher < BaseEventFetcher
def initialize(*args) def initialize(*args)
@projections = [mr_diff_table[:id], @projections = [mr_diff_table[:id],
mr_diff_table[:st_commits],
issue_metrics_table[:first_mentioned_in_commit_at]] issue_metrics_table[:first_mentioned_in_commit_at]]
super(*args) super(*args)
...@@ -37,12 +36,7 @@ module Gitlab ...@@ -37,12 +36,7 @@ module Gitlab
def first_time_reference_commit(event) def first_time_reference_commit(event)
return nil unless event && merge_request_diff_commits return nil unless event && merge_request_diff_commits
commits = commits = merge_request_diff_commits[event['id'].to_i]
if event['st_commits'].present?
YAML.load(event['st_commits'])
else
merge_request_diff_commits[event['id'].to_i]
end
return nil if commits.blank? return nil if commits.blank?
......
...@@ -1047,9 +1047,15 @@ module Gitlab ...@@ -1047,9 +1047,15 @@ module Gitlab
end end
def with_repo_tmp_commit(start_repository, start_branch_name, sha) def with_repo_tmp_commit(start_repository, start_branch_name, sha)
source_ref = start_branch_name
unless Gitlab::Git.branch_ref?(source_ref)
source_ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{source_ref}"
end
tmp_ref = fetch_ref( tmp_ref = fetch_ref(
start_repository, start_repository,
source_ref: "#{Gitlab::Git::BRANCH_REF_PREFIX}#{start_branch_name}", source_ref: source_ref,
target_ref: "refs/tmp/#{SecureRandom.hex}" target_ref: "refs/tmp/#{SecureRandom.hex}"
) )
......
...@@ -31,14 +31,38 @@ module Gitlab ...@@ -31,14 +31,38 @@ module Gitlab
CLIENT_NAME = (Sidekiq.server? ? 'gitlab-sidekiq' : 'gitlab-web').freeze CLIENT_NAME = (Sidekiq.server? ? 'gitlab-sidekiq' : 'gitlab-web').freeze
MUTEX = Mutex.new MUTEX = Mutex.new
private_constant :MUTEX METRICS_MUTEX = Mutex.new
private_constant :MUTEX, :METRICS_MUTEX
class << self class << self
attr_accessor :query_time, :migrate_histogram attr_accessor :query_time
end end
self.query_time = 0 self.query_time = 0
self.migrate_histogram = Gitlab::Metrics.histogram(:gitaly_migrate_call_duration, "Gitaly migration call execution timings")
def self.migrate_histogram
@migrate_histogram ||=
METRICS_MUTEX.synchronize do
# If a thread was blocked on the mutex, the value was set already
return @migrate_histogram if @migrate_histogram
Gitlab::Metrics.histogram(:gitaly_migrate_call_duration_seconds,
"Gitaly migration call execution timings",
gitaly_enabled: nil, feature: nil)
end
end
def self.gitaly_call_histogram
@gitaly_call_histogram ||=
METRICS_MUTEX.synchronize do
# If a thread was blocked on the mutex, the value was set already
return @gitaly_call_histogram if @gitaly_call_histogram
Gitlab::Metrics.histogram(:gitaly_controller_action_duration_seconds,
"Gitaly endpoint histogram by controller and action combination",
Gitlab::Metrics::Transaction::BASE_LABELS.merge(gitaly_service: nil, rpc: nil))
end
end
def self.stub(name, storage) def self.stub(name, storage)
MUTEX.synchronize do MUTEX.synchronize do
...@@ -94,7 +118,11 @@ module Gitlab ...@@ -94,7 +118,11 @@ module Gitlab
# end # end
# #
def self.call(storage, service, rpc, request, remote_storage: nil, timeout: nil) def self.call(storage, service, rpc, request, remote_storage: nil, timeout: nil)
<<<<<<< HEAD
start = Process.clock_gettime(Process::CLOCK_MONOTONIC) start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
=======
start = Gitlab::Metrics::System.monotonic_time
>>>>>>> upstream/master
enforce_gitaly_request_limits(:call) enforce_gitaly_request_limits(:call)
kwargs = request_kwargs(storage, timeout, remote_storage: remote_storage) kwargs = request_kwargs(storage, timeout, remote_storage: remote_storage)
...@@ -102,9 +130,23 @@ module Gitlab ...@@ -102,9 +130,23 @@ module Gitlab
stub(service, storage).__send__(rpc, request, kwargs) # rubocop:disable GitlabSecurity/PublicSend stub(service, storage).__send__(rpc, request, kwargs) # rubocop:disable GitlabSecurity/PublicSend
ensure ensure
self.query_time += Process.clock_gettime(Process::CLOCK_MONOTONIC) - start duration = Gitlab::Metrics::System.monotonic_time - start
# Keep track, seperately, for the performance bar
self.query_time += duration
gitaly_call_histogram.observe(
current_transaction_labels.merge(gitaly_service: service.to_s, rpc: rpc.to_s),
duration)
end
<<<<<<< HEAD
=======
def self.current_transaction_labels
Gitlab::Metrics::Transaction.current&.labels || {}
end end
private_class_method :current_transaction_labels
>>>>>>> upstream/master
def self.request_kwargs(storage, timeout, remote_storage: nil) def self.request_kwargs(storage, timeout, remote_storage: nil)
encoded_token = Base64.strict_encode64(token(storage).to_s) encoded_token = Base64.strict_encode64(token(storage).to_s)
metadata = { metadata = {
...@@ -193,10 +235,10 @@ module Gitlab ...@@ -193,10 +235,10 @@ module Gitlab
feature_stack = Thread.current[:gitaly_feature_stack] ||= [] feature_stack = Thread.current[:gitaly_feature_stack] ||= []
feature_stack.unshift(feature) feature_stack.unshift(feature)
begin begin
start = Process.clock_gettime(Process::CLOCK_MONOTONIC) start = Gitlab::Metrics::System.monotonic_time
yield is_enabled yield is_enabled
ensure ensure
total_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start total_time = Gitlab::Metrics::System.monotonic_time - start
migrate_histogram.observe({ gitaly_enabled: is_enabled, feature: feature }, total_time) migrate_histogram.observe({ gitaly_enabled: is_enabled, feature: feature }, total_time)
feature_stack.shift feature_stack.shift
Thread.current[:gitaly_feature_stack] = nil if feature_stack.empty? Thread.current[:gitaly_feature_stack] = nil if feature_stack.empty?
......
...@@ -3,7 +3,7 @@ module Gitlab ...@@ -3,7 +3,7 @@ module Gitlab
extend self extend self
# For every version update, the version history in import_export.md has to be kept up to date. # For every version update, the version history in import_export.md has to be kept up to date.
VERSION = '0.2.0'.freeze VERSION = '0.2.1'.freeze
FILENAME_LIMIT = 50 FILENAME_LIMIT = 50
def export_path(relative_path:) def export_path(relative_path:)
......
...@@ -138,8 +138,6 @@ methods: ...@@ -138,8 +138,6 @@ methods:
- :type - :type
services: services:
- :type - :type
merge_request_diff:
- :utf8_st_diffs
merge_request_diff_files: merge_request_diff_files:
- :utf8_diff - :utf8_diff
merge_requests: merge_requests:
......
...@@ -58,7 +58,6 @@ module Gitlab ...@@ -58,7 +58,6 @@ module Gitlab
def setup_models def setup_models
case @relation_name case @relation_name
when :merge_request_diff then setup_st_diff_commits
when :merge_request_diff_files then setup_diff when :merge_request_diff_files then setup_diff
when :notes then setup_note when :notes then setup_note
when :project_label, :project_labels then setup_label when :project_label, :project_labels then setup_label
...@@ -208,13 +207,6 @@ module Gitlab ...@@ -208,13 +207,6 @@ module Gitlab
relation_class: relation_class) relation_class: relation_class)
end end
def setup_st_diff_commits
@relation_hash['st_diffs'] = @relation_hash.delete('utf8_st_diffs')
HashUtil.deep_symbolize_array!(@relation_hash['st_diffs'])
HashUtil.deep_symbolize_array_with_date!(@relation_hash['st_commits'])
end
def setup_diff def setup_diff
@relation_hash['diff'] = @relation_hash.delete('utf8_diff') @relation_hash['diff'] = @relation_hash.delete('utf8_diff')
end end
......
...@@ -4,9 +4,15 @@ module Gitlab ...@@ -4,9 +4,15 @@ module Gitlab
extend ActiveSupport::Concern extend ActiveSupport::Concern
MIN_CHARS_FOR_PARTIAL_MATCHING = 3 MIN_CHARS_FOR_PARTIAL_MATCHING = 3
REGEX_QUOTED_WORD = /(?<=^| )"[^"]+"(?= |$)/ REGEX_QUOTED_WORD = /(?<=\A| )"[^"]+"(?= |\z)/
class_methods do class_methods do
def fuzzy_search(query, columns)
matches = columns.map { |col| fuzzy_arel_match(col, query) }.compact.reduce(:or)
where(matches)
end
def to_pattern(query) def to_pattern(query)
if partial_matching?(query) if partial_matching?(query)
"%#{sanitize_sql_like(query)}%" "%#{sanitize_sql_like(query)}%"
...@@ -19,12 +25,19 @@ module Gitlab ...@@ -19,12 +25,19 @@ module Gitlab
query.length >= MIN_CHARS_FOR_PARTIAL_MATCHING query.length >= MIN_CHARS_FOR_PARTIAL_MATCHING
end end
def to_fuzzy_arel(column, query) def fuzzy_arel_match(column, query)
words = select_fuzzy_words(query) query = query.squish
return nil unless query.present?
matches = words.map { |word| arel_table[column].matches(to_pattern(word)) } words = select_fuzzy_words(query)
matches.reduce { |result, match| result.and(match) } if words.any?
words.map { |word| arel_table[column].matches(to_pattern(word)) }.reduce(:and)
else
# No words of at least 3 chars, but we can search for an exact
# case insensitive match with the query as a whole
arel_table[column].matches(sanitize_sql_like(query))
end
end end
def select_fuzzy_words(query) def select_fuzzy_words(query)
...@@ -32,7 +45,7 @@ module Gitlab ...@@ -32,7 +45,7 @@ module Gitlab
query = quoted_words.reduce(query) { |q, quoted_word| q.sub(quoted_word, '') } query = quoted_words.reduce(query) { |q, quoted_word| q.sub(quoted_word, '') }
words = query.split(/\s+/) words = query.split
quoted_words.map! { |quoted_word| quoted_word[1..-2] } quoted_words.map! { |quoted_word| quoted_word[1..-2] }
......
...@@ -500,6 +500,18 @@ describe 'Pipelines', :js do ...@@ -500,6 +500,18 @@ describe 'Pipelines', :js do
end end
it { expect(page).to have_content('Missing .gitlab-ci.yml file') } it { expect(page).to have_content('Missing .gitlab-ci.yml file') }
it 'creates a pipeline after first request failed and a valid gitlab-ci.yml file is available when trying again' do
click_button project.default_branch
stub_ci_pipeline_to_return_yaml_file
page.within '.dropdown-menu' do
click_link 'master'
end
expect { click_on 'Create pipeline' }
.to change { Ci::Pipeline.count }.by(1)
end
end end
end end
end end
......
...@@ -26,9 +26,11 @@ feature 'Multi-file editor new directory', :js do ...@@ -26,9 +26,11 @@ feature 'Multi-file editor new directory', :js do
click_button('Create directory') click_button('Create directory')
end end
find('.multi-file-commit-panel-collapse-btn').click
fill_in('commit-message', with: 'commit message') fill_in('commit-message', with: 'commit message')
click_button('Commit 1 file') click_button('Commit')
expect(page).to have_selector('td', text: 'commit message') expect(page).to have_selector('td', text: 'commit message')
end end
......
...@@ -26,9 +26,11 @@ feature 'Multi-file editor new file', :js do ...@@ -26,9 +26,11 @@ feature 'Multi-file editor new file', :js do
click_button('Create file') click_button('Create file')
end end
find('.multi-file-commit-panel-collapse-btn').click
fill_in('commit-message', with: 'commit message') fill_in('commit-message', with: 'commit message')
click_button('Commit 1 file') click_button('Commit')
expect(page).to have_selector('td', text: 'commit message') expect(page).to have_selector('td', text: 'commit message')
end end
......
...@@ -26,7 +26,7 @@ feature 'Multi-file editor upload file', :js do ...@@ -26,7 +26,7 @@ feature 'Multi-file editor upload file', :js do
find('.add-to-tree').click find('.add-to-tree').click
expect(page).to have_selector('.repo-tab', text: 'doc_sample.txt') expect(page).to have_selector('.multi-file-tab', text: 'doc_sample.txt')
expect(find('.blob-editor-container .lines-content')['innerText']).to have_content(File.open(txt_file, &:readline)) expect(find('.blob-editor-container .lines-content')['innerText']).to have_content(File.open(txt_file, &:readline))
end end
...@@ -39,7 +39,7 @@ feature 'Multi-file editor upload file', :js do ...@@ -39,7 +39,7 @@ feature 'Multi-file editor upload file', :js do
find('.add-to-tree').click find('.add-to-tree').click
expect(page).to have_selector('.repo-tab', text: 'dk.png') expect(page).to have_selector('.multi-file-tab', text: 'dk.png')
expect(page).not_to have_selector('.monaco-editor') expect(page).not_to have_selector('.monaco-editor')
expect(page).to have_content('The source could not be displayed for this temporary file.') expect(page).to have_content('The source could not be displayed for this temporary file.')
end end
......
/* eslint-disable space-before-function-paren, no-var, comma-dangle, no-return-assign, max-len */
import '~/behaviors/autosize'; import '~/behaviors/autosize';
(function() { function load() {
describe('Autosize behavior', function() { $(document).trigger('load');
var load; }
beforeEach(function() {
return setFixtures('<textarea class="js-autosize" style="resize: vertical"></textarea>'); describe('Autosize behavior', () => {
}); beforeEach(() => {
it('does not overwrite the resize property', function() { setFixtures('<textarea class="js-autosize" style="resize: vertical"></textarea>');
load(); });
return expect($('textarea')).toHaveCss({
resize: 'vertical' it('does not overwrite the resize property', () => {
}); load();
expect($('textarea')).toHaveCss({
resize: 'vertical',
}); });
return load = function() {
return $(document).trigger('load');
};
}); });
}).call(window); });
...@@ -50,6 +50,18 @@ describe('ProjectsListItemComponent', () => { ...@@ -50,6 +50,18 @@ describe('ProjectsListItemComponent', () => {
expect(vm.highlightedProjectName).toBe(mockProject.name); expect(vm.highlightedProjectName).toBe(mockProject.name);
}); });
}); });
describe('truncatedNamespace', () => {
it('should truncate project name from namespace string', () => {
vm.namespace = 'platform / nokia-3310';
expect(vm.truncatedNamespace).toBe('platform');
});
it('should truncate namespace string from the middle if it includes more than two groups in path', () => {
vm.namespace = 'platform / hardware / broadcom / Wifi Group / Mobile Chipset / nokia-3310';
expect(vm.truncatedNamespace).toBe('platform / ... / Mobile Chipset');
});
});
}); });
describe('template', () => { describe('template', () => {
......
import Vue from 'vue';
import store from '~/repo/stores';
import listCollapsed from '~/repo/components/commit_sidebar/list_collapsed.vue';
import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
import { file } from '../../helpers';
describe('Multi-file editor commit sidebar list collapsed', () => {
let vm;
beforeEach(() => {
const Component = Vue.extend(listCollapsed);
vm = createComponentWithStore(Component, store);
vm.$store.state.openFiles.push(file(), file());
vm.$store.state.openFiles[0].tempFile = true;
vm.$store.state.openFiles.forEach((f) => {
Object.assign(f, {
changed: true,
});
});
vm.$mount();
});
afterEach(() => {
vm.$destroy();
});
it('renders added & modified files count', () => {
expect(vm.$el.textContent.replace(/\s+/g, ' ').trim()).toBe('1 1');
});
});
import Vue from 'vue';
import listItem from '~/repo/components/commit_sidebar/list_item.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import { file } from '../../helpers';
describe('Multi-file editor commit sidebar list item', () => {
let vm;
let f;
beforeEach(() => {
const Component = Vue.extend(listItem);
f = file();
vm = mountComponent(Component, {
file: f,
});
});
afterEach(() => {
vm.$destroy();
});
it('renders file path', () => {
expect(vm.$el.querySelector('.multi-file-commit-list-path').textContent.trim()).toBe(f.path);
});
describe('computed', () => {
describe('iconName', () => {
it('returns modified when not a tempFile', () => {
expect(vm.iconName).toBe('file-modified');
});
it('returns addition when not a tempFile', () => {
f.tempFile = true;
expect(vm.iconName).toBe('file-addition');
});
});
describe('iconClass', () => {
it('returns modified when not a tempFile', () => {
expect(vm.iconClass).toContain('multi-file-modified');
});
it('returns addition when not a tempFile', () => {
f.tempFile = true;
expect(vm.iconClass).toContain('multi-file-addition');
});
});
});
});
import Vue from 'vue';
import store from '~/repo/stores';
import commitSidebarList from '~/repo/components/commit_sidebar/list.vue';
import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
import { file } from '../../helpers';
describe('Multi-file editor commit sidebar list', () => {
let vm;
beforeEach(() => {
const Component = Vue.extend(commitSidebarList);
vm = createComponentWithStore(Component, store, {
title: 'Staged',
fileList: [],
collapsed: false,
}).$mount();
});
afterEach(() => {
vm.$destroy();
});
describe('empty file list', () => {
it('renders no changes text', () => {
expect(vm.$el.querySelector('.help-block').textContent.trim()).toBe('No changes');
});
});
describe('with a list of files', () => {
beforeEach((done) => {
const f = file('file name');
f.changed = true;
vm.fileList.push(f);
Vue.nextTick(done);
});
it('renders list', () => {
expect(vm.$el.querySelectorAll('li').length).toBe(1);
});
});
describe('collapsed', () => {
beforeEach((done) => {
vm.collapsed = true;
Vue.nextTick(done);
});
it('adds collapsed class', () => {
expect(vm.$el.querySelector('.is-collapsed')).not.toBeNull();
});
it('hides list', () => {
expect(vm.$el.querySelector('.list-unstyled')).toBeNull();
expect(vm.$el.querySelector('.help-block')).toBeNull();
});
it('hides collapse button', () => {
expect(vm.$el.querySelector('.multi-file-commit-panel-collapse-btn')).toBeNull();
});
});
it('clicking toggle collapse button emits toggle event', () => {
spyOn(vm, '$emit');
vm.$el.querySelector('.multi-file-commit-panel-collapse-btn').click();
expect(vm.$emit).toHaveBeenCalledWith('toggleCollapsed');
});
});
...@@ -25,8 +25,12 @@ describe('RepoCommitSection', () => { ...@@ -25,8 +25,12 @@ describe('RepoCommitSection', () => {
return comp.$mount(); return comp.$mount();
} }
beforeEach(() => { beforeEach((done) => {
vm = createComponent(); vm = createComponent();
vm.collapsed = false;
Vue.nextTick(done);
}); });
afterEach(() => { afterEach(() => {
...@@ -36,12 +40,11 @@ describe('RepoCommitSection', () => { ...@@ -36,12 +40,11 @@ describe('RepoCommitSection', () => {
}); });
it('renders a commit section', () => { it('renders a commit section', () => {
const changedFileElements = [...vm.$el.querySelectorAll('.changed-files > li')]; const changedFileElements = [...vm.$el.querySelectorAll('.multi-file-commit-list li')];
const submitCommit = vm.$el.querySelector('.btn'); const submitCommit = vm.$el.querySelector('form .btn');
const targetBranch = vm.$el.querySelector('.target-branch');
expect(vm.$el.querySelector(':scope > form')).toBeTruthy(); expect(vm.$el.querySelector('.multi-file-commit-form')).not.toBeNull();
expect(vm.$el.querySelector('.staged-files').textContent.trim()).toEqual('Staged files (2)'); expect(vm.$el.querySelector('.multi-file-commit-panel-section header').textContent.trim()).toEqual('Staged');
expect(changedFileElements.length).toEqual(2); expect(changedFileElements.length).toEqual(2);
changedFileElements.forEach((changedFile, i) => { changedFileElements.forEach((changedFile, i) => {
...@@ -49,10 +52,7 @@ describe('RepoCommitSection', () => { ...@@ -49,10 +52,7 @@ describe('RepoCommitSection', () => {
}); });
expect(submitCommit.disabled).toBeTruthy(); expect(submitCommit.disabled).toBeTruthy();
expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeFalsy(); expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeNull();
expect(vm.$el.querySelector('.commit-summary').textContent.trim()).toEqual('Commit 2 files');
expect(targetBranch.querySelector(':scope > label').textContent.trim()).toEqual('Target branch');
expect(targetBranch.querySelector('.help-block').textContent.trim()).toEqual('master');
}); });
describe('when submitting', () => { describe('when submitting', () => {
...@@ -69,7 +69,7 @@ describe('RepoCommitSection', () => { ...@@ -69,7 +69,7 @@ describe('RepoCommitSection', () => {
}); });
it('allows you to submit', () => { it('allows you to submit', () => {
expect(vm.$el.querySelector('.btn').disabled).toBeTruthy(); expect(vm.$el.querySelector('form .btn').disabled).toBeTruthy();
}); });
it('submits commit', (done) => { it('submits commit', (done) => {
......
...@@ -29,7 +29,6 @@ describe('RepoSidebar', () => { ...@@ -29,7 +29,6 @@ describe('RepoSidebar', () => {
const thead = vm.$el.querySelector('thead'); const thead = vm.$el.querySelector('thead');
const tbody = vm.$el.querySelector('tbody'); const tbody = vm.$el.querySelector('tbody');
expect(vm.$el.id).toEqual('sidebar');
expect(vm.$el.classList.contains('sidebar-mini')).toBeFalsy(); expect(vm.$el.classList.contains('sidebar-mini')).toBeFalsy();
expect(thead.querySelector('.name').textContent.trim()).toEqual('Name'); expect(thead.querySelector('.name').textContent.trim()).toEqual('Name');
expect(thead.querySelector('.last-commit').textContent.trim()).toEqual('Last commit'); expect(thead.querySelector('.last-commit').textContent.trim()).toEqual('Last commit');
...@@ -40,18 +39,6 @@ describe('RepoSidebar', () => { ...@@ -40,18 +39,6 @@ describe('RepoSidebar', () => {
expect(tbody.querySelector('.file')).toBeTruthy(); expect(tbody.querySelector('.file')).toBeTruthy();
}); });
it('does not render a thead, renders repo-file-options and sets sidebar-mini class if isMini', (done) => {
vm.$store.state.openFiles.push(vm.$store.state.tree[0]);
Vue.nextTick(() => {
expect(vm.$el.classList.contains('sidebar-mini')).toBeTruthy();
expect(vm.$el.querySelector('thead')).toBeTruthy();
expect(vm.$el.querySelector('thead .repo-file-options')).toBeTruthy();
done();
});
});
it('renders 5 loading files if tree is loading', (done) => { it('renders 5 loading files if tree is loading', (done) => {
vm.$store.state.tree = []; vm.$store.state.tree = [];
vm.$store.state.loading = true; vm.$store.state.loading = true;
......
...@@ -24,8 +24,8 @@ describe('RepoTab', () => { ...@@ -24,8 +24,8 @@ describe('RepoTab', () => {
tab: file(), tab: file(),
}); });
vm.$store.state.openFiles.push(vm.tab); vm.$store.state.openFiles.push(vm.tab);
const close = vm.$el.querySelector('.close-btn'); const close = vm.$el.querySelector('.multi-file-tab-close');
const name = vm.$el.querySelector(`a[title="${vm.tab.url}"]`); const name = vm.$el.querySelector(`[title="${vm.tab.url}"]`);
expect(close.querySelector('.fa-times')).toBeTruthy(); expect(close.querySelector('.fa-times')).toBeTruthy();
expect(name.textContent.trim()).toEqual(vm.tab.name); expect(name.textContent.trim()).toEqual(vm.tab.name);
...@@ -50,7 +50,7 @@ describe('RepoTab', () => { ...@@ -50,7 +50,7 @@ describe('RepoTab', () => {
spyOn(vm, 'closeFile'); spyOn(vm, 'closeFile');
vm.$el.querySelector('.close-btn').click(); vm.$el.querySelector('.multi-file-tab-close').click();
expect(vm.closeFile).toHaveBeenCalledWith({ file: vm.tab }); expect(vm.closeFile).toHaveBeenCalledWith({ file: vm.tab });
}); });
...@@ -62,7 +62,7 @@ describe('RepoTab', () => { ...@@ -62,7 +62,7 @@ describe('RepoTab', () => {
tab, tab,
}); });
expect(vm.$el.querySelector('.close-btn .fa-circle')).toBeTruthy(); expect(vm.$el.querySelector('.multi-file-tab-close .fa-circle')).not.toBeNull();
}); });
describe('locked file', () => { describe('locked file', () => {
...@@ -107,7 +107,7 @@ describe('RepoTab', () => { ...@@ -107,7 +107,7 @@ describe('RepoTab', () => {
vm.$store.state.openFiles.push(tab); vm.$store.state.openFiles.push(tab);
vm.$store.dispatch('setFileActive', tab); vm.$store.dispatch('setFileActive', tab);
vm.$el.querySelector('.close-btn').click(); vm.$el.querySelector('.multi-file-tab-close').click();
vm.$nextTick(() => { vm.$nextTick(() => {
expect(tab.opened).toBeTruthy(); expect(tab.opened).toBeTruthy();
...@@ -125,7 +125,7 @@ describe('RepoTab', () => { ...@@ -125,7 +125,7 @@ describe('RepoTab', () => {
vm.$store.state.openFiles.push(tab); vm.$store.state.openFiles.push(tab);
vm.$store.dispatch('setFileActive', tab); vm.$store.dispatch('setFileActive', tab);
vm.$el.querySelector('.close-btn').click(); vm.$el.querySelector('.multi-file-tab-close').click();
vm.$nextTick(() => { vm.$nextTick(() => {
expect(tab.opened).toBeFalsy(); expect(tab.opened).toBeFalsy();
......
...@@ -25,12 +25,11 @@ describe('RepoTabs', () => { ...@@ -25,12 +25,11 @@ describe('RepoTabs', () => {
vm.$store.state.openFiles = openedFiles; vm.$store.state.openFiles = openedFiles;
vm.$nextTick(() => { vm.$nextTick(() => {
const tabs = [...vm.$el.querySelectorAll(':scope > li')]; const tabs = [...vm.$el.querySelectorAll('.multi-file-tab')];
expect(tabs.length).toEqual(3); expect(tabs.length).toEqual(2);
expect(tabs[0].classList.contains('active')).toBeTruthy(); expect(tabs[0].classList.contains('active')).toBeTruthy();
expect(tabs[1].classList.contains('active')).toBeFalsy(); expect(tabs[1].classList.contains('active')).toBeFalsy();
expect(tabs[2].classList.contains('tabs-divider')).toBeTruthy();
done(); done();
}); });
......
...@@ -116,4 +116,31 @@ describe('Multi-file store getters', () => { ...@@ -116,4 +116,31 @@ describe('Multi-file store getters', () => {
expect(getters.canEditFile(localState)).toBeFalsy(); expect(getters.canEditFile(localState)).toBeFalsy();
}); });
}); });
describe('modifiedFiles', () => {
it('returns a list of modified files', () => {
localState.openFiles.push(file());
localState.openFiles.push(file('changed'));
localState.openFiles[1].changed = true;
const modifiedFiles = getters.modifiedFiles(localState);
expect(modifiedFiles.length).toBe(1);
expect(modifiedFiles[0].name).toBe('changed');
});
});
describe('addedFiles', () => {
it('returns a list of added files', () => {
localState.openFiles.push(file());
localState.openFiles.push(file('added'));
localState.openFiles[1].changed = true;
localState.openFiles[1].tempFile = true;
const modifiedFiles = getters.addedFiles(localState);
expect(modifiedFiles.length).toBe(1);
expect(modifiedFiles[0].name).toBe('added');
});
});
}); });
require 'spec_helper' require 'spec_helper'
describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :truncate do describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :truncate, :migration, schema: 20171114162227 do
let(:merge_request_diffs) { table(:merge_request_diffs) }
let(:merge_requests) { table(:merge_requests) }
describe '#perform' do describe '#perform' do
let(:merge_request) { create(:merge_request) } let(:project) { create(:project, :repository) }
let(:merge_request_diff) { merge_request.merge_request_diff } let(:merge_request) { merge_requests.create!(iid: 1, target_project_id: project.id, source_project_id: project.id, target_branch: 'feature', source_branch: 'master').becomes(MergeRequest) }
let(:merge_request_diff) { MergeRequest.find(merge_request.id).create_merge_request_diff }
let(:updated_merge_request_diff) { MergeRequestDiff.find(merge_request_diff.id) } let(:updated_merge_request_diff) { MergeRequestDiff.find(merge_request_diff.id) }
def diffs_to_hashes(diffs) def diffs_to_hashes(diffs)
...@@ -68,7 +72,7 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :t ...@@ -68,7 +72,7 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :t
let(:stop_id) { described_class::MergeRequestDiff.maximum(:id) } let(:stop_id) { described_class::MergeRequestDiff.maximum(:id) }
before do before do
merge_request.reload_diff(true) merge_request.create_merge_request_diff
convert_to_yaml(start_id, merge_request_diff.commits, diffs_to_hashes(merge_request_diff.merge_request_diff_files)) convert_to_yaml(start_id, merge_request_diff.commits, diffs_to_hashes(merge_request_diff.merge_request_diff_files))
convert_to_yaml(stop_id, updated_merge_request_diff.commits, diffs_to_hashes(updated_merge_request_diff.merge_request_diff_files)) convert_to_yaml(stop_id, updated_merge_request_diff.commits, diffs_to_hashes(updated_merge_request_diff.merge_request_diff_files))
...@@ -288,7 +292,7 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :t ...@@ -288,7 +292,7 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :t
context 'when the merge request diffs are Rugged::Patch instances' do context 'when the merge request diffs are Rugged::Patch instances' do
let(:commits) { merge_request_diff.commits.map(&:to_hash) } let(:commits) { merge_request_diff.commits.map(&:to_hash) }
let(:first_commit) { merge_request.project.repository.commit(merge_request_diff.head_commit_sha) } let(:first_commit) { project.repository.commit(merge_request_diff.head_commit_sha) }
let(:expected_commits) { commits } let(:expected_commits) { commits }
let(:diffs) { first_commit.rugged_diff_from_parent.patches } let(:diffs) { first_commit.rugged_diff_from_parent.patches }
let(:expected_diffs) { [] } let(:expected_diffs) { [] }
...@@ -298,7 +302,7 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :t ...@@ -298,7 +302,7 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :t
context 'when the merge request diffs are Rugged::Diff::Delta instances' do context 'when the merge request diffs are Rugged::Diff::Delta instances' do
let(:commits) { merge_request_diff.commits.map(&:to_hash) } let(:commits) { merge_request_diff.commits.map(&:to_hash) }
let(:first_commit) { merge_request.project.repository.commit(merge_request_diff.head_commit_sha) } let(:first_commit) { project.repository.commit(merge_request_diff.head_commit_sha) }
let(:expected_commits) { commits } let(:expected_commits) { commits }
let(:diffs) { first_commit.rugged_diff_from_parent.deltas } let(:diffs) { first_commit.rugged_diff_from_parent.deltas }
let(:expected_diffs) { [] } let(:expected_diffs) { [] }
......
...@@ -62,12 +62,15 @@ describe Gitlab::BackgroundMigration::PopulateForkNetworksRange, :migration, sch ...@@ -62,12 +62,15 @@ describe Gitlab::BackgroundMigration::PopulateForkNetworksRange, :migration, sch
expect(base2_membership).not_to be_nil expect(base2_membership).not_to be_nil
end end
it 'skips links that had their source project deleted' do it 'creates a fork network for the fork of which the source was deleted' do
forked_project_links.create(id: 6, forked_from_project_id: 99999, forked_to_project_id: create(:project).id) fork = create(:project)
forked_project_links.create(id: 6, forked_from_project_id: 99999, forked_to_project_id: fork.id)
migration.perform(5, 8) migration.perform(5, 8)
expect(fork_networks.find_by(root_project_id: 99999)).to be_nil expect(fork_networks.find_by(root_project_id: 99999)).to be_nil
expect(fork_networks.find_by(root_project_id: fork.id)).not_to be_nil
expect(fork_network_members.find_by(project_id: fork.id)).not_to be_nil
end end
it 'schedules a job for inserting memberships for forks-of-forks' do it 'schedules a job for inserting memberships for forks-of-forks' do
......
...@@ -1771,9 +1771,9 @@ describe Gitlab::Diff::PositionTracer do ...@@ -1771,9 +1771,9 @@ describe Gitlab::Diff::PositionTracer do
describe "merge of target branch" do describe "merge of target branch" do
let(:merge_commit) do let(:merge_commit) do
update_file_again_commit second_create_file_commit
merge_request = create(:merge_request, source_branch: second_create_file_commit.sha, target_branch: branch_name, source_project: project) merge_request = create(:merge_request, source_branch: second_branch_name, target_branch: branch_name, source_project: project)
repository.merge(current_user, merge_request.diff_head_sha, merge_request, "Merge branches") repository.merge(current_user, merge_request.diff_head_sha, merge_request, "Merge branches")
......
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