Commit 75c02edb authored by GitLab Bot's avatar GitLab Bot

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

# Conflicts:
#	LICENSE

[ci skip]
parents 2963bc01 622aeea9
...@@ -41,7 +41,7 @@ gem 'omniauth-auth0', '~> 2.0.0' ...@@ -41,7 +41,7 @@ gem 'omniauth-auth0', '~> 2.0.0'
gem 'omniauth-azure-oauth2', '~> 0.0.9' gem 'omniauth-azure-oauth2', '~> 0.0.9'
gem 'omniauth-cas3', '~> 1.1.4' gem 'omniauth-cas3', '~> 1.1.4'
gem 'omniauth-facebook', '~> 4.0.0' gem 'omniauth-facebook', '~> 4.0.0'
gem 'omniauth-github', '~> 1.1.1' gem 'omniauth-github', '~> 1.3'
gem 'omniauth-gitlab', '~> 1.0.2' gem 'omniauth-gitlab', '~> 1.0.2'
gem 'omniauth-google-oauth2', '~> 0.5.3' gem 'omniauth-google-oauth2', '~> 0.5.3'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
......
...@@ -575,9 +575,9 @@ GEM ...@@ -575,9 +575,9 @@ GEM
omniauth (~> 1.2) omniauth (~> 1.2)
omniauth-facebook (4.0.0) omniauth-facebook (4.0.0)
omniauth-oauth2 (~> 1.2) omniauth-oauth2 (~> 1.2)
omniauth-github (1.1.2) omniauth-github (1.3.0)
omniauth (~> 1.0) omniauth (~> 1.5)
omniauth-oauth2 (~> 1.1) omniauth-oauth2 (>= 1.4.0, < 2.0)
omniauth-gitlab (1.0.2) omniauth-gitlab (1.0.2)
omniauth (~> 1.0) omniauth (~> 1.0)
omniauth-oauth2 (~> 1.0) omniauth-oauth2 (~> 1.0)
...@@ -1151,7 +1151,7 @@ DEPENDENCIES ...@@ -1151,7 +1151,7 @@ DEPENDENCIES
omniauth-azure-oauth2 (~> 0.0.9) omniauth-azure-oauth2 (~> 0.0.9)
omniauth-cas3 (~> 1.1.4) omniauth-cas3 (~> 1.1.4)
omniauth-facebook (~> 4.0.0) omniauth-facebook (~> 4.0.0)
omniauth-github (~> 1.1.1) omniauth-github (~> 1.3)
omniauth-gitlab (~> 1.0.2) omniauth-gitlab (~> 1.0.2)
omniauth-google-oauth2 (~> 0.5.3) omniauth-google-oauth2 (~> 0.5.3)
omniauth-kerberos (~> 0.3.0) omniauth-kerberos (~> 0.3.0)
......
...@@ -23,6 +23,7 @@ granted any other rights beyond what is expressly stated herein. Subject to the ...@@ -23,6 +23,7 @@ granted any other rights beyond what is expressly stated herein. Subject to the
foregoing, it is forbidden to copy, merge, publish, distribute, sublicense, foregoing, it is forbidden to copy, merge, publish, distribute, sublicense,
and/or sell the Software. and/or sell the Software.
<<<<<<< HEAD
This EE License applies only to the part of this Software that is not This EE License applies only to the part of this Software that is not
distributed as part of GitLab Community Edition (CE), and that is not a file distributed as part of GitLab Community Edition (CE), and that is not a file
that produces client-side JavaScript, in whole or in part. Any part of this that produces client-side JavaScript, in whole or in part. Any part of this
...@@ -42,6 +43,9 @@ THE SOFTWARE. ...@@ -42,6 +43,9 @@ THE SOFTWARE.
For all third party components incorporated into the GitLab Software, those For all third party components incorporated into the GitLab Software, those
components are licensed under the original license provided by the owner of the components are licensed under the original license provided by the owner of the
applicable component. applicable component.
=======
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
>>>>>>> upstream/master
--- ---
......
...@@ -43,7 +43,7 @@ export default { ...@@ -43,7 +43,7 @@ export default {
return `${this.changedIcon}-solid`; return `${this.changedIcon}-solid`;
}, },
changedIconClass() { changedIconClass() {
return `multi-${this.changedIcon} prepend-left-5 pull-left`; return `multi-${this.changedIcon} pull-left`;
}, },
tooltipTitle() { tooltipTitle() {
if (!this.showTooltip) return undefined; if (!this.showTooltip) return undefined;
...@@ -79,13 +79,7 @@ export default { ...@@ -79,13 +79,7 @@ export default {
class="ide-file-changed-icon" class="ide-file-changed-icon"
> >
<icon <icon
v-if="file.staged && showStagedIcon" v-if="file.changed || file.tempFile || file.staged"
:name="stagedIcon"
:size="12"
:css-classes="changedIconClass"
/>
<icon
v-if="file.changed || file.tempFile || (file.staged && !showStagedIcon)"
:name="changedIcon" :name="changedIcon"
:size="12" :size="12"
:css-classes="changedIconClass" :css-classes="changedIconClass"
......
...@@ -36,7 +36,7 @@ export default { ...@@ -36,7 +36,7 @@ export default {
return this.file.tempFile ? `file-addition${prefix}` : `file-modified${prefix}`; return this.file.tempFile ? `file-addition${prefix}` : `file-modified${prefix}`;
}, },
iconClass() { iconClass() {
return `multi-file-${this.file.tempFile ? 'additions' : 'modified'} append-right-8`; return `multi-file-${this.file.tempFile ? 'addition' : 'modified'} append-right-8`;
}, },
}, },
methods: { methods: {
......
<script> <script>
import { mapActions } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; import { n__, __, sprintf } from '~/locale';
import fileIcon from '~/vue_shared/components/file_icon.vue'; import tooltip from '~/vue_shared/directives/tooltip';
import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import Icon from '~/vue_shared/components/icon.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import router from '../ide_router'; import router from '../ide_router';
import newDropdown from './new_dropdown/index.vue'; import NewDropdown from './new_dropdown/index.vue';
import fileStatusIcon from './repo_file_status_icon.vue'; import FileStatusIcon from './repo_file_status_icon.vue';
import changedFileIcon from './changed_file_icon.vue'; import ChangedFileIcon from './changed_file_icon.vue';
import mrFileIcon from './mr_file_icon.vue'; import MrFileIcon from './mr_file_icon.vue';
export default { export default {
name: 'RepoFile', name: 'RepoFile',
directives: {
tooltip,
},
components: { components: {
skeletonLoadingContainer, SkeletonLoadingContainer,
newDropdown, NewDropdown,
fileStatusIcon, FileStatusIcon,
fileIcon, FileIcon,
changedFileIcon, ChangedFileIcon,
mrFileIcon, MrFileIcon,
Icon,
}, },
props: { props: {
file: { file: {
...@@ -29,6 +36,34 @@ export default { ...@@ -29,6 +36,34 @@ export default {
}, },
}, },
computed: { computed: {
...mapGetters([
'getChangesInFolder',
'getUnstagedFilesCountForPath',
'getStagedFilesCountForPath',
]),
folderUnstagedCount() {
return this.getUnstagedFilesCountForPath(this.file.path);
},
folderStagedCount() {
return this.getStagedFilesCountForPath(this.file.path);
},
changesCount() {
return this.getChangesInFolder(this.file.path);
},
folderChangesTooltip() {
if (this.changesCount === 0) return undefined;
if (this.folderUnstagedCount > 0 && this.folderStagedCount === 0) {
return n__('%d unstaged change', '%d unstaged changes', this.folderUnstagedCount);
} else if (this.folderUnstagedCount === 0 && this.folderStagedCount > 0) {
return n__('%d staged change', '%d staged changes', this.folderStagedCount);
}
return sprintf(__('%{unstaged} unstaged and %{staged} staged changes'), {
unstaged: this.folderUnstagedCount,
staged: this.folderStagedCount,
});
},
isTree() { isTree() {
return this.file.type === 'tree'; return this.file.type === 'tree';
}, },
...@@ -48,10 +83,19 @@ export default { ...@@ -48,10 +83,19 @@ export default {
'is-open': this.file.opened, 'is-open': this.file.opened,
}; };
}, },
showTreeChangesCount() {
return this.isTree && this.changesCount > 0 && !this.file.opened;
},
showChangedFileIcon() {
return this.file.changed || this.file.tempFile || this.file.staged;
},
}, },
updated() { updated() {
if (this.file.type === 'blob' && this.file.active) { if (this.file.type === 'blob' && this.file.active) {
this.$el.scrollIntoView(); this.$el.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
});
} }
}, },
methods: { methods: {
...@@ -101,8 +145,23 @@ export default { ...@@ -101,8 +145,23 @@ export default {
<mr-file-icon <mr-file-icon
v-if="file.mrChange" v-if="file.mrChange"
/> />
<span
v-if="showTreeChangesCount"
class="ide-tree-changes"
>
{{ changesCount }}
<icon
v-tooltip
:title="folderChangesTooltip"
data-container="body"
data-placement="right"
name="file-modified"
:size="12"
css-classes="prepend-left-5 multi-file-modified"
/>
</span>
<changed-file-icon <changed-file-icon
v-if="file.changed || file.tempFile || file.staged" v-else-if="showChangedFileIcon"
:file="file" :file="file"
:show-tooltip="true" :show-tooltip="true"
:show-staged-icon="true" :show-staged-icon="true"
......
import { __ } from '~/locale'; import { __ } from '~/locale';
import { getChangesCountForFiles, filePathMatches } from './utils';
export const activeFile = state => state.openFiles.find(file => file.active) || null; export const activeFile = state => state.openFiles.find(file => file.active) || null;
...@@ -55,7 +56,23 @@ export const allBlobs = state => ...@@ -55,7 +56,23 @@ export const allBlobs = state =>
}, []) }, [])
.sort((a, b) => b.lastOpenedAt - a.lastOpenedAt); .sort((a, b) => b.lastOpenedAt - a.lastOpenedAt);
export const getChangedFile = state => path => state.changedFiles.find(f => f.path === path);
export const getStagedFile = state => path => state.stagedFiles.find(f => f.path === path); export const getStagedFile = state => path => state.stagedFiles.find(f => f.path === path);
export const getChangesInFolder = state => path => {
const changedFilesCount = state.changedFiles.filter(f => filePathMatches(f, path)).length;
const stagedFilesCount = state.stagedFiles.filter(
f => filePathMatches(f, path) && !getChangedFile(state)(f.path),
).length;
return changedFilesCount + stagedFilesCount;
};
export const getUnstagedFilesCountForPath = state => path =>
getChangesCountForFiles(state.changedFiles, path);
export const getStagedFilesCountForPath = state => path =>
getChangesCountForFiles(state.stagedFiles, path);
// prevent babel-plugin-rewire from generating an invalid default during karma tests // prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {}; export default () => {};
...@@ -33,7 +33,6 @@ export const dataStructure = () => ({ ...@@ -33,7 +33,6 @@ export const dataStructure = () => ({
raw: '', raw: '',
content: '', content: '',
parentTreeUrl: '', parentTreeUrl: '',
parentPath: '',
renderError: false, renderError: false,
base64: false, base64: false,
editorRow: 1, editorRow: 1,
...@@ -43,6 +42,7 @@ export const dataStructure = () => ({ ...@@ -43,6 +42,7 @@ export const dataStructure = () => ({
viewMode: 'edit', viewMode: 'edit',
previewMode: null, previewMode: null,
size: 0, size: 0,
parentPath: null,
lastOpenedAt: 0, lastOpenedAt: 0,
}); });
...@@ -83,7 +83,6 @@ export const decorateData = entity => { ...@@ -83,7 +83,6 @@ export const decorateData = entity => {
opened, opened,
active, active,
parentTreeUrl, parentTreeUrl,
parentPath,
changed, changed,
renderError, renderError,
content, content,
...@@ -91,6 +90,7 @@ export const decorateData = entity => { ...@@ -91,6 +90,7 @@ export const decorateData = entity => {
previewMode, previewMode,
file_lock, file_lock,
html, html,
parentPath,
}; };
}; };
...@@ -137,3 +137,9 @@ export const sortTree = sortedTree => ...@@ -137,3 +137,9 @@ export const sortTree = sortedTree =>
}), }),
) )
.sort(sortTreesByTypeAndName); .sort(sortTreesByTypeAndName);
export const filePathMatches = (f, path) =>
f.path.replace(new RegExp(`${f.name}$`), '').indexOf(`${path}/`) === 0;
export const getChangesCountForFiles = (files, path) =>
files.filter(f => filePathMatches(f, path)).length;
...@@ -603,14 +603,14 @@ ...@@ -603,14 +603,14 @@
} }
} }
.multi-file-additions, .multi-file-addition,
.multi-file-additions-solid { .multi-file-addition-solid {
fill: $green-500; color: $green-500;
} }
.multi-file-modified, .multi-file-modified,
.multi-file-modified-solid { .multi-file-modified-solid {
fill: $orange-500; color: $orange-500;
} }
.multi-file-commit-list-collapsed { .multi-file-commit-list-collapsed {
...@@ -991,6 +991,12 @@ ...@@ -991,6 +991,12 @@
resize: none; resize: none;
} }
.ide-tree-changes {
display: flex;
align-items: center;
font-size: 12px;
}
.ide-new-modal-label { .ide-new-modal-label {
line-height: 34px; line-height: 34px;
} }
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
= link_to avatar_icon_for_user(@user, 400), target: '_blank', rel: 'noopener noreferrer' do = link_to avatar_icon_for_user(@user, 400), target: '_blank', rel: 'noopener noreferrer' do
= image_tag avatar_icon_for_user(@user, 90), class: "avatar s90", alt: '' = image_tag avatar_icon_for_user(@user, 90), class: "avatar s90", alt: ''
.user-info .user-info.prepend-left-default.append-right-default
.cover-title .cover-title
= @user.name = @user.name
......
---
title: Add padding to profile description
merge_request: 18663
author: George Tsiolis
type: changed
---
title: Output some useful information when running the rails console
merge_request: 18697
author:
type: added
# rubocop:disable Rails/Output
if defined?(Rails::Console)
# note that this will not print out when using `spring`
justify = 15
puts "-------------------------------------------------------------------------------------"
puts " Gitlab:".ljust(justify) + "#{Gitlab::VERSION} (#{Gitlab::REVISION})"
puts " Gitlab Shell:".ljust(justify) + Gitlab::Shell.new.version
puts " #{Gitlab::Database.adapter_name}:".ljust(justify) + Gitlab::Database.version
puts "-------------------------------------------------------------------------------------"
end
...@@ -48,6 +48,33 @@ describe('RepoFile', () => { ...@@ -48,6 +48,33 @@ describe('RepoFile', () => {
}); });
}); });
describe('folder', () => {
it('renders changes count inside folder', () => {
const f = {
...file('folder'),
path: 'testing',
type: 'tree',
branchId: 'master',
projectId: 'project',
};
store.state.changedFiles.push({
...file('fileName'),
path: 'testing/fileName',
});
createComponent({
file: f,
level: 0,
});
const treeChangesEl = vm.$el.querySelector('.ide-tree-changes');
expect(treeChangesEl).not.toBeNull();
expect(treeChangesEl.textContent).toContain('1');
});
});
describe('locked file', () => { describe('locked file', () => {
let f; let f;
...@@ -72,8 +99,7 @@ describe('RepoFile', () => { ...@@ -72,8 +99,7 @@ describe('RepoFile', () => {
it('renders a tooltip', () => { it('renders a tooltip', () => {
expect( expect(
vm.$el.querySelector('.ide-file-name span:nth-child(2)').dataset vm.$el.querySelector('.ide-file-name span:nth-child(2)').dataset.originalTitle,
.originalTitle,
).toContain('Locked by testuser'); ).toContain('Locked by testuser');
}); });
}); });
......
...@@ -84,4 +84,67 @@ describe('IDE store getters', () => { ...@@ -84,4 +84,67 @@ describe('IDE store getters', () => {
expect(getters.allBlobs(localState)[0].name).toBe('blob'); expect(getters.allBlobs(localState)[0].name).toBe('blob');
}); });
}); });
describe('getChangesInFolder', () => {
it('returns length of changed files for a path', () => {
localState.changedFiles.push(
{
path: 'test/index',
name: 'index',
},
{
path: 'app/123',
name: '123',
},
);
expect(getters.getChangesInFolder(localState)('test')).toBe(1);
});
it('returns length of changed & staged files for a path', () => {
localState.changedFiles.push(
{
path: 'test/index',
name: 'index',
},
{
path: 'testing/123',
name: '123',
},
);
localState.stagedFiles.push(
{
path: 'test/123',
name: '123',
},
{
path: 'test/index',
name: 'index',
},
{
path: 'testing/12345',
name: '12345',
},
);
expect(getters.getChangesInFolder(localState)('test')).toBe(2);
});
it('returns length of changed & tempFiles files for a path', () => {
localState.changedFiles.push(
{
path: 'test/index',
name: 'index',
},
{
path: 'test/newfile',
name: 'newfile',
tempFile: true,
},
);
expect(getters.getChangesInFolder(localState)('test')).toBe(2);
});
});
}); });
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