Merge branch 'master' into sh-support-bitbucket-server-import

This commit is contained in:
Stan Hu 2018-06-28 22:16:32 -07:00
commit f7d0ee1f09
164 changed files with 2564 additions and 1392 deletions

View File

@ -1 +1 @@
0.108.0
0.109.0

View File

@ -35,7 +35,7 @@ gem 'faraday', '~> 0.12'
# Authentication libraries
gem 'devise', '~> 4.4'
gem 'doorkeeper', '~> 4.3'
gem 'doorkeeper-openid_connect', '~> 1.3'
gem 'doorkeeper-openid_connect', '~> 1.5'
gem 'omniauth', '~> 1.8'
gem 'omniauth-auth0', '~> 2.0.0'
gem 'omniauth-azure-oauth2', '~> 0.0.9'

View File

@ -171,7 +171,7 @@ GEM
unf (>= 0.0.5, < 1.0.0)
doorkeeper (4.3.2)
railties (>= 4.2)
doorkeeper-openid_connect (1.4.0)
doorkeeper-openid_connect (1.5.0)
doorkeeper (~> 4.3)
json-jwt (~> 1.6)
dropzonejs-rails (0.7.2)
@ -427,12 +427,10 @@ GEM
oauth (~> 0.5, >= 0.5.0)
jquery-atwho-rails (1.3.2)
json (1.8.6)
json-jwt (1.9.2)
json-jwt (1.9.4)
activesupport
aes_key_wrap
bindata
securecompare
url_safe_base64
json-schema (2.8.0)
addressable (>= 2.4)
jwt (1.5.6)
@ -512,7 +510,7 @@ GEM
net-ldap (0.16.0)
net-ssh (5.0.1)
netrc (0.11.0)
nokogiri (1.8.2)
nokogiri (1.8.3)
mini_portile2 (~> 2.3.0)
nokogumbo (1.5.0)
nokogiri
@ -827,7 +825,6 @@ GEM
scss_lint (0.56.0)
rake (>= 0.9, < 13)
sass (~> 3.5.3)
securecompare (1.0.0)
seed-fu (2.3.7)
activerecord (>= 3.1)
activesupport (>= 3.1)
@ -939,7 +936,6 @@ GEM
equalizer (~> 0.0.9)
parser (>= 2.3.1.2, < 2.6)
procto (~> 0.0.2)
url_safe_base64 (0.2.2)
validates_hostname (1.0.6)
activerecord (>= 3.0)
activesupport (>= 3.0)
@ -1013,7 +1009,7 @@ DEPENDENCIES
devise-two-factor (~> 3.0.0)
diffy (~> 3.1.0)
doorkeeper (~> 4.3)
doorkeeper-openid_connect (~> 1.3)
doorkeeper-openid_connect (~> 1.5)
dropzonejs-rails (~> 0.7.1)
ed25519 (~> 1.2)
email_reply_trimmer (~> 0.1)

View File

@ -174,7 +174,7 @@ GEM
unf (>= 0.0.5, < 1.0.0)
doorkeeper (4.3.2)
railties (>= 4.2)
doorkeeper-openid_connect (1.4.0)
doorkeeper-openid_connect (1.5.0)
doorkeeper (~> 4.3)
json-jwt (~> 1.6)
dropzonejs-rails (0.7.2)
@ -430,12 +430,10 @@ GEM
oauth (~> 0.5, >= 0.5.0)
jquery-atwho-rails (1.3.2)
json (1.8.6)
json-jwt (1.9.2)
json-jwt (1.9.4)
activesupport
aes_key_wrap
bindata
securecompare
url_safe_base64
json-schema (2.8.0)
addressable (>= 2.4)
jwt (1.5.6)
@ -836,7 +834,6 @@ GEM
scss_lint (0.56.0)
rake (>= 0.9, < 13)
sass (~> 3.5.3)
securecompare (1.0.0)
seed-fu (2.3.7)
activerecord (>= 3.1)
activesupport (>= 3.1)
@ -946,7 +943,6 @@ GEM
equalizer (~> 0.0.9)
parser (>= 2.3.1.2, < 2.6)
procto (~> 0.0.2)
url_safe_base64 (0.2.2)
validates_hostname (1.0.6)
activerecord (>= 3.0)
activesupport (>= 3.0)
@ -1023,7 +1019,7 @@ DEPENDENCIES
devise-two-factor (~> 3.0.0)
diffy (~> 3.1.0)
doorkeeper (~> 4.3)
doorkeeper-openid_connect (~> 1.3)
doorkeeper-openid_connect (~> 1.5)
dropzonejs-rails (~> 0.7.1)
ed25519 (~> 1.2)
email_reply_trimmer (~> 0.1)

View File

@ -15,6 +15,8 @@
- [Between the 1st and the 7th](#between-the-1st-and-the-7th)
- [On the 7th](#on-the-7th)
- [After the 7th](#after-the-7th)
- [Regressions](#regressions)
- [How to manage a regression](#how-to-manage-a-regression)
- [Release retrospective and kickoff](#release-retrospective-and-kickoff)
- [Retrospective](#retrospective)
- [Kickoff](#kickoff)
@ -199,7 +201,7 @@ you can ask for an exception to be made.
Check [this guide](https://gitlab.com/gitlab-org/release/docs/blob/master/general/exception-request/process.md) about how to open an exception request before opening one.
### Regressions
## Regressions
A regression for a particular monthly release is a bug that exists in that
release, but wasn't present in the release before. This includes bugs in
@ -217,10 +219,30 @@ month. When we say 'the most recent monthly release', this can refer to either
the version currently running on GitLab.com, or the most recent version
available in the package repositories.
A regression issue should be labeled with the appropriate [subject label](../CONTRIBUTING.md#subject-labels-wiki-container-registry-ldap-api-etc)
and [team label](../CONTRIBUTING.md#team-labels-ci-discussion-edge-platform-etc),
just like any other issue, to help GitLab team members focus on issues that are
relevant to [their area of responsibility](https://about.gitlab.com/handbook/engineering/workflow/#choosing-something-to-work-on).
### How to manage a regression
Regressions are very important, and they should be considered high priority
issues that should be solved as soon as possible, especially if they affect
users. Despite that, ~regression label itself does not imply when the issue
will be scheduled.
When a regression is found:
1. Create an issue describing the problem in the most detailed way possible
1. If possible, provide links to real examples and how to reproduce the problem
1. Label the issue properly, using the [team label](../CONTRIBUTING.md#team-labels),
the [subject label](../CONTRIBUTING.md#subject-labels)
and any other label that may apply in the specific case
1. Add the ~bug and ~regression labels
1. Notify the respective Engineering Manager to evaluate the Severity of the regression and add a [Severity label](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#bug-severity-labels). The counterpart Product Manager is included to weigh-in on prioritization as needed to set the [Priority label](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#bug-priority-labels).
1. If the regression is either an ~S1, ~S2 or ~S3 severity, label the regression with the current milestone as it should be fixed in the current milestone.
1. If the regression was introduced in an RC of the current release, label with ~Deliverable
1. If the regression was introduced in the previous release, label with ~"Next Patch Release"
1. If the regression is an ~S4 severity, the regression may be scheduled for later milestones at the discretion of Engineering Manager and Product Manager.
When a new issue is found, the fix should start as soon as possible. You can
ping the Engineering Manager or the Product Manager for the relative area to
make them aware of the issue earlier. They will analyze the priority and change
it if needed.
## Release retrospective and kickoff

View File

@ -243,6 +243,15 @@ const Api = {
});
},
createBranch(id, { ref, branch }) {
const url = Api.buildUrl(this.createBranchPath).replace(':id', encodeURIComponent(id));
return axios.post(url, {
ref,
branch,
});
},
buildUrl(url) {
let urlRoot = '';
if (gon.relative_url_root != null) {

View File

@ -2,6 +2,7 @@
import $ from 'jquery';
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import eventHub from '../eventhub';
import tooltip from '../../vue_shared/directives/tooltip';
const Store = gl.issueBoards.BoardsStore;
@ -9,6 +10,9 @@
components: {
UserAvatarLink,
},
directives: {
tooltip,
},
props: {
issue: {
type: Object,
@ -166,9 +170,10 @@
tooltip-placement="bottom"
/>
<span
v-tooltip
v-if="shouldRenderCounter"
:title="assigneeCounterTooltip"
class="avatar-counter has-tooltip"
class="avatar-counter"
>
{{ assigneeCounterLabel }}
</span>
@ -179,12 +184,13 @@
class="board-card-footer"
>
<button
v-tooltip
v-for="label in issue.labels"
v-if="showLabel(label)"
:key="label.id"
:style="labelStyle(label)"
:title="label.description"
class="badge color-label has-tooltip"
class="badge color-label"
type="button"
data-container="body"
@click="filterByLabel(label, $event)"

View File

@ -4,14 +4,7 @@ import { s__ } from '~/locale';
import { mapState, mapGetters, mapActions } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import DiffGutterAvatars from './diff_gutter_avatars.vue';
import {
MATCH_LINE_TYPE,
CONTEXT_LINE_TYPE,
OLD_NO_NEW_LINE_TYPE,
NEW_NO_NEW_LINE_TYPE,
LINE_POSITION_RIGHT,
UNFOLD_COUNT,
} from '../constants';
import { LINE_POSITION_RIGHT, UNFOLD_COUNT } from '../constants';
import * as utils from '../store/utils';
export default {
@ -63,6 +56,21 @@ export default {
required: false,
default: false,
},
isMatchLine: {
type: Boolean,
required: false,
default: false,
},
isMetaLine: {
type: Boolean,
required: false,
default: false,
},
isContextLine: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
...mapState({
@ -70,15 +78,6 @@ export default {
diffFiles: state => state.diffs.diffFiles,
}),
...mapGetters(['isLoggedIn', 'discussionsByLineCode']),
isMatchLine() {
return this.lineType === MATCH_LINE_TYPE;
},
isContextLine() {
return this.lineType === CONTEXT_LINE_TYPE;
},
isMetaLine() {
return this.lineType === OLD_NO_NEW_LINE_TYPE || this.lineType === NEW_NO_NEW_LINE_TYPE;
},
lineHref() {
return this.lineCode ? `#${this.lineCode}` : '#';
},
@ -109,9 +108,9 @@ export default {
},
},
methods: {
...mapActions(['loadMoreLines']),
...mapActions(['loadMoreLines', 'showCommentForm']),
handleCommentButton() {
this.$emit('showCommentForm', { lineCode: this.lineCode });
this.showCommentForm({ lineCode: this.lineCode });
},
handleLoadMoreLines() {
if (this.isRequesting) {

View File

@ -0,0 +1,131 @@
<script>
import { mapGetters } from 'vuex';
import DiffLineGutterContent from './diff_line_gutter_content.vue';
import {
MATCH_LINE_TYPE,
CONTEXT_LINE_TYPE,
EMPTY_CELL_TYPE,
OLD_LINE_TYPE,
OLD_NO_NEW_LINE_TYPE,
NEW_NO_NEW_LINE_TYPE,
LINE_HOVER_CLASS_NAME,
LINE_UNFOLD_CLASS_NAME,
} from '../constants';
export default {
components: {
DiffLineGutterContent,
},
props: {
line: {
type: Object,
required: true,
},
diffFile: {
type: Object,
required: true,
},
showCommentButton: {
type: Boolean,
required: false,
default: false,
},
linePosition: {
type: String,
required: false,
default: '',
},
lineType: {
type: String,
required: false,
default: '',
},
isContentLine: {
type: Boolean,
required: false,
default: false,
},
isBottom: {
type: Boolean,
required: false,
default: false,
},
isHover: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
...mapGetters(['isLoggedIn', 'isInlineView']),
normalizedLine() {
if (this.isInlineView) {
return this.line;
}
return this.lineType === OLD_LINE_TYPE ? this.line.left : this.line.right;
},
isMatchLine() {
return this.normalizedLine.type === MATCH_LINE_TYPE;
},
isContextLine() {
return this.normalizedLine.type === CONTEXT_LINE_TYPE;
},
isMetaLine() {
return (
this.normalizedLine.type === OLD_NO_NEW_LINE_TYPE ||
this.normalizedLine.type === NEW_NO_NEW_LINE_TYPE ||
this.normalizedLine.type === EMPTY_CELL_TYPE
);
},
classNameMap() {
const { type } = this.normalizedLine;
return {
[type]: type,
[LINE_UNFOLD_CLASS_NAME]: this.isMatchLine,
[LINE_HOVER_CLASS_NAME]:
this.isLoggedIn &&
this.isHover &&
!this.isMatchLine &&
!this.isContextLine &&
!this.isMetaLine,
};
},
lineNumber() {
const { lineType, normalizedLine } = this;
return lineType === OLD_LINE_TYPE ? normalizedLine.oldLine : normalizedLine.newLine;
},
},
};
</script>
<template>
<td
v-if="isContentLine"
:class="lineType"
class="line_content"
v-html="normalizedLine.richText"
>
</td>
<td
v-else
:class="classNameMap"
>
<diff-line-gutter-content
:file-hash="diffFile.fileHash"
:line-type="normalizedLine.type"
:line-code="normalizedLine.lineCode"
:line-position="linePosition"
:line-number="lineNumber"
:meta-data="normalizedLine.metaData"
:show-comment-button="showCommentButton"
:context-lines-path="diffFile.contextLinesPath"
:is-bottom="isBottom"
:is-match-line="isMatchLine"
:is-context-line="isContentLine"
:is-meta-line="isMetaLine"
/>
</td>
</template>

View File

@ -0,0 +1,191 @@
<script>
import $ from 'jquery';
import { mapGetters } from 'vuex';
import DiffTableCell from './diff_table_cell.vue';
import {
NEW_LINE_TYPE,
OLD_LINE_TYPE,
CONTEXT_LINE_TYPE,
CONTEXT_LINE_CLASS_NAME,
OLD_NO_NEW_LINE_TYPE,
PARALLEL_DIFF_VIEW_TYPE,
NEW_NO_NEW_LINE_TYPE,
LINE_POSITION_LEFT,
LINE_POSITION_RIGHT,
} from '../constants';
export default {
components: {
DiffTableCell,
},
props: {
diffFile: {
type: Object,
required: true,
},
line: {
type: Object,
required: true,
},
isBottom: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
isHover: false,
isLeftHover: false,
isRightHover: false,
};
},
computed: {
...mapGetters(['isInlineView', 'isParallelView']),
isContextLine() {
return this.line.left
? this.line.left.type === CONTEXT_LINE_TYPE
: this.line.type === CONTEXT_LINE_TYPE;
},
classNameMap() {
return {
[this.line.type]: this.line.type,
[CONTEXT_LINE_CLASS_NAME]: this.isContextLine,
[PARALLEL_DIFF_VIEW_TYPE]: this.isParallelView,
};
},
inlineRowId() {
const { lineCode, oldLine, newLine } = this.line;
return lineCode || `${this.diffFile.fileHash}_${oldLine}_${newLine}`;
},
parallelViewLeftLineType() {
if (this.line.right.type === NEW_NO_NEW_LINE_TYPE) {
return OLD_NO_NEW_LINE_TYPE;
}
return this.line.left.type;
},
},
created() {
this.newLineType = NEW_LINE_TYPE;
this.oldLineType = OLD_LINE_TYPE;
this.linePositionLeft = LINE_POSITION_LEFT;
this.linePositionRight = LINE_POSITION_RIGHT;
},
methods: {
handleMouseMove(e) {
const isHover = e.type === 'mouseover';
if (this.isInlineView) {
this.isHover = isHover;
} else {
const hoveringCell = e.target.closest('td');
const allCellsInHoveringRow = Array.from(e.currentTarget.children);
const hoverIndex = allCellsInHoveringRow.indexOf(hoveringCell);
if (hoverIndex >= 2) {
this.isRightHover = isHover;
} else {
this.isLeftHover = isHover;
}
}
},
// Prevent text selecting on both sides of parallel diff view
// Backport of the same code from legacy diff notes.
handleParallelLineMouseDown(e) {
const line = $(e.currentTarget);
const table = line.closest('table');
table.removeClass('left-side-selected right-side-selected');
const [lineClass] = ['left-side', 'right-side'].filter(name => line.hasClass(name));
if (lineClass) {
table.addClass(`${lineClass}-selected`);
}
},
},
};
</script>
<template>
<tr
v-if="isInlineView"
:id="inlineRowId"
:class="classNameMap"
class="line_holder"
@mouseover="handleMouseMove"
@mouseout="handleMouseMove"
>
<diff-table-cell
:diff-file="diffFile"
:line="line"
:line-type="oldLineType"
:is-bottom="isBottom"
:is-hover="isHover"
:show-comment-button="true"
class="diff-line-num old_line"
/>
<diff-table-cell
:diff-file="diffFile"
:line="line"
:line-type="newLineType"
:is-bottom="isBottom"
:is-hover="isHover"
class="diff-line-num new_line"
/>
<diff-table-cell
:class="line.type"
:diff-file="diffFile"
:line="line"
:is-content-line="true"
/>
</tr>
<tr
v-else
:class="classNameMap"
class="line_holder"
@mouseover="handleMouseMove"
@mouseout="handleMouseMove"
>
<diff-table-cell
:diff-file="diffFile"
:line="line"
:line-type="oldLineType"
:line-position="linePositionLeft"
:is-bottom="isBottom"
:is-hover="isLeftHover"
:show-comment-button="true"
class="diff-line-num old_line"
/>
<diff-table-cell
:id="line.left.lineCode"
:diff-file="diffFile"
:line="line"
:is-content-line="true"
:line-type="parallelViewLeftLineType"
class="line_content parallel left-side"
@mousedown.native="handleParallelLineMouseDown"
/>
<diff-table-cell
:diff-file="diffFile"
:line="line"
:line-type="newLineType"
:line-position="linePositionRight"
:is-bottom="isBottom"
:is-hover="isRightHover"
:show-comment-button="true"
class="diff-line-num new_line"
/>
<diff-table-cell
:id="line.right.lineCode"
:diff-file="diffFile"
:line="line"
:is-content-line="true"
:line-type="line.right.type"
class="line_content parallel right-side"
@mousedown.native="handleParallelLineMouseDown"
/>
</tr>
</template>

View File

@ -0,0 +1,82 @@
<script>
import { mapState, mapGetters } from 'vuex';
import diffDiscussions from './diff_discussions.vue';
import diffLineNoteForm from './diff_line_note_form.vue';
export default {
components: {
diffDiscussions,
diffLineNoteForm,
},
props: {
line: {
type: Object,
required: true,
},
diffFile: {
type: Object,
required: true,
},
diffLines: {
type: Array,
required: true,
},
lineIndex: {
type: Number,
required: true,
},
},
computed: {
...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}),
...mapGetters(['discussionsByLineCode']),
isDiscussionExpanded() {
if (!this.discussions.length) {
return false;
}
return this.discussions.every(discussion => discussion.expanded);
},
hasCommentForm() {
return this.diffLineCommentForms[this.line.lineCode];
},
discussions() {
return this.discussionsByLineCode[this.line.lineCode] || [];
},
shouldRender() {
return this.isDiscussionExpanded || this.hasCommentForm;
},
className() {
return this.discussions.length ? '' : 'js-temp-notes-holder';
},
},
};
</script>
<template>
<tr
v-if="shouldRender"
:class="className"
class="notes_holder"
>
<td
class="notes_line"
colspan="2"
></td>
<td class="notes_content">
<div class="content">
<diff-discussions
:discussions="discussions"
/>
<diff-line-note-form
v-if="diffLineCommentForms[line.lineCode]"
:diff-file="diffFile"
:diff-lines="diffLines"
:line="line"
:note-target-line="diffLines[lineIndex]"
/>
</div>
</td>
</tr>
</template>

View File

@ -1,34 +1,12 @@
<script>
import diffContentMixin from '../mixins/diff_content';
import {
MATCH_LINE_TYPE,
CONTEXT_LINE_TYPE,
OLD_NO_NEW_LINE_TYPE,
NEW_NO_NEW_LINE_TYPE,
LINE_HOVER_CLASS_NAME,
LINE_UNFOLD_CLASS_NAME,
} from '../constants';
import inlineDiffCommentRow from './inline_diff_comment_row.vue';
export default {
mixins: [diffContentMixin],
methods: {
handleMouse(lineCode, isOver) {
this.hoveredLineCode = isOver ? lineCode : null;
},
getLineClass(line) {
const isSameLine = this.hoveredLineCode && this.hoveredLineCode === line.lineCode;
const isMatchLine = line.type === MATCH_LINE_TYPE;
const isContextLine = line.type === CONTEXT_LINE_TYPE;
const isMetaLine = line.type === OLD_NO_NEW_LINE_TYPE || line.type === NEW_NO_NEW_LINE_TYPE;
return {
[line.type]: line.type,
[LINE_UNFOLD_CLASS_NAME]: isMatchLine,
[LINE_HOVER_CLASS_NAME]:
this.isLoggedIn && isSameLine && !isMatchLine && !isContextLine && !isMetaLine,
};
},
components: {
inlineDiffCommentRow,
},
mixins: [diffContentMixin],
};
</script>
@ -41,76 +19,19 @@ export default {
<template
v-for="(line, index) in normalizedDiffLines"
>
<tr
:id="line.lineCode || `${fileHash}_${line.oldLine}_${line.newLine}`"
<diff-table-row
:diff-file="diffFile"
:line="line"
:is-bottom="index + 1 === diffLinesLength"
:key="line.lineCode"
:class="getRowClass(line)"
class="line_holder"
@mouseover="handleMouse(line.lineCode, true)"
@mouseout="handleMouse(line.lineCode, false)"
>
<td
:class="getLineClass(line)"
class="diff-line-num old_line"
>
<diff-line-gutter-content
:file-hash="fileHash"
:line-type="line.type"
:line-code="line.lineCode"
:line-number="line.oldLine"
:meta-data="line.metaData"
:show-comment-button="true"
:context-lines-path="diffFile.contextLinesPath"
:is-bottom="index + 1 === diffLinesLength"
@showCommentForm="handleShowCommentForm"
/>
</td>
<td
:class="getLineClass(line)"
class="diff-line-num new_line"
>
<diff-line-gutter-content
:file-hash="fileHash"
:line-type="line.type"
:line-code="line.lineCode"
:line-number="line.newLine"
:meta-data="line.metaData"
:is-bottom="index + 1 === diffLinesLength"
:context-lines-path="diffFile.contextLinesPath"
/>
</td>
<td
:class="line.type"
class="line_content"
v-html="line.richText"
>
</td>
</tr>
<tr
v-if="isDiscussionExpanded(line.lineCode) || diffLineCommentForms[line.lineCode]"
/>
<inline-diff-comment-row
:diff-file="diffFile"
:diff-lines="normalizedDiffLines"
:line="line"
:line-index="index"
:key="index"
:class="discussionsByLineCode[line.lineCode] ? '' : 'js-temp-notes-holder'"
class="notes_holder"
>
<td
class="notes_line"
colspan="2"
></td>
<td class="notes_content">
<div class="content">
<diff-discussions
:discussions="discussionsByLineCode[line.lineCode] || []"
/>
<diff-line-note-form
v-if="diffLineCommentForms[line.lineCode]"
:diff-file="diffFile"
:diff-lines="diffLines"
:line="line"
:note-target-line="diffLines[index]"
/>
</div>
</td>
</tr>
/>
</template>
</tbody>
</table>

View File

@ -0,0 +1,129 @@
<script>
import { mapState, mapGetters } from 'vuex';
import diffDiscussions from './diff_discussions.vue';
import diffLineNoteForm from './diff_line_note_form.vue';
export default {
components: {
diffDiscussions,
diffLineNoteForm,
},
props: {
line: {
type: Object,
required: true,
},
diffFile: {
type: Object,
required: true,
},
diffLines: {
type: Array,
required: true,
},
lineIndex: {
type: Number,
required: true,
},
},
computed: {
...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}),
...mapGetters(['discussionsByLineCode']),
leftLineCode() {
return this.line.left.lineCode;
},
rightLineCode() {
return this.line.right.lineCode;
},
hasDiscussion() {
const discussions = this.discussionsByLineCode;
return discussions[this.leftLineCode] || discussions[this.rightLineCode];
},
hasExpandedDiscussionOnLeft() {
const discussions = this.discussionsByLineCode[this.leftLineCode];
return discussions ? discussions.every(discussion => discussion.expanded) : false;
},
hasExpandedDiscussionOnRight() {
const discussions = this.discussionsByLineCode[this.rightLineCode];
return discussions ? discussions.every(discussion => discussion.expanded) : false;
},
hasAnyExpandedDiscussion() {
return this.hasExpandedDiscussionOnLeft || this.hasExpandedDiscussionOnRight;
},
shouldRenderDiscussionsRow() {
const hasDiscussion = this.hasDiscussion && this.hasAnyExpandedDiscussion;
const hasCommentFormOnLeft = this.diffLineCommentForms[this.leftLineCode];
const hasCommentFormOnRight = this.diffLineCommentForms[this.rightLineCode];
return hasDiscussion || hasCommentFormOnLeft || hasCommentFormOnRight;
},
shouldRenderDiscussionsOnLeft() {
return this.discussionsByLineCode[this.leftLineCode] && this.hasExpandedDiscussionOnLeft;
},
shouldRenderDiscussionsOnRight() {
return (
this.discussionsByLineCode[this.rightLineCode] &&
this.hasExpandedDiscussionOnRight &&
this.line.right.type
);
},
className() {
return this.hasDiscussion ? '' : 'js-temp-notes-holder';
},
},
};
</script>
<template>
<tr
v-if="shouldRenderDiscussionsRow"
:class="className"
class="notes_holder"
>
<td class="notes_line old"></td>
<td class="notes_content parallel old">
<div
v-if="shouldRenderDiscussionsOnLeft"
class="content"
>
<diff-discussions
:discussions="discussionsByLineCode[leftLineCode]"
/>
</div>
<diff-line-note-form
v-if="diffLineCommentForms[leftLineCode] &&
diffLineCommentForms[leftLineCode]"
:diff-file="diffFile"
:diff-lines="diffLines"
:line="line.left"
:note-target-line="diffLines[lineIndex].left"
position="left"
/>
</td>
<td class="notes_line new"></td>
<td class="notes_content parallel new">
<div
v-if="shouldRenderDiscussionsOnRight"
class="content"
>
<diff-discussions
:discussions="discussionsByLineCode[rightLineCode]"
/>
</div>
<diff-line-note-form
v-if="diffLineCommentForms[rightLineCode] &&
diffLineCommentForms[rightLineCode] && line.right.type"
:diff-file="diffFile"
:diff-lines="diffLines"
:line="line.right"
:note-target-line="diffLines[lineIndex].right"
position="right"
/>
</td>
</tr>
</template>

View File

@ -1,17 +1,12 @@
<script>
import diffContentMixin from '../mixins/diff_content';
import {
EMPTY_CELL_TYPE,
MATCH_LINE_TYPE,
CONTEXT_LINE_TYPE,
OLD_NO_NEW_LINE_TYPE,
NEW_NO_NEW_LINE_TYPE,
LINE_HOVER_CLASS_NAME,
LINE_UNFOLD_CLASS_NAME,
LINE_POSITION_RIGHT,
} from '../constants';
import parallelDiffCommentRow from './parallel_diff_comment_row.vue';
import { EMPTY_CELL_TYPE } from '../constants';
export default {
components: {
parallelDiffCommentRow,
},
mixins: [diffContentMixin],
computed: {
parallelDiffLines() {
@ -26,77 +21,6 @@ export default {
});
},
},
methods: {
hasDiscussion(line) {
const discussions = this.discussionsByLineCode;
const hasDiscussion = discussions[line.left.lineCode] || discussions[line.right.lineCode];
return hasDiscussion;
},
getClassName(line, position) {
const { type, lineCode } = line[position];
const isMatchLine = type === MATCH_LINE_TYPE;
const isContextLine = !isMatchLine && type !== EMPTY_CELL_TYPE && type !== CONTEXT_LINE_TYPE;
const isMetaLine = type === OLD_NO_NEW_LINE_TYPE || type === NEW_NO_NEW_LINE_TYPE;
const isSameLine = this.hoveredLineCode && this.hoveredLineCode === lineCode;
const isSameSection = position === this.hoveredSection;
return {
[type]: type,
[LINE_UNFOLD_CLASS_NAME]: isMatchLine,
[LINE_HOVER_CLASS_NAME]:
this.isLoggedIn && isContextLine && isSameLine && isSameSection && !isMetaLine,
};
},
handleMouse(e, line, isHover) {
if (isHover) {
const cell = e.target.closest('td');
if (this.$refs.leftLines.indexOf(cell) > -1) {
this.hoveredLineCode = line.left.lineCode;
this.hoveredSection = 'left';
} else if (this.$refs.rightLines.indexOf(cell) > -1) {
this.hoveredLineCode = line.right.lineCode;
this.hoveredSection = 'right';
}
} else {
this.hoveredLineCode = null;
this.hoveredSection = null;
}
},
shouldRenderDiscussionsRow(line) {
const hasDiscussion = this.hasDiscussion(line) && this.hasAnyExpandedDiscussion(line);
const hasCommentFormOnLeft = this.diffLineCommentForms[line.left.lineCode];
const hasCommentFormOnRight = this.diffLineCommentForms[line.right.lineCode];
return hasDiscussion || hasCommentFormOnLeft || hasCommentFormOnRight;
},
shouldRenderDiscussions(line, position) {
const { lineCode } = line[position];
let render = this.discussionsByLineCode[lineCode] && this.isDiscussionExpanded(lineCode);
// Avoid rendering context line discussions on the right side in parallel view
if (position === LINE_POSITION_RIGHT) {
render = render && line.right.type;
}
return render;
},
hasAnyExpandedDiscussion(line) {
const isLeftExpanded = this.isDiscussionExpanded(line.left.lineCode);
const isRightExpanded = this.isDiscussionExpanded(line.right.lineCode);
return isLeftExpanded || isRightExpanded;
},
getLineCode(line, side) {
const { lineCode } = side;
if (lineCode) {
return lineCode;
}
return `${this.fileHash}_${line.left.oldLine}_${line.right.newLine}`;
},
},
};
</script>
@ -104,119 +28,26 @@ export default {
<div
:class="userColorScheme"
:data-commit-id="commitId"
class="code diff-wrap-lines js-syntax-highlight text-file">
class="code diff-wrap-lines js-syntax-highlight text-file"
>
<table>
<tbody>
<template
v-for="(line, index) in parallelDiffLines"
>
<tr
<diff-table-row
:diff-file="diffFile"
:line="line"
:is-bottom="index + 1 === diffLinesLength"
:key="index"
:class="getRowClass(line)"
class="line_holder parallel"
@mouseover="handleMouse($event, line, true)"
@mouseout="handleMouse($event, line, false)"
>
<td
ref="leftLines"
:class="getClassName(line, 'left')"
class="diff-line-num old_line"
>
<diff-line-gutter-content
:file-hash="fileHash"
:line-type="line.left.type"
:line-code="line.left.lineCode"
:line-number="line.left.oldLine"
:meta-data="line.left.metaData"
:show-comment-button="true"
:context-lines-path="diffFile.contextLinesPath"
:is-bottom="index + 1 === diffLinesLength"
line-position="left"
@showCommentForm="handleShowCommentForm"
/>
</td>
<td
ref="leftLines"
:class="getClassName(line, 'left')"
:id="getLineCode(line, line.left)"
class="line_content parallel left-side"
v-html="line.left.richText"
>
</td>
<td
ref="rightLines"
:class="getClassName(line, 'right')"
class="diff-line-num new_line"
>
<diff-line-gutter-content
:file-hash="fileHash"
:line-type="line.right.type"
:line-code="line.right.lineCode"
:line-number="line.right.newLine"
:meta-data="line.right.metaData"
:show-comment-button="true"
:context-lines-path="diffFile.contextLinesPath"
:is-bottom="index + 1 === diffLinesLength"
line-position="right"
@showCommentForm="handleShowCommentForm"
/>
</td>
<td
ref="rightLines"
:class="getClassName(line, 'right')"
:id="getLineCode(line, line.right)"
class="line_content parallel right-side"
v-html="line.right.richText"
>
</td>
</tr>
<tr
v-if="shouldRenderDiscussionsRow(line)"
/>
<parallel-diff-comment-row
:key="line.left.lineCode || line.right.lineCode"
:class="hasDiscussion(line) ? '' : 'js-temp-notes-holder'"
class="notes_holder"
>
<td class="notes_line old"></td>
<td class="notes_content parallel old">
<div
v-if="shouldRenderDiscussions(line, 'left')"
class="content"
>
<diff-discussions
:discussions="discussionsByLineCode[line.left.lineCode]"
/>
</div>
<diff-line-note-form
v-if="diffLineCommentForms[line.left.lineCode] &&
diffLineCommentForms[line.left.lineCode]"
:diff-file="diffFile"
:diff-lines="diffLines"
:line="line.left"
:note-target-line="diffLines[index].left"
position="left"
/>
</td>
<td class="notes_line new"></td>
<td class="notes_content parallel new">
<div
v-if="shouldRenderDiscussions(line, 'right')"
class="content"
>
<diff-discussions
:discussions="discussionsByLineCode[line.right.lineCode]"
/>
</div>
<diff-line-note-form
v-if="diffLineCommentForms[line.right.lineCode] &&
diffLineCommentForms[line.right.lineCode] && line.right.type"
:diff-file="diffFile"
:diff-lines="diffLines"
:line="line.right"
:note-target-line="diffLines[index].right"
position="right"
/>
</td>
</tr>
:line="line"
:diff-file="diffFile"
:diff-lines="parallelDiffLines"
:line-index="index"
/>
</template>
</tbody>
</table>

View File

@ -14,6 +14,8 @@ export const TEXT_DIFF_POSITION_TYPE = 'text';
export const LINE_POSITION_LEFT = 'left';
export const LINE_POSITION_RIGHT = 'right';
export const LINE_SIDE_LEFT = 'left-side';
export const LINE_SIDE_RIGHT = 'right-side';
export const DIFF_VIEW_COOKIE_NAME = 'diff_view';
export const LINE_HOVER_CLASS_NAME = 'is-over';

View File

@ -1,9 +1,9 @@
import { mapState, mapGetters, mapActions } from 'vuex';
import { mapGetters } from 'vuex';
import diffDiscussions from '../components/diff_discussions.vue';
import diffLineGutterContent from '../components/diff_line_gutter_content.vue';
import diffLineNoteForm from '../components/diff_line_note_form.vue';
import diffTableRow from '../components/diff_table_row.vue';
import { trimFirstCharOfLineContent } from '../store/utils';
import { CONTEXT_LINE_TYPE, CONTEXT_LINE_CLASS_NAME } from '../constants';
export default {
props: {
@ -16,22 +16,14 @@ export default {
required: true,
},
},
data() {
return {
hoveredLineCode: null,
hoveredSection: null,
};
},
components: {
diffDiscussions,
diffTableRow,
diffLineNoteForm,
diffLineGutterContent,
},
computed: {
...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}),
...mapGetters(['discussionsByLineCode', 'isLoggedIn', 'commit']),
...mapGetters(['commit']),
commitId() {
return this.commit && this.commit.id;
},
@ -41,15 +33,15 @@ export default {
normalizedDiffLines() {
return this.diffLines.map(line => {
if (line.richText) {
return this.trimFirstChar(line);
return trimFirstCharOfLineContent(line);
}
if (line.left) {
Object.assign(line, { left: this.trimFirstChar(line.left) });
Object.assign(line, { left: trimFirstCharOfLineContent(line.left) });
}
if (line.right) {
Object.assign(line, { right: this.trimFirstChar(line.right) });
Object.assign(line, { right: trimFirstCharOfLineContent(line.right) });
}
return line;
@ -62,28 +54,4 @@ export default {
return this.diffFile.fileHash;
},
},
methods: {
...mapActions(['showCommentForm', 'cancelCommentForm']),
getRowClass(line) {
const isContextLine = line.left
? line.left.type === CONTEXT_LINE_TYPE
: line.type === CONTEXT_LINE_TYPE;
return {
[line.type]: line.type,
[CONTEXT_LINE_CLASS_NAME]: isContextLine,
};
},
trimFirstChar(line) {
return trimFirstCharOfLineContent(line);
},
handleShowCommentForm(params) {
this.showCommentForm({ lineCode: params.lineCode });
},
isDiscussionExpanded(lineCode) {
const discussions = this.discussionsByLineCode[lineCode];
return discussions ? discussions.every(discussion => discussion.expanded) : false;
},
},
};

View File

@ -10,7 +10,7 @@ export default {
},
computed: {
...mapState(['currentBranchId', 'changedFiles', 'stagedFiles']),
...mapGetters(['currentProject']),
...mapGetters(['currentProject', 'currentBranch']),
commitToCurrentBranchText() {
return sprintf(
__('Commit to %{branchName} branch'),
@ -22,17 +22,30 @@ export default {
return this.changedFiles.length > 0 && this.stagedFiles.length > 0;
},
},
watch: {
disableMergeRequestRadio() {
this.updateSelectedCommitAction();
},
},
mounted() {
if (this.disableMergeRequestRadio) {
this.updateCommitAction(consts.COMMIT_TO_CURRENT_BRANCH);
}
this.updateSelectedCommitAction();
},
methods: {
...mapActions('commit', ['updateCommitAction']),
updateSelectedCommitAction() {
if (this.currentBranch && !this.currentBranch.can_push) {
this.updateCommitAction(consts.COMMIT_TO_NEW_BRANCH);
} else if (this.disableMergeRequestRadio) {
this.updateCommitAction(consts.COMMIT_TO_CURRENT_BRANCH);
}
},
},
commitToCurrentBranch: consts.COMMIT_TO_CURRENT_BRANCH,
commitToNewBranch: consts.COMMIT_TO_NEW_BRANCH,
commitToNewBranchMR: consts.COMMIT_TO_NEW_BRANCH_MR,
currentBranchPermissionsTooltip: __(
"This option is disabled as you don't have write permissions for the current branch",
),
};
</script>
@ -40,9 +53,11 @@ export default {
<div class="append-bottom-15 ide-commit-radios">
<radio-group
:value="$options.commitToCurrentBranch"
:checked="true"
:disabled="currentBranch && !currentBranch.can_push"
:title="$options.currentBranchPermissionsTooltip"
>
<span
class="ide-radio-label"
v-html="commitToCurrentBranchText"
>
</span>
@ -56,6 +71,7 @@ export default {
v-if="currentProject.merge_requests_enabled"
:value="$options.commitToNewBranchMR"
:label="__('Create a new branch and merge request')"
:title="__('This option is disabled while you still have unstaged changes')"
:show-input="true"
:disabled="disableMergeRequestRadio"
/>

View File

@ -24,7 +24,7 @@ export default {
...mapState(['changedFiles', 'stagedFiles', 'currentActivityView', 'lastCommitMsg']),
...mapState('commit', ['commitMessage', 'submitCommitLoading']),
...mapGetters(['hasChanges']),
...mapGetters('commit', ['commitButtonDisabled', 'discardDraftButtonDisabled']),
...mapGetters('commit', ['discardDraftButtonDisabled', 'preBuiltCommitMessage']),
overviewText() {
return sprintf(
__(
@ -36,6 +36,9 @@ export default {
},
);
},
commitButtonText() {
return this.stagedFiles.length ? __('Commit') : __('Stage & Commit');
},
},
watch: {
currentActivityView() {
@ -136,14 +139,14 @@ export default {
</transition>
<commit-message-field
:text="commitMessage"
:placeholder="preBuiltCommitMessage"
@input="updateCommitMessage"
/>
<div class="clearfix prepend-top-15">
<actions />
<loading-button
:loading="submitCommitLoading"
:disabled="commitButtonDisabled"
:label="__('Commit')"
:label="commitButtonText"
container-class="btn btn-success btn-sm float-left"
@click="commitChanges"
/>

View File

@ -16,6 +16,10 @@ export default {
type: String,
required: true,
},
placeholder: {
type: String,
required: true,
},
},
data() {
return {
@ -114,7 +118,7 @@ export default {
</div>
<textarea
ref="textarea"
:placeholder="__('Write a commit message...')"
:placeholder="placeholder"
:value="text"
class="note-textarea ide-commit-message-textarea"
name="commit-message"

View File

@ -1,6 +1,5 @@
<script>
import { mapActions, mapState, mapGetters } from 'vuex';
import { __ } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
export default {
@ -32,14 +31,17 @@ export default {
required: false,
default: false,
},
title: {
type: String,
required: false,
default: '',
},
},
computed: {
...mapState('commit', ['commitAction']),
...mapGetters('commit', ['newBranchName']),
tooltipTitle() {
return this.disabled
? __('This option is disabled while you still have unstaged changes')
: '';
return this.disabled ? this.title : '';
},
},
methods: {

View File

@ -0,0 +1,69 @@
<script>
import { mapActions } from 'vuex';
import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
components: {
LoadingIcon,
},
props: {
message: {
type: Object,
required: true,
},
},
data() {
return {
isLoading: false,
};
},
methods: {
...mapActions(['setErrorMessage']),
clickAction() {
if (this.isLoading) return;
this.isLoading = true;
this.$store
.dispatch(this.message.action, this.message.actionPayload)
.then(() => {
this.isLoading = false;
})
.catch(() => {
this.isLoading = false;
});
},
clickFlash() {
if (!this.message.action) {
this.setErrorMessage(null);
}
},
},
};
</script>
<template>
<div
class="flash-container flash-container-page"
@click="clickFlash"
>
<div class="flash-alert">
<span
v-html="message.text"
>
</span>
<button
v-if="message.action"
type="button"
class="flash-action text-white p-0 border-top-0 border-right-0 border-left-0 bg-transparent"
@click.stop.prevent="clickAction"
>
{{ message.actionText }}
<loading-icon
v-show="isLoading"
inline
/>
</button>
</div>
</div>
</template>

View File

@ -7,6 +7,7 @@ import IdeStatusBar from './ide_status_bar.vue';
import RepoEditor from './repo_editor.vue';
import FindFile from './file_finder/index.vue';
import RightPane from './panes/right.vue';
import ErrorMessage from './error_message.vue';
const originalStopCallback = Mousetrap.stopCallback;
@ -18,6 +19,7 @@ export default {
RepoEditor,
FindFile,
RightPane,
ErrorMessage,
},
computed: {
...mapState([
@ -28,6 +30,7 @@ export default {
'fileFindVisible',
'emptyStateSvgPath',
'currentProjectId',
'errorMessage',
]),
...mapGetters(['activeFile', 'hasChanges']),
},
@ -72,6 +75,10 @@ export default {
<template>
<article class="ide">
<error-message
v-if="errorMessage"
:message="errorMessage"
/>
<div
class="ide-view"
>

View File

@ -28,7 +28,7 @@ export default {
]),
...mapState('commit', ['commitMessage', 'submitCommitLoading']),
...mapGetters(['lastOpenedFile', 'hasChanges', 'someUncommitedChanges', 'activeFile']),
...mapGetters('commit', ['commitButtonDisabled', 'discardDraftButtonDisabled']),
...mapGetters('commit', ['discardDraftButtonDisabled']),
showStageUnstageArea() {
return !!(this.someUncommitedChanges || this.lastCommitMsg || !this.unusedSeal);
},

View File

@ -95,14 +95,6 @@ router.beforeEach((to, from, next) => {
}
})
.catch(e => {
flash(
'Error while loading the branch files. Please try again.',
'alert',
document,
null,
false,
true,
);
throw e;
});
} else if (to.params.mrid) {

View File

@ -1,5 +1,6 @@
import Vue from 'vue';
import VueResource from 'vue-resource';
import axios from '~/lib/utils/axios_utils';
import Api from '~/api';
Vue.use(VueResource);
@ -69,11 +70,7 @@ export default {
},
getFiles(projectUrl, branchId) {
const url = `${projectUrl}/files/${branchId}`;
return Vue.http.get(url, {
params: {
format: 'json',
},
});
return axios.get(url, { params: { format: 'json' } });
},
lastCommitPipelines({ getters }) {
const commitSha = getters.lastCommit.id;

View File

@ -175,6 +175,9 @@ export const setRightPane = ({ commit }, view) => {
export const setLinks = ({ commit }, links) => commit(types.SET_LINKS, links);
export const setErrorMessage = ({ commit }, errorMessage) =>
commit(types.SET_ERROR_MESSAGE, errorMessage);
export * from './actions/tree';
export * from './actions/file';
export * from './actions/project';

View File

@ -1,7 +1,10 @@
import _ from 'underscore';
import flash from '~/flash';
import { __ } from '~/locale';
import { __, sprintf } from '~/locale';
import service from '../../services';
import api from '../../../api';
import * as types from '../mutation_types';
import router from '../../ide_router';
export const getProjectData = ({ commit, state }, { namespace, projectId, force = false } = {}) =>
new Promise((resolve, reject) => {
@ -32,7 +35,10 @@ export const getProjectData = ({ commit, state }, { namespace, projectId, force
}
});
export const getBranchData = ({ commit, state }, { projectId, branchId, force = false } = {}) =>
export const getBranchData = (
{ commit, dispatch, state },
{ projectId, branchId, force = false } = {},
) =>
new Promise((resolve, reject) => {
if (
typeof state.projects[`${projectId}`] === 'undefined' ||
@ -51,15 +57,19 @@ export const getBranchData = ({ commit, state }, { projectId, branchId, force =
commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id });
resolve(data);
})
.catch(() => {
flash(
__('Error loading branch data. Please try again.'),
'alert',
document,
null,
false,
true,
);
.catch(e => {
if (e.response.status === 404) {
dispatch('showBranchNotFoundError', branchId);
} else {
flash(
__('Error loading branch data. Please try again.'),
'alert',
document,
null,
false,
true,
);
}
reject(new Error(`Branch not loaded - ${projectId}/${branchId}`));
});
} else {
@ -80,3 +90,37 @@ export const refreshLastCommitData = ({ commit }, { projectId, branchId } = {})
.catch(() => {
flash(__('Error loading last commit.'), 'alert', document, null, false, true);
});
export const createNewBranchFromDefault = ({ state, dispatch, getters }, branch) =>
api
.createBranch(state.currentProjectId, {
ref: getters.currentProject.default_branch,
branch,
})
.then(() => {
dispatch('setErrorMessage', null);
router.push(`${router.currentRoute.path}?${Date.now()}`);
})
.catch(() => {
dispatch('setErrorMessage', {
text: __('An error occured creating the new branch.'),
action: 'createNewBranchFromDefault',
actionText: __('Please try again'),
actionPayload: branch,
});
});
export const showBranchNotFoundError = ({ dispatch }, branchId) => {
dispatch('setErrorMessage', {
text: sprintf(
__("Branch %{branchName} was not found in this project's repository."),
{
branchName: `<strong>${_.escape(branchId)}</strong>`,
},
false,
),
action: 'createNewBranchFromDefault',
actionText: __('Create branch'),
actionPayload: branchId,
});
};

View File

@ -1,5 +1,6 @@
import { normalizeHeaders } from '~/lib/utils/common_utils';
import flash from '~/flash';
import { __ } from '../../../locale';
import service from '../../services';
import * as types from '../mutation_types';
import { findEntry } from '../utils';
@ -62,16 +63,19 @@ export const getLastCommitData = ({ state, commit, dispatch }, tree = state) =>
.catch(() => flash('Error fetching log data.', 'alert', document, null, false, true));
};
export const getFiles = ({ state, commit }, { projectId, branchId } = {}) =>
export const getFiles = ({ state, commit, dispatch }, { projectId, branchId } = {}) =>
new Promise((resolve, reject) => {
if (!state.trees[`${projectId}/${branchId}`]) {
if (
!state.trees[`${projectId}/${branchId}`] ||
(state.trees[`${projectId}/${branchId}`].tree &&
state.trees[`${projectId}/${branchId}`].tree.length === 0)
) {
const selectedProject = state.projects[projectId];
commit(types.CREATE_TREE, { treePath: `${projectId}/${branchId}` });
service
.getFiles(selectedProject.web_url, branchId)
.then(res => res.json())
.then(data => {
.then(({ data }) => {
const worker = new FilesDecoratorWorker();
worker.addEventListener('message', e => {
const { entries, treeList } = e.data;
@ -99,7 +103,18 @@ export const getFiles = ({ state, commit }, { projectId, branchId } = {}) =>
});
})
.catch(e => {
flash('Error loading tree data. Please try again.', 'alert', document, null, false, true);
if (e.response.status === 404) {
dispatch('showBranchNotFoundError', branchId);
} else {
flash(
__('Error loading tree data. Please try again.'),
'alert',
document,
null,
false,
true,
);
}
reject(e);
});
} else {

View File

@ -82,10 +82,13 @@ export const getStagedFilesCountForPath = state => path =>
getChangesCountForFiles(state.stagedFiles, path);
export const lastCommit = (state, getters) => {
const branch = getters.currentProject && getters.currentProject.branches[state.currentBranchId];
const branch = getters.currentProject && getters.currentBranch;
return branch ? branch.commit : null;
};
export const currentBranch = (state, getters) =>
getters.currentProject && getters.currentProject.branches[state.currentBranchId];
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};

View File

@ -103,17 +103,24 @@ export const updateFilesAfterCommit = ({ commit, dispatch, rootState }, { data }
export const commitChanges = ({ commit, state, getters, dispatch, rootState, rootGetters }) => {
const newBranch = state.commitAction !== consts.COMMIT_TO_CURRENT_BRANCH;
const payload = createCommitPayload({
branch: getters.branchName,
newBranch,
state,
rootState,
});
const stageFilesPromise = rootState.stagedFiles.length
? Promise.resolve()
: dispatch('stageAllChanges', null, { root: true });
commit(types.UPDATE_LOADING, true);
return service
.commit(rootState.currentProjectId, payload)
return stageFilesPromise
.then(() => {
const payload = createCommitPayload({
branch: getters.branchName,
newBranch,
getters,
state,
rootState,
});
return service.commit(rootState.currentProjectId, payload);
})
.then(({ data }) => {
commit(types.UPDATE_LOADING, false);

View File

@ -1,3 +1,4 @@
import { sprintf, n__ } from '../../../../locale';
import * as consts from './constants';
const BRANCH_SUFFIX_COUNT = 5;
@ -5,9 +6,6 @@ const BRANCH_SUFFIX_COUNT = 5;
export const discardDraftButtonDisabled = state =>
state.commitMessage === '' || state.submitCommitLoading;
export const commitButtonDisabled = (state, getters, rootState) =>
getters.discardDraftButtonDisabled || !rootState.stagedFiles.length;
export const newBranchName = (state, _, rootState) =>
`${gon.current_username}-${rootState.currentBranchId}-patch-${`${new Date().getTime()}`.substr(
-BRANCH_SUFFIX_COUNT,
@ -28,5 +26,18 @@ export const branchName = (state, getters, rootState) => {
return rootState.currentBranchId;
};
export const preBuiltCommitMessage = (state, _, rootState) => {
if (state.commitMessage) return state.commitMessage;
const files = (rootState.stagedFiles.length
? rootState.stagedFiles
: rootState.changedFiles
).reduce((acc, val) => acc.concat(val.path), []);
return sprintf(n__('Update %{files}', 'Update %{files} files', files.length), {
files: files.join(', '),
});
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};

View File

@ -72,3 +72,5 @@ export const SET_RIGHT_PANE = 'SET_RIGHT_PANE';
export const CLEAR_PROJECTS = 'CLEAR_PROJECTS';
export const RESET_OPEN_FILES = 'RESET_OPEN_FILES';
export const SET_ERROR_MESSAGE = 'SET_ERROR_MESSAGE';

View File

@ -163,6 +163,9 @@ export default {
[types.RESET_OPEN_FILES](state) {
Object.assign(state, { openFiles: [] });
},
[types.SET_ERROR_MESSAGE](state, errorMessage) {
Object.assign(state, { errorMessage });
},
...projectMutations,
...mergeRequestMutation,
...fileMutations,

View File

@ -25,4 +25,5 @@ export default () => ({
fileFindVisible: false,
rightPane: null,
links: {},
errorMessage: null,
});

View File

@ -105,9 +105,9 @@ export const setPageTitle = title => {
document.title = title;
};
export const createCommitPayload = ({ branch, newBranch, state, rootState }) => ({
export const createCommitPayload = ({ branch, getters, newBranch, state, rootState }) => ({
branch,
commit_message: state.commitMessage,
commit_message: state.commitMessage || getters.preBuiltCommitMessage,
actions: rootState.stagedFiles.map(f => ({
action: f.tempFile ? 'create' : 'update',
file_path: f.path,

View File

@ -164,7 +164,7 @@ export default class Job extends LogOutputBehaviours {
// eslint-disable-next-line class-methods-use-this
shouldHideSidebarForViewport() {
const bootstrapBreakpoint = bp.getBreakpointSize();
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
return bootstrapBreakpoint === 'xs';
}
toggleSidebar(shouldHide) {

View File

@ -200,6 +200,7 @@ export default {
:class="getAwardClassBindings(awardList, awardName)"
:title="awardTitle(awardList)"
class="btn award-control"
data-boundary="viewport"
data-placement="bottom"
type="button"
@click="handleAward(awardName)">
@ -217,6 +218,7 @@ export default {
class="award-control btn js-add-award"
title="Add reaction"
aria-label="Add reaction"
data-boundary="viewport"
data-placement="bottom"
type="button">
<span

View File

@ -48,7 +48,7 @@ export default class Wikis {
static sidebarCanCollapse() {
const bootstrapBreakpoint = bp.getBreakpointSize();
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
return bootstrapBreakpoint === 'xs';
}
renderSidebar() {

View File

@ -113,7 +113,7 @@ export default {
>
<div
v-if="currentRequest"
class="container-fluid container-limited"
class="d-flex container-fluid container-limited"
>
<div
id="peek-view-host"
@ -179,6 +179,7 @@ export default {
v-if="currentRequest"
:current-request="currentRequest"
:requests="requests"
class="ml-auto"
@change-current-request="changeCurrentRequest"
/>
</div>

View File

@ -35,10 +35,7 @@ export default {
};
</script>
<template>
<div
id="peek-request-selector"
class="float-right"
>
<div id="peek-request-selector">
<select v-model="currentRequestId">
<option
v-for="request in requests"

View File

@ -61,7 +61,13 @@ export default class Profile {
url: this.form.attr('action'),
data: formData,
})
.then(({ data }) => flash(data.message, 'notice'))
.then(({ data }) => {
if (avatarBlob != null) {
this.updateHeaderAvatar();
}
flash(data.message, 'notice');
})
.then(() => {
window.scrollTo(0, 0);
// Enable submit button after requests ends
@ -70,6 +76,10 @@ export default class Profile {
.catch(error => flash(error.message));
}
updateHeaderAvatar() {
$('.header-user-avatar').attr('src', this.avatarGlCrop.dataURL);
}
setRepoRadio() {
const multiEditRadios = $('input[name="user[multi_file]"]');
if (this.newRepoActivated || this.newRepoActivated === 'true') {

View File

@ -104,6 +104,10 @@
position: relative;
top: 3px;
}
> gl-emoji {
line-height: 1.5;
}
}
.award-menu-holder {

View File

@ -4,4 +4,5 @@ gl-emoji {
vertical-align: middle;
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 1.5em;
line-height: 0.9;
}

View File

@ -42,7 +42,7 @@
display: inline-block;
}
a.flash-action {
.flash-action {
margin-left: 5px;
text-decoration: none;
font-weight: $gl-font-weight-normal;

View File

@ -233,7 +233,7 @@ $md-area-border: #ddd;
/*
* Code
*/
$code_font_size: 12px;
$code_font_size: 90%;
$code_line_height: 1.6;
/*

View File

@ -93,6 +93,10 @@
font-size: 12px;
}
}
svg {
vertical-align: text-top;
}
}
.light-well {

View File

@ -102,7 +102,9 @@ pre.code,
// Diff line
.line_holder {
&.match .line_content {
&.match .line_content,
.new-nonewline.line_content,
.old-nonewline.line_content {
@include matchLine;
}

View File

@ -698,7 +698,7 @@
&.diff-files-changed-merge-request {
position: sticky;
top: 90px;
z-index: 190;
z-index: 200;
margin: $gl-padding 0;
padding: 0;
}
@ -706,6 +706,7 @@
&.is-stuck {
padding-top: 0;
padding-bottom: 0;
border-top: 1px solid $white-dark;
border-bottom: 1px solid $white-dark;
.diff-stats-additions-deletions-expanded,

View File

@ -721,7 +721,7 @@ ul.notes {
.line-resolve-all {
vertical-align: middle;
display: inline-block;
padding: 6px 10px;
padding: 5px 10px 6px;
background-color: $gray-light;
border: 1px solid $border-color;
border-radius: $border-radius-default;

View File

@ -7,7 +7,6 @@
top: 0;
width: 100%;
z-index: 2000;
overflow-x: hidden;
height: $performance-bar-height;
background: $black;
@ -82,7 +81,7 @@
.view {
margin-right: 15px;
float: left;
flex-shrink: 0;
&:last-child {
margin-right: 0;

View File

@ -1,6 +1,7 @@
module Types
class BaseObject < GraphQL::Schema::Object
prepend Gitlab::Graphql::Present
prepend Gitlab::Graphql::ExposePermissions
field_class Types::BaseField
end

View File

@ -1,5 +1,7 @@
module Types
class MergeRequestType < BaseObject
expose_permissions Types::PermissionTypes::MergeRequest
present_using MergeRequestPresenter
graphql_name 'MergeRequest'

View File

@ -0,0 +1,38 @@
module Types
module PermissionTypes
class BasePermissionType < BaseObject
extend Gitlab::Allowable
RESOLVING_KEYWORDS = [:resolver, :method, :hash_key, :function].to_set.freeze
def self.abilities(*abilities)
abilities.each { |ability| ability_field(ability) }
end
def self.ability_field(ability, **kword_args)
unless resolving_keywords?(kword_args)
kword_args[:resolve] ||= -> (object, args, context) do
can?(context[:current_user], ability, object, args.to_h)
end
end
permission_field(ability, **kword_args)
end
def self.permission_field(name, **kword_args)
kword_args = kword_args.reverse_merge(
name: name,
type: GraphQL::BOOLEAN_TYPE,
description: "Whether or not a user can perform `#{name}` on this resource",
null: false)
field(**kword_args)
end
def self.resolving_keywords?(arguments)
RESOLVING_KEYWORDS.intersect?(arguments.keys.to_set)
end
private_class_method :resolving_keywords?
end
end
end

View File

@ -0,0 +1,17 @@
module Types
module PermissionTypes
class MergeRequest < BasePermissionType
present_using MergeRequestPresenter
description 'Check permissions for the current user on a merge request'
graphql_name 'MergeRequestPermissions'
abilities :read_merge_request, :admin_merge_request,
:update_merge_request, :create_note
permission_field :push_to_source_branch, method: :can_push_to_source_branch?
permission_field :remove_source_branch, method: :can_remove_source_branch?
permission_field :cherry_pick_on_current_merge_request, method: :can_cherry_pick_on_current_merge_request?
permission_field :revert_on_current_merge_request, method: :can_revert_on_current_merge_request?
end
end
end

View File

@ -0,0 +1,20 @@
module Types
module PermissionTypes
class Project < BasePermissionType
graphql_name 'ProjectPermissions'
abilities :change_namespace, :change_visibility_level, :rename_project,
:remove_project, :archive_project, :remove_fork_project,
:remove_pages, :read_project, :create_merge_request_in,
:read_wiki, :read_project_member, :create_issue, :upload_file,
:read_cycle_analytics, :download_code, :download_wiki_code,
:fork_project, :create_project_snippet, :read_commit_status,
:request_access, :create_pipeline, :create_pipeline_schedule,
:create_merge_request_from, :create_wiki, :push_code,
:create_deployment, :push_to_delete_protected_branch,
:admin_wiki, :admin_project, :update_pages,
:admin_remote_mirror, :create_label, :update_wiki, :destroy_wiki,
:create_pages, :destroy_pages
end
end
end

View File

@ -1,5 +1,7 @@
module Types
class ProjectType < BaseObject
expose_permissions Types::PermissionTypes::Project
graphql_name 'Project'
field :id, GraphQL::ID_TYPE, null: false

View File

@ -168,6 +168,10 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
.can_push_to_branch?(source_branch)
end
def can_remove_source_branch?
source_branch_exists? && merge_request.can_remove_source_branch?(current_user)
end
def mergeable_discussions_state
# This avoids calling MergeRequest#mergeable_discussions_state without
# considering the state of the MR first. If a MR isn't mergeable, we can

View File

@ -109,7 +109,7 @@ class MergeRequestWidgetEntity < IssuableEntity
expose :current_user do
expose :can_remove_source_branch do |merge_request|
merge_request.source_branch_exists? && merge_request.can_remove_source_branch?(current_user)
presenter(merge_request).can_remove_source_branch?
end
expose :can_revert_on_current_merge_request do |merge_request|

View File

@ -6,9 +6,9 @@ module MergeRequests
#
class PostMergeService < MergeRequests::BaseService
def execute(merge_request)
merge_request.mark_as_merged
close_issues(merge_request)
todo_service.merge_merge_request(merge_request, current_user)
merge_request.mark_as_merged
create_event(merge_request)
create_note(merge_request)
notification_service.merge_mr(merge_request, current_user)

View File

@ -18,10 +18,18 @@ module MergeRequests
return false
end
log_prefix = "#{self.class.name} info (#{merge_request.to_reference(full: true)}):"
Gitlab::GitLogger.info("#{log_prefix} rebase started")
rebase_sha = repository.rebase(current_user, merge_request)
Gitlab::GitLogger.info("#{log_prefix} rebased to #{rebase_sha}")
merge_request.update_attributes(rebase_commit_sha: rebase_sha)
Gitlab::GitLogger.info("#{log_prefix} rebase SHA saved: #{rebase_sha}")
true
rescue => e
log_error(REBASE_ERROR, save_message_on_model: true)

View File

@ -3,7 +3,7 @@
- project = local_assigns.fetch(:project)
- expanded = Rails.env.test?
%section.settings.no-animate{ class: ('expanded' if expanded) }
%section.settings.no-animate#js-export-project{ class: ('expanded' if expanded) }
.settings-header
%h4
Export project

View File

@ -1,6 +1,6 @@
- expanded = expand_deploy_tokens_section?(@new_deploy_token)
%section.settings.no-animate{ class: ('expanded' if expanded) }
%section.settings.no-animate#js-deploy-tokens{ class: ('expanded' if expanded) }
.settings-header
%h4= s_('DeployTokens|Deploy Tokens')
%button.btn.js-settings-toggle.qa-expand-deploy-keys{ type: 'button' }

View File

@ -4,7 +4,7 @@
- expanded = Rails.env.test?
.project-edit-container
%section.settings.general-settings.no-animate{ class: ('expanded' if expanded) }
%section.settings.general-settings.no-animate#js-general-project-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
General project
@ -65,7 +65,7 @@
= link_to _('Remove avatar'), project_avatar_path(@project), data: { confirm: _("Avatar will be removed. Are you sure?") }, method: :delete, class: "btn btn-danger btn-inverted"
= f.submit 'Save changes', class: "btn btn-success js-btn-save-general-project-settings"
%section.settings.sharing-permissions.no-animate{ class: ('expanded' if expanded) }
%section.settings.sharing-permissions.no-animate#js-shared-permissions{ class: ('expanded' if expanded) }
.settings-header
%h4
Permissions
@ -82,7 +82,7 @@
= render_if_exists 'projects/issues_settings'
%section.qa-merge-request-settings.settings.merge-requests-feature.no-animate{ class: [('expanded' if expanded), ('hidden' if @project.project_feature.send(:merge_requests_access_level) == 0)] }
%section.qa-merge-request-settings.settings.merge-requests-feature.no-animate#js-merge-request-settings{ class: [('expanded' if expanded), ('hidden' if @project.project_feature.send(:merge_requests_access_level) == 0)] }
.settings-header
%h4
Merge request
@ -101,7 +101,7 @@
= render 'export', project: @project
%section.qa-advanced-settings.settings.advanced-settings.no-animate{ class: ('expanded' if expanded) }
%section.qa-advanced-settings.settings.advanced-settings.no-animate#js-project-advanced-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
Advanced

View File

@ -1,4 +1,4 @@
- if environment.external_url && can?(current_user, :read_environment, environment)
= link_to environment.external_url, target: '_blank', rel: 'noopener noreferrer', class: 'btn external-url' do
= icon('external-link')
= sprite_icon('external-link')
View deployment

View File

@ -3,5 +3,5 @@
- return unless can?(current_user, :read_environment, environment)
= link_to environment_metrics_path(environment), title: 'See metrics', class: 'btn metrics-button' do
= icon('area-chart')
= sprite_icon('chart')
Monitoring

View File

@ -1,3 +1,3 @@
- if environment.has_terminals? && can?(current_user, :admin_environment, @project)
= link_to terminal_project_environment_path(@project, environment), class: 'btn terminal-button' do
= icon('terminal')
= sprite_icon('terminal')

View File

@ -16,7 +16,7 @@
.nav-controls
- if @environment.external_url.present?
= link_to @environment.external_url, class: 'btn btn-default', target: '_blank', rel: 'noopener noreferrer nofollow' do
= icon('external-link')
= sprite_icon('external-link')
= render 'projects/deployments/actions', deployment: @environment.last_deployment
.terminal-container{ class: container_class }

View File

@ -1,5 +1,5 @@
- expanded = Rails.env.test?
%section.settings.no-animate{ class: ('expanded' if expanded) }
%section.settings.no-animate#js-push-remote-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
Push to a remote repository

View File

@ -1,6 +1,6 @@
- expanded = Rails.env.test?
%section.settings.no-animate{ class: ('expanded' if expanded) }
%section.settings.no-animate#js-protected-tags-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
Protected Tags

View File

@ -6,13 +6,13 @@
1.
= link_to 'https://docs.mattermost.com/developer/slash-commands.html#enabling-custom-commands', target: '_blank', rel: 'noopener noreferrer nofollow' do
Enable custom slash commands
= icon('external-link')
= sprite_icon('external-link', size: 16)
on your Mattermost installation
%li
2.
= link_to 'https://docs.mattermost.com/developer/slash-commands.html#set-up-a-custom-command', target: '_blank', rel: 'noopener noreferrer nofollow' do
Add a slash command
= icon('external-link')
= sprite_icon('external-link', size: 16)
in your Mattermost team with these options:
%hr

View File

@ -7,7 +7,7 @@
project by entering slash commands in Mattermost.
= link_to help_page_path('user/project/integrations/mattermost_slash_commands.md'), target: '_blank' do
View documentation
= icon('external-link')
= sprite_icon('external-link', size: 16)
%p.inline
See list of available commands in Mattermost after setting up this service,
by entering

View File

@ -8,7 +8,7 @@
project by entering slash commands in Slack.
= link_to help_page_path('user/project/integrations/slack_slash_commands.md'), target: '_blank' do
View documentation
= icon('external-link')
= sprite_icon('external-link', size: 16)
%p.inline
See list of available commands in Slack after setting up this service,
by entering
@ -20,7 +20,7 @@
1.
= link_to 'https://my.slack.com/services/new/slash-commands', target: '_blank', rel: 'noreferrer noopener nofollow' do
Add a slash command
= icon('external-link')
= sprite_icon('external-link', size: 16)
in your Slack team with these options:
%hr

View File

@ -51,7 +51,7 @@
.settings-content
= render 'ci/variables/index', save_endpoint: project_variables_path(@project)
%section.settings.no-animate{ class: ('expanded' if expanded) }
%section.settings.no-animate#js-pipeline-triggers{ class: ('expanded' if expanded) }
.settings-header
%h4
Pipeline triggers

View File

@ -21,8 +21,9 @@
= sprite_icon('star-o')
%button.label-action.remove-priority.btn.btn-transparent.has-tooltip{ title: _('Remove priority'), type: 'button', data: { placement: 'top' }, aria_label: _('Deprioritize label') }
= sprite_icon('star')
- if can?(current_user, :admin_label, label)
%li.inline
= link_to edit_label_path(label), class: 'btn btn-transparent label-action', aria_label: 'Edit label' do
= link_to edit_label_path(label), class: 'btn btn-transparent label-action edit', aria_label: 'Edit label' do
= sprite_icon('pencil')
%li.inline
.dropdown
@ -42,9 +43,10 @@
container: 'body',
toggle: 'modal' } }
= _('Promote to group label')
%li
%span{ data: { toggle: 'modal', target: "#modal-delete-label-#{label.id}" } }
%button.text-danger.remove-row{ type: 'button' }= _('Delete')
- if can?(current_user, :admin_label, label)
%li
%span{ data: { toggle: 'modal', target: "#modal-delete-label-#{label.id}" } }
%button.text-danger.remove-row{ type: 'button' }= _('Delete')
- if current_user
%li.inline.label-subscription
- if can_subscribe_to_label_in_different_levels?(label)

View File

@ -11,7 +11,7 @@
- cronjob:remove_old_web_hook_logs
- cronjob:remove_unreferenced_lfs_objects
- cronjob:repository_archive_cache
- cronjob:repository_check_batch
- cronjob:repository_check_dispatch
- cronjob:requests_profiles
- cronjob:schedule_update_user_activity
- cronjob:stuck_ci_jobs
@ -71,6 +71,7 @@
- pipeline_processing:update_head_pipeline_for_merge_request
- repository_check:repository_check_clear
- repository_check:repository_check_batch
- repository_check:repository_check_single_repository
- default

View File

@ -0,0 +1,31 @@
module EachShardWorker
extend ActiveSupport::Concern
include ::Gitlab::Utils::StrongMemoize
def each_eligible_shard
Gitlab::ShardHealthCache.update(eligible_shard_names)
eligible_shard_names.each do |shard_name|
yield shard_name
end
end
# override when you want to filter out some shards
def eligible_shard_names
healthy_shard_names
end
def healthy_shard_names
strong_memoize(:healthy_shard_names) do
healthy_ready_shards.map { |result| result.labels[:shard] }
end
end
def healthy_ready_shards
ready_shards.select(&:success)
end
def ready_shards
Gitlab::HealthChecks::GitalyCheck.readiness
end
end

View File

@ -3,13 +3,18 @@
module RepositoryCheck
class BatchWorker
include ApplicationWorker
include CronjobQueue
include RepositoryCheckQueue
RUN_TIME = 3600
BATCH_SIZE = 10_000
def perform
attr_reader :shard_name
def perform(shard_name)
@shard_name = shard_name
return unless Gitlab::CurrentSettings.repository_checks_enabled
return unless Gitlab::ShardHealthCache.healthy_shard?(shard_name)
start = Time.now
@ -39,18 +44,22 @@ module RepositoryCheck
end
def never_checked_project_ids(batch_size)
Project.where(last_repository_check_at: nil)
projects_on_shard.where(last_repository_check_at: nil)
.where('created_at < ?', 24.hours.ago)
.limit(batch_size).pluck(:id)
end
def old_checked_project_ids(batch_size)
Project.where.not(last_repository_check_at: nil)
projects_on_shard.where.not(last_repository_check_at: nil)
.where('last_repository_check_at < ?', 1.month.ago)
.reorder(last_repository_check_at: :asc)
.limit(batch_size).pluck(:id)
end
def projects_on_shard
Project.where(repository_storage: shard_name)
end
def try_obtain_lease(id)
# Use a 24-hour timeout because on servers/projects where 'git fsck' is
# super slow we definitely do not want to run it twice in parallel.

View File

@ -0,0 +1,15 @@
module RepositoryCheck
class DispatchWorker
include ApplicationWorker
include CronjobQueue
include ::EachShardWorker
def perform
return unless Gitlab::CurrentSettings.repository_checks_enabled
each_eligible_shard do |shard_name|
RepositoryCheck::BatchWorker.perform_async(shard_name)
end
end
end
end

View File

@ -0,0 +1,5 @@
---
title: Add SHA256 and HEAD on File API
merge_request: 19439
author: ahmet2mir
type: added

View File

@ -0,0 +1,5 @@
---
title: Allows settings sections to expand by default when linking to them
merge_request: 20211
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Change avatar image in the header when user updates their avatar.
merge_request: 20119
author: Jamie Schembri
type: added

View File

@ -0,0 +1,5 @@
---
title: Fix sidebar collapse breapoints for job and wiki pages
merge_request:
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fixed Merge request changes dropdown displays incorrectly
merge_request: 20237
author: Constance Okoghenun
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix performance bar modal visibility in Safari
merge_request:
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: fix size of code blocks in headings
merge_request:
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Add more detailed logging to githost.log when rebasing
merge_request:
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: 'Expose permissions of the current user on resources in GraphQL'
merge_request: 20152
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Fixes an issue where migrations instead of schema loading were run
merge_request: 20227
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Don't hash user ID in OIDC subject claim
merge_request: 19784
author: Markus Koller
type: changed

View File

@ -0,0 +1,5 @@
---
title: Fix paragraph line height for emoji
merge_request: 20137
author: George Tsiolis
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Mark MR as merged regardless of errors when closing issues
merge_request:
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Revert merge request discussion buttons padding
merge_request: 20060
author: George Tsiolis
type: changed

View File

@ -0,0 +1,5 @@
---
title: Allow straight diff in Compare API
merge_request: 20120
author: Maciej Nowak
type: added

View File

@ -0,0 +1,5 @@
---
title: Run repository checks in parallel for each shard
merge_request: 20179
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Update environments nav controls icons
merge_request: 20199
author: George Tsiolis
type: changed

View File

@ -0,0 +1,5 @@
---
title: Update integrations external link icons
merge_request: 20205
author: George Tsiolis
type: changed

View File

@ -33,7 +33,7 @@ production: &base
port: 80 # Set to 443 if using HTTPS, see installation.md#using-https for additional HTTPS configuration details
https: false # Set to true if using HTTPS, see installation.md#using-https for additional HTTPS configuration details
# Uncommment this line below if your ssh host is different from HTTP/HTTPS one
# Uncomment this line below if your ssh host is different from HTTP/HTTPS one
# (you'd obviously need to replace ssh.host_example.com with your own host).
# Otherwise, ssh host will be set to the `host:` value above
# ssh_host: ssh.host_example.com

View File

@ -279,7 +279,7 @@ Settings.cron_jobs['expire_build_artifacts_worker']['cron'] ||= '50 * * * *'
Settings.cron_jobs['expire_build_artifacts_worker']['job_class'] = 'ExpireBuildArtifactsWorker'
Settings.cron_jobs['repository_check_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['repository_check_worker']['cron'] ||= '20 * * * *'
Settings.cron_jobs['repository_check_worker']['job_class'] = 'RepositoryCheck::BatchWorker'
Settings.cron_jobs['repository_check_worker']['job_class'] = 'RepositoryCheck::DispatchWorker'
Settings.cron_jobs['admin_email_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['admin_email_worker']['cron'] ||= '0 0 * * 0'
Settings.cron_jobs['admin_email_worker']['job_class'] = 'AdminEmailWorker'
@ -394,7 +394,7 @@ repositories_storages = Settings.repositories.storages.values
repository_downloads_path = Settings.gitlab['repository_downloads_path'].to_s.gsub(%r{/$}, '')
repository_downloads_full_path = File.expand_path(repository_downloads_path, Settings.gitlab['user_home'])
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/1237
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/1255
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
if repository_downloads_path.blank? || repositories_storages.any? { |rs| [repository_downloads_path, repository_downloads_full_path].include?(rs.legacy_disk_path.gsub(%r{/$}, '')) }
Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive')

View File

@ -2,20 +2,6 @@ def storage_name_valid?(name)
!!(name =~ /\A[a-zA-Z0-9\-_]+\z/)
end
def find_parent_path(name, path)
parent = Pathname.new(path).realpath.parent
Gitlab.config.repositories.storages.detect do |n, rs|
name != n && Pathname.new(rs.legacy_disk_path).realpath == parent
end
rescue Errno::EIO, Errno::ENOENT => e
warning = "WARNING: couldn't verify #{path} (#{name}). "\
"If this is an external storage, it might be offline."
message = "#{warning}\n#{e.message}"
Rails.logger.error("#{message}\n\t" + e.backtrace.join("\n\t"))
nil
end
def storage_validation_error(message)
raise "#{message}. Please fix this in your gitlab.yml before starting GitLab."
end
@ -37,17 +23,4 @@ def validate_storages_config
end
end
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/1237
def validate_storages_paths
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
Gitlab.config.repositories.storages.each do |name, repository_storage|
parent_name, _parent_path = find_parent_path(name, repository_storage.legacy_disk_path)
if parent_name
storage_validation_error("#{name} is a nested path of #{parent_name}. Nested paths are not supported for repository storages")
end
end
end
end
validate_storages_config
validate_storages_paths unless Rails.env.test? || ENV['SKIP_STORAGE_VALIDATION'] == 'true'

View File

@ -18,12 +18,17 @@ Doorkeeper::OpenidConnect.configure do
end
subject do |user|
# hash the user's ID with the Rails secret_key_base to avoid revealing it
Digest::SHA256.hexdigest "#{user.id}-#{Rails.application.secrets.secret_key_base}"
user.id
end
claims do
with_options scope: :openid do |o|
o.claim(:sub_legacy, response: [:id_token, :user_info]) do |user|
# provide the previously hashed 'sub' claim to allow third-party apps
# to migrate to the new unhashed value
Digest::SHA256.hexdigest "#{user.id}-#{Rails.application.secrets.secret_key_base}"
end
o.claim(:name) { |user| user.name }
o.claim(:nickname) { |user| user.username }
o.claim(:email) { |user| user.public_email }

Some files were not shown because too many files have changed in this diff Show More