Merge branch 'master' into sh-support-bitbucket-server-import
This commit is contained in:
commit
f7d0ee1f09
|
@ -1 +1 @@
|
|||
0.108.0
|
||||
0.109.0
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -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'
|
||||
|
|
12
Gemfile.lock
12
Gemfile.lock
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
32
PROCESS.md
32
PROCESS.md
|
@ -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
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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>
|
|
@ -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"
|
||||
>
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 () => {};
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 () => {};
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -25,4 +25,5 @@ export default () => ({
|
|||
fileFindVisible: false,
|
||||
rightPane: null,
|
||||
links: {},
|
||||
errorMessage: null,
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -48,7 +48,7 @@ export default class Wikis {
|
|||
|
||||
static sidebarCanCollapse() {
|
||||
const bootstrapBreakpoint = bp.getBreakpointSize();
|
||||
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
|
||||
return bootstrapBreakpoint === 'xs';
|
||||
}
|
||||
|
||||
renderSidebar() {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -104,6 +104,10 @@
|
|||
position: relative;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
> gl-emoji {
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.award-menu-holder {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
a.flash-action {
|
||||
.flash-action {
|
||||
margin-left: 5px;
|
||||
text-decoration: none;
|
||||
font-weight: $gl-font-weight-normal;
|
||||
|
|
|
@ -233,7 +233,7 @@ $md-area-border: #ddd;
|
|||
/*
|
||||
* Code
|
||||
*/
|
||||
$code_font_size: 12px;
|
||||
$code_font_size: 90%;
|
||||
$code_line_height: 1.6;
|
||||
|
||||
/*
|
||||
|
|
|
@ -93,6 +93,10 @@
|
|||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
vertical-align: text-top;
|
||||
}
|
||||
}
|
||||
|
||||
.light-well {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
module Types
|
||||
class BaseObject < GraphQL::Schema::Object
|
||||
prepend Gitlab::Graphql::Present
|
||||
prepend Gitlab::Graphql::ExposePermissions
|
||||
|
||||
field_class Types::BaseField
|
||||
end
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
module Types
|
||||
class MergeRequestType < BaseObject
|
||||
expose_permissions Types::PermissionTypes::MergeRequest
|
||||
|
||||
present_using MergeRequestPresenter
|
||||
|
||||
graphql_name 'MergeRequest'
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1,5 +1,7 @@
|
|||
module Types
|
||||
class ProjectType < BaseObject
|
||||
expose_permissions Types::PermissionTypes::Project
|
||||
|
||||
graphql_name 'Project'
|
||||
|
||||
field :id, GraphQL::ID_TYPE, null: false
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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' }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add SHA256 and HEAD on File API
|
||||
merge_request: 19439
|
||||
author: ahmet2mir
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Allows settings sections to expand by default when linking to them
|
||||
merge_request: 20211
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Change avatar image in the header when user updates their avatar.
|
||||
merge_request: 20119
|
||||
author: Jamie Schembri
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix sidebar collapse breapoints for job and wiki pages
|
||||
merge_request:
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fixed Merge request changes dropdown displays incorrectly
|
||||
merge_request: 20237
|
||||
author: Constance Okoghenun
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix performance bar modal visibility in Safari
|
||||
merge_request:
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: fix size of code blocks in headings
|
||||
merge_request:
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add more detailed logging to githost.log when rebasing
|
||||
merge_request:
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: 'Expose permissions of the current user on resources in GraphQL'
|
||||
merge_request: 20152
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fixes an issue where migrations instead of schema loading were run
|
||||
merge_request: 20227
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Don't hash user ID in OIDC subject claim
|
||||
merge_request: 19784
|
||||
author: Markus Koller
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix paragraph line height for emoji
|
||||
merge_request: 20137
|
||||
author: George Tsiolis
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Mark MR as merged regardless of errors when closing issues
|
||||
merge_request:
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Revert merge request discussion buttons padding
|
||||
merge_request: 20060
|
||||
author: George Tsiolis
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Allow straight diff in Compare API
|
||||
merge_request: 20120
|
||||
author: Maciej Nowak
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Run repository checks in parallel for each shard
|
||||
merge_request: 20179
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update environments nav controls icons
|
||||
merge_request: 20199
|
||||
author: George Tsiolis
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update integrations external link icons
|
||||
merge_request: 20205
|
||||
author: George Tsiolis
|
||||
type: changed
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue