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
|
# Authentication libraries
|
||||||
gem 'devise', '~> 4.4'
|
gem 'devise', '~> 4.4'
|
||||||
gem 'doorkeeper', '~> 4.3'
|
gem 'doorkeeper', '~> 4.3'
|
||||||
gem 'doorkeeper-openid_connect', '~> 1.3'
|
gem 'doorkeeper-openid_connect', '~> 1.5'
|
||||||
gem 'omniauth', '~> 1.8'
|
gem 'omniauth', '~> 1.8'
|
||||||
gem 'omniauth-auth0', '~> 2.0.0'
|
gem 'omniauth-auth0', '~> 2.0.0'
|
||||||
gem 'omniauth-azure-oauth2', '~> 0.0.9'
|
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)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
doorkeeper (4.3.2)
|
doorkeeper (4.3.2)
|
||||||
railties (>= 4.2)
|
railties (>= 4.2)
|
||||||
doorkeeper-openid_connect (1.4.0)
|
doorkeeper-openid_connect (1.5.0)
|
||||||
doorkeeper (~> 4.3)
|
doorkeeper (~> 4.3)
|
||||||
json-jwt (~> 1.6)
|
json-jwt (~> 1.6)
|
||||||
dropzonejs-rails (0.7.2)
|
dropzonejs-rails (0.7.2)
|
||||||
|
@ -427,12 +427,10 @@ GEM
|
||||||
oauth (~> 0.5, >= 0.5.0)
|
oauth (~> 0.5, >= 0.5.0)
|
||||||
jquery-atwho-rails (1.3.2)
|
jquery-atwho-rails (1.3.2)
|
||||||
json (1.8.6)
|
json (1.8.6)
|
||||||
json-jwt (1.9.2)
|
json-jwt (1.9.4)
|
||||||
activesupport
|
activesupport
|
||||||
aes_key_wrap
|
aes_key_wrap
|
||||||
bindata
|
bindata
|
||||||
securecompare
|
|
||||||
url_safe_base64
|
|
||||||
json-schema (2.8.0)
|
json-schema (2.8.0)
|
||||||
addressable (>= 2.4)
|
addressable (>= 2.4)
|
||||||
jwt (1.5.6)
|
jwt (1.5.6)
|
||||||
|
@ -512,7 +510,7 @@ GEM
|
||||||
net-ldap (0.16.0)
|
net-ldap (0.16.0)
|
||||||
net-ssh (5.0.1)
|
net-ssh (5.0.1)
|
||||||
netrc (0.11.0)
|
netrc (0.11.0)
|
||||||
nokogiri (1.8.2)
|
nokogiri (1.8.3)
|
||||||
mini_portile2 (~> 2.3.0)
|
mini_portile2 (~> 2.3.0)
|
||||||
nokogumbo (1.5.0)
|
nokogumbo (1.5.0)
|
||||||
nokogiri
|
nokogiri
|
||||||
|
@ -827,7 +825,6 @@ GEM
|
||||||
scss_lint (0.56.0)
|
scss_lint (0.56.0)
|
||||||
rake (>= 0.9, < 13)
|
rake (>= 0.9, < 13)
|
||||||
sass (~> 3.5.3)
|
sass (~> 3.5.3)
|
||||||
securecompare (1.0.0)
|
|
||||||
seed-fu (2.3.7)
|
seed-fu (2.3.7)
|
||||||
activerecord (>= 3.1)
|
activerecord (>= 3.1)
|
||||||
activesupport (>= 3.1)
|
activesupport (>= 3.1)
|
||||||
|
@ -939,7 +936,6 @@ GEM
|
||||||
equalizer (~> 0.0.9)
|
equalizer (~> 0.0.9)
|
||||||
parser (>= 2.3.1.2, < 2.6)
|
parser (>= 2.3.1.2, < 2.6)
|
||||||
procto (~> 0.0.2)
|
procto (~> 0.0.2)
|
||||||
url_safe_base64 (0.2.2)
|
|
||||||
validates_hostname (1.0.6)
|
validates_hostname (1.0.6)
|
||||||
activerecord (>= 3.0)
|
activerecord (>= 3.0)
|
||||||
activesupport (>= 3.0)
|
activesupport (>= 3.0)
|
||||||
|
@ -1013,7 +1009,7 @@ DEPENDENCIES
|
||||||
devise-two-factor (~> 3.0.0)
|
devise-two-factor (~> 3.0.0)
|
||||||
diffy (~> 3.1.0)
|
diffy (~> 3.1.0)
|
||||||
doorkeeper (~> 4.3)
|
doorkeeper (~> 4.3)
|
||||||
doorkeeper-openid_connect (~> 1.3)
|
doorkeeper-openid_connect (~> 1.5)
|
||||||
dropzonejs-rails (~> 0.7.1)
|
dropzonejs-rails (~> 0.7.1)
|
||||||
ed25519 (~> 1.2)
|
ed25519 (~> 1.2)
|
||||||
email_reply_trimmer (~> 0.1)
|
email_reply_trimmer (~> 0.1)
|
||||||
|
|
|
@ -174,7 +174,7 @@ GEM
|
||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
doorkeeper (4.3.2)
|
doorkeeper (4.3.2)
|
||||||
railties (>= 4.2)
|
railties (>= 4.2)
|
||||||
doorkeeper-openid_connect (1.4.0)
|
doorkeeper-openid_connect (1.5.0)
|
||||||
doorkeeper (~> 4.3)
|
doorkeeper (~> 4.3)
|
||||||
json-jwt (~> 1.6)
|
json-jwt (~> 1.6)
|
||||||
dropzonejs-rails (0.7.2)
|
dropzonejs-rails (0.7.2)
|
||||||
|
@ -430,12 +430,10 @@ GEM
|
||||||
oauth (~> 0.5, >= 0.5.0)
|
oauth (~> 0.5, >= 0.5.0)
|
||||||
jquery-atwho-rails (1.3.2)
|
jquery-atwho-rails (1.3.2)
|
||||||
json (1.8.6)
|
json (1.8.6)
|
||||||
json-jwt (1.9.2)
|
json-jwt (1.9.4)
|
||||||
activesupport
|
activesupport
|
||||||
aes_key_wrap
|
aes_key_wrap
|
||||||
bindata
|
bindata
|
||||||
securecompare
|
|
||||||
url_safe_base64
|
|
||||||
json-schema (2.8.0)
|
json-schema (2.8.0)
|
||||||
addressable (>= 2.4)
|
addressable (>= 2.4)
|
||||||
jwt (1.5.6)
|
jwt (1.5.6)
|
||||||
|
@ -836,7 +834,6 @@ GEM
|
||||||
scss_lint (0.56.0)
|
scss_lint (0.56.0)
|
||||||
rake (>= 0.9, < 13)
|
rake (>= 0.9, < 13)
|
||||||
sass (~> 3.5.3)
|
sass (~> 3.5.3)
|
||||||
securecompare (1.0.0)
|
|
||||||
seed-fu (2.3.7)
|
seed-fu (2.3.7)
|
||||||
activerecord (>= 3.1)
|
activerecord (>= 3.1)
|
||||||
activesupport (>= 3.1)
|
activesupport (>= 3.1)
|
||||||
|
@ -946,7 +943,6 @@ GEM
|
||||||
equalizer (~> 0.0.9)
|
equalizer (~> 0.0.9)
|
||||||
parser (>= 2.3.1.2, < 2.6)
|
parser (>= 2.3.1.2, < 2.6)
|
||||||
procto (~> 0.0.2)
|
procto (~> 0.0.2)
|
||||||
url_safe_base64 (0.2.2)
|
|
||||||
validates_hostname (1.0.6)
|
validates_hostname (1.0.6)
|
||||||
activerecord (>= 3.0)
|
activerecord (>= 3.0)
|
||||||
activesupport (>= 3.0)
|
activesupport (>= 3.0)
|
||||||
|
@ -1023,7 +1019,7 @@ DEPENDENCIES
|
||||||
devise-two-factor (~> 3.0.0)
|
devise-two-factor (~> 3.0.0)
|
||||||
diffy (~> 3.1.0)
|
diffy (~> 3.1.0)
|
||||||
doorkeeper (~> 4.3)
|
doorkeeper (~> 4.3)
|
||||||
doorkeeper-openid_connect (~> 1.3)
|
doorkeeper-openid_connect (~> 1.5)
|
||||||
dropzonejs-rails (~> 0.7.1)
|
dropzonejs-rails (~> 0.7.1)
|
||||||
ed25519 (~> 1.2)
|
ed25519 (~> 1.2)
|
||||||
email_reply_trimmer (~> 0.1)
|
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)
|
- [Between the 1st and the 7th](#between-the-1st-and-the-7th)
|
||||||
- [On the 7th](#on-the-7th)
|
- [On the 7th](#on-the-7th)
|
||||||
- [After the 7th](#after-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)
|
- [Release retrospective and kickoff](#release-retrospective-and-kickoff)
|
||||||
- [Retrospective](#retrospective)
|
- [Retrospective](#retrospective)
|
||||||
- [Kickoff](#kickoff)
|
- [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.
|
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
|
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
|
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
|
the version currently running on GitLab.com, or the most recent version
|
||||||
available in the package repositories.
|
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)
|
### How to manage a regression
|
||||||
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
|
Regressions are very important, and they should be considered high priority
|
||||||
relevant to [their area of responsibility](https://about.gitlab.com/handbook/engineering/workflow/#choosing-something-to-work-on).
|
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
|
## 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) {
|
buildUrl(url) {
|
||||||
let urlRoot = '';
|
let urlRoot = '';
|
||||||
if (gon.relative_url_root != null) {
|
if (gon.relative_url_root != null) {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
|
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||||
import eventHub from '../eventhub';
|
import eventHub from '../eventhub';
|
||||||
|
import tooltip from '../../vue_shared/directives/tooltip';
|
||||||
|
|
||||||
const Store = gl.issueBoards.BoardsStore;
|
const Store = gl.issueBoards.BoardsStore;
|
||||||
|
|
||||||
|
@ -9,6 +10,9 @@
|
||||||
components: {
|
components: {
|
||||||
UserAvatarLink,
|
UserAvatarLink,
|
||||||
},
|
},
|
||||||
|
directives: {
|
||||||
|
tooltip,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
issue: {
|
issue: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -166,9 +170,10 @@
|
||||||
tooltip-placement="bottom"
|
tooltip-placement="bottom"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
|
v-tooltip
|
||||||
v-if="shouldRenderCounter"
|
v-if="shouldRenderCounter"
|
||||||
:title="assigneeCounterTooltip"
|
:title="assigneeCounterTooltip"
|
||||||
class="avatar-counter has-tooltip"
|
class="avatar-counter"
|
||||||
>
|
>
|
||||||
{{ assigneeCounterLabel }}
|
{{ assigneeCounterLabel }}
|
||||||
</span>
|
</span>
|
||||||
|
@ -179,12 +184,13 @@
|
||||||
class="board-card-footer"
|
class="board-card-footer"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
|
v-tooltip
|
||||||
v-for="label in issue.labels"
|
v-for="label in issue.labels"
|
||||||
v-if="showLabel(label)"
|
v-if="showLabel(label)"
|
||||||
:key="label.id"
|
:key="label.id"
|
||||||
:style="labelStyle(label)"
|
:style="labelStyle(label)"
|
||||||
:title="label.description"
|
:title="label.description"
|
||||||
class="badge color-label has-tooltip"
|
class="badge color-label"
|
||||||
type="button"
|
type="button"
|
||||||
data-container="body"
|
data-container="body"
|
||||||
@click="filterByLabel(label, $event)"
|
@click="filterByLabel(label, $event)"
|
||||||
|
|
|
@ -4,14 +4,7 @@ import { s__ } from '~/locale';
|
||||||
import { mapState, mapGetters, mapActions } from 'vuex';
|
import { mapState, mapGetters, mapActions } from 'vuex';
|
||||||
import Icon from '~/vue_shared/components/icon.vue';
|
import Icon from '~/vue_shared/components/icon.vue';
|
||||||
import DiffGutterAvatars from './diff_gutter_avatars.vue';
|
import DiffGutterAvatars from './diff_gutter_avatars.vue';
|
||||||
import {
|
import { LINE_POSITION_RIGHT, UNFOLD_COUNT } from '../constants';
|
||||||
MATCH_LINE_TYPE,
|
|
||||||
CONTEXT_LINE_TYPE,
|
|
||||||
OLD_NO_NEW_LINE_TYPE,
|
|
||||||
NEW_NO_NEW_LINE_TYPE,
|
|
||||||
LINE_POSITION_RIGHT,
|
|
||||||
UNFOLD_COUNT,
|
|
||||||
} from '../constants';
|
|
||||||
import * as utils from '../store/utils';
|
import * as utils from '../store/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -63,6 +56,21 @@ export default {
|
||||||
required: false,
|
required: false,
|
||||||
default: 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: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
|
@ -70,15 +78,6 @@ export default {
|
||||||
diffFiles: state => state.diffs.diffFiles,
|
diffFiles: state => state.diffs.diffFiles,
|
||||||
}),
|
}),
|
||||||
...mapGetters(['isLoggedIn', 'discussionsByLineCode']),
|
...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() {
|
lineHref() {
|
||||||
return this.lineCode ? `#${this.lineCode}` : '#';
|
return this.lineCode ? `#${this.lineCode}` : '#';
|
||||||
},
|
},
|
||||||
|
@ -109,9 +108,9 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['loadMoreLines']),
|
...mapActions(['loadMoreLines', 'showCommentForm']),
|
||||||
handleCommentButton() {
|
handleCommentButton() {
|
||||||
this.$emit('showCommentForm', { lineCode: this.lineCode });
|
this.showCommentForm({ lineCode: this.lineCode });
|
||||||
},
|
},
|
||||||
handleLoadMoreLines() {
|
handleLoadMoreLines() {
|
||||||
if (this.isRequesting) {
|
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>
|
<script>
|
||||||
import diffContentMixin from '../mixins/diff_content';
|
import diffContentMixin from '../mixins/diff_content';
|
||||||
import {
|
import inlineDiffCommentRow from './inline_diff_comment_row.vue';
|
||||||
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';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [diffContentMixin],
|
components: {
|
||||||
methods: {
|
inlineDiffCommentRow,
|
||||||
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,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
mixins: [diffContentMixin],
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -41,76 +19,19 @@ export default {
|
||||||
<template
|
<template
|
||||||
v-for="(line, index) in normalizedDiffLines"
|
v-for="(line, index) in normalizedDiffLines"
|
||||||
>
|
>
|
||||||
<tr
|
<diff-table-row
|
||||||
:id="line.lineCode || `${fileHash}_${line.oldLine}_${line.newLine}`"
|
:diff-file="diffFile"
|
||||||
|
:line="line"
|
||||||
|
:is-bottom="index + 1 === diffLinesLength"
|
||||||
:key="line.lineCode"
|
:key="line.lineCode"
|
||||||
:class="getRowClass(line)"
|
/>
|
||||||
class="line_holder"
|
<inline-diff-comment-row
|
||||||
@mouseover="handleMouse(line.lineCode, true)"
|
:diff-file="diffFile"
|
||||||
@mouseout="handleMouse(line.lineCode, false)"
|
:diff-lines="normalizedDiffLines"
|
||||||
>
|
:line="line"
|
||||||
<td
|
:line-index="index"
|
||||||
: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]"
|
|
||||||
:key="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>
|
</template>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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>
|
<script>
|
||||||
import diffContentMixin from '../mixins/diff_content';
|
import diffContentMixin from '../mixins/diff_content';
|
||||||
import {
|
import parallelDiffCommentRow from './parallel_diff_comment_row.vue';
|
||||||
EMPTY_CELL_TYPE,
|
import { EMPTY_CELL_TYPE } from '../constants';
|
||||||
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';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
parallelDiffCommentRow,
|
||||||
|
},
|
||||||
mixins: [diffContentMixin],
|
mixins: [diffContentMixin],
|
||||||
computed: {
|
computed: {
|
||||||
parallelDiffLines() {
|
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>
|
</script>
|
||||||
|
|
||||||
|
@ -104,119 +28,26 @@ export default {
|
||||||
<div
|
<div
|
||||||
:class="userColorScheme"
|
:class="userColorScheme"
|
||||||
:data-commit-id="commitId"
|
: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>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<template
|
<template
|
||||||
v-for="(line, index) in parallelDiffLines"
|
v-for="(line, index) in parallelDiffLines"
|
||||||
>
|
>
|
||||||
<tr
|
<diff-table-row
|
||||||
|
:diff-file="diffFile"
|
||||||
|
:line="line"
|
||||||
|
:is-bottom="index + 1 === diffLinesLength"
|
||||||
:key="index"
|
:key="index"
|
||||||
:class="getRowClass(line)"
|
/>
|
||||||
class="line_holder parallel"
|
<parallel-diff-comment-row
|
||||||
@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)"
|
|
||||||
:key="line.left.lineCode || line.right.lineCode"
|
:key="line.left.lineCode || line.right.lineCode"
|
||||||
:class="hasDiscussion(line) ? '' : 'js-temp-notes-holder'"
|
:line="line"
|
||||||
class="notes_holder"
|
:diff-file="diffFile"
|
||||||
>
|
:diff-lines="parallelDiffLines"
|
||||||
<td class="notes_line old"></td>
|
:line-index="index"
|
||||||
<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>
|
|
||||||
</template>
|
</template>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -14,6 +14,8 @@ export const TEXT_DIFF_POSITION_TYPE = 'text';
|
||||||
|
|
||||||
export const LINE_POSITION_LEFT = 'left';
|
export const LINE_POSITION_LEFT = 'left';
|
||||||
export const LINE_POSITION_RIGHT = 'right';
|
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 DIFF_VIEW_COOKIE_NAME = 'diff_view';
|
||||||
export const LINE_HOVER_CLASS_NAME = 'is-over';
|
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 diffDiscussions from '../components/diff_discussions.vue';
|
||||||
import diffLineGutterContent from '../components/diff_line_gutter_content.vue';
|
import diffLineGutterContent from '../components/diff_line_gutter_content.vue';
|
||||||
import diffLineNoteForm from '../components/diff_line_note_form.vue';
|
import diffLineNoteForm from '../components/diff_line_note_form.vue';
|
||||||
|
import diffTableRow from '../components/diff_table_row.vue';
|
||||||
import { trimFirstCharOfLineContent } from '../store/utils';
|
import { trimFirstCharOfLineContent } from '../store/utils';
|
||||||
import { CONTEXT_LINE_TYPE, CONTEXT_LINE_CLASS_NAME } from '../constants';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
@ -16,22 +16,14 @@ export default {
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
hoveredLineCode: null,
|
|
||||||
hoveredSection: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
components: {
|
components: {
|
||||||
diffDiscussions,
|
diffDiscussions,
|
||||||
|
diffTableRow,
|
||||||
diffLineNoteForm,
|
diffLineNoteForm,
|
||||||
diffLineGutterContent,
|
diffLineGutterContent,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapGetters(['commit']),
|
||||||
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
|
|
||||||
}),
|
|
||||||
...mapGetters(['discussionsByLineCode', 'isLoggedIn', 'commit']),
|
|
||||||
commitId() {
|
commitId() {
|
||||||
return this.commit && this.commit.id;
|
return this.commit && this.commit.id;
|
||||||
},
|
},
|
||||||
|
@ -41,15 +33,15 @@ export default {
|
||||||
normalizedDiffLines() {
|
normalizedDiffLines() {
|
||||||
return this.diffLines.map(line => {
|
return this.diffLines.map(line => {
|
||||||
if (line.richText) {
|
if (line.richText) {
|
||||||
return this.trimFirstChar(line);
|
return trimFirstCharOfLineContent(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line.left) {
|
if (line.left) {
|
||||||
Object.assign(line, { left: this.trimFirstChar(line.left) });
|
Object.assign(line, { left: trimFirstCharOfLineContent(line.left) });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line.right) {
|
if (line.right) {
|
||||||
Object.assign(line, { right: this.trimFirstChar(line.right) });
|
Object.assign(line, { right: trimFirstCharOfLineContent(line.right) });
|
||||||
}
|
}
|
||||||
|
|
||||||
return line;
|
return line;
|
||||||
|
@ -62,28 +54,4 @@ export default {
|
||||||
return this.diffFile.fileHash;
|
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: {
|
computed: {
|
||||||
...mapState(['currentBranchId', 'changedFiles', 'stagedFiles']),
|
...mapState(['currentBranchId', 'changedFiles', 'stagedFiles']),
|
||||||
...mapGetters(['currentProject']),
|
...mapGetters(['currentProject', 'currentBranch']),
|
||||||
commitToCurrentBranchText() {
|
commitToCurrentBranchText() {
|
||||||
return sprintf(
|
return sprintf(
|
||||||
__('Commit to %{branchName} branch'),
|
__('Commit to %{branchName} branch'),
|
||||||
|
@ -22,17 +22,30 @@ export default {
|
||||||
return this.changedFiles.length > 0 && this.stagedFiles.length > 0;
|
return this.changedFiles.length > 0 && this.stagedFiles.length > 0;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
disableMergeRequestRadio() {
|
||||||
|
this.updateSelectedCommitAction();
|
||||||
|
},
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.disableMergeRequestRadio) {
|
this.updateSelectedCommitAction();
|
||||||
this.updateCommitAction(consts.COMMIT_TO_CURRENT_BRANCH);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions('commit', ['updateCommitAction']),
|
...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,
|
commitToCurrentBranch: consts.COMMIT_TO_CURRENT_BRANCH,
|
||||||
commitToNewBranch: consts.COMMIT_TO_NEW_BRANCH,
|
commitToNewBranch: consts.COMMIT_TO_NEW_BRANCH,
|
||||||
commitToNewBranchMR: consts.COMMIT_TO_NEW_BRANCH_MR,
|
commitToNewBranchMR: consts.COMMIT_TO_NEW_BRANCH_MR,
|
||||||
|
currentBranchPermissionsTooltip: __(
|
||||||
|
"This option is disabled as you don't have write permissions for the current branch",
|
||||||
|
),
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -40,9 +53,11 @@ export default {
|
||||||
<div class="append-bottom-15 ide-commit-radios">
|
<div class="append-bottom-15 ide-commit-radios">
|
||||||
<radio-group
|
<radio-group
|
||||||
:value="$options.commitToCurrentBranch"
|
:value="$options.commitToCurrentBranch"
|
||||||
:checked="true"
|
:disabled="currentBranch && !currentBranch.can_push"
|
||||||
|
:title="$options.currentBranchPermissionsTooltip"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
class="ide-radio-label"
|
||||||
v-html="commitToCurrentBranchText"
|
v-html="commitToCurrentBranchText"
|
||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
|
@ -56,6 +71,7 @@ export default {
|
||||||
v-if="currentProject.merge_requests_enabled"
|
v-if="currentProject.merge_requests_enabled"
|
||||||
:value="$options.commitToNewBranchMR"
|
:value="$options.commitToNewBranchMR"
|
||||||
:label="__('Create a new branch and merge request')"
|
:label="__('Create a new branch and merge request')"
|
||||||
|
:title="__('This option is disabled while you still have unstaged changes')"
|
||||||
:show-input="true"
|
:show-input="true"
|
||||||
:disabled="disableMergeRequestRadio"
|
:disabled="disableMergeRequestRadio"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -24,7 +24,7 @@ export default {
|
||||||
...mapState(['changedFiles', 'stagedFiles', 'currentActivityView', 'lastCommitMsg']),
|
...mapState(['changedFiles', 'stagedFiles', 'currentActivityView', 'lastCommitMsg']),
|
||||||
...mapState('commit', ['commitMessage', 'submitCommitLoading']),
|
...mapState('commit', ['commitMessage', 'submitCommitLoading']),
|
||||||
...mapGetters(['hasChanges']),
|
...mapGetters(['hasChanges']),
|
||||||
...mapGetters('commit', ['commitButtonDisabled', 'discardDraftButtonDisabled']),
|
...mapGetters('commit', ['discardDraftButtonDisabled', 'preBuiltCommitMessage']),
|
||||||
overviewText() {
|
overviewText() {
|
||||||
return sprintf(
|
return sprintf(
|
||||||
__(
|
__(
|
||||||
|
@ -36,6 +36,9 @@ export default {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
commitButtonText() {
|
||||||
|
return this.stagedFiles.length ? __('Commit') : __('Stage & Commit');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
currentActivityView() {
|
currentActivityView() {
|
||||||
|
@ -136,14 +139,14 @@ export default {
|
||||||
</transition>
|
</transition>
|
||||||
<commit-message-field
|
<commit-message-field
|
||||||
:text="commitMessage"
|
:text="commitMessage"
|
||||||
|
:placeholder="preBuiltCommitMessage"
|
||||||
@input="updateCommitMessage"
|
@input="updateCommitMessage"
|
||||||
/>
|
/>
|
||||||
<div class="clearfix prepend-top-15">
|
<div class="clearfix prepend-top-15">
|
||||||
<actions />
|
<actions />
|
||||||
<loading-button
|
<loading-button
|
||||||
:loading="submitCommitLoading"
|
:loading="submitCommitLoading"
|
||||||
:disabled="commitButtonDisabled"
|
:label="commitButtonText"
|
||||||
:label="__('Commit')"
|
|
||||||
container-class="btn btn-success btn-sm float-left"
|
container-class="btn btn-success btn-sm float-left"
|
||||||
@click="commitChanges"
|
@click="commitChanges"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -16,6 +16,10 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -114,7 +118,7 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
ref="textarea"
|
ref="textarea"
|
||||||
:placeholder="__('Write a commit message...')"
|
:placeholder="placeholder"
|
||||||
:value="text"
|
:value="text"
|
||||||
class="note-textarea ide-commit-message-textarea"
|
class="note-textarea ide-commit-message-textarea"
|
||||||
name="commit-message"
|
name="commit-message"
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { mapActions, mapState, mapGetters } from 'vuex';
|
import { mapActions, mapState, mapGetters } from 'vuex';
|
||||||
import { __ } from '~/locale';
|
|
||||||
import tooltip from '~/vue_shared/directives/tooltip';
|
import tooltip from '~/vue_shared/directives/tooltip';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -32,14 +31,17 @@ export default {
|
||||||
required: false,
|
required: false,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState('commit', ['commitAction']),
|
...mapState('commit', ['commitAction']),
|
||||||
...mapGetters('commit', ['newBranchName']),
|
...mapGetters('commit', ['newBranchName']),
|
||||||
tooltipTitle() {
|
tooltipTitle() {
|
||||||
return this.disabled
|
return this.disabled ? this.title : '';
|
||||||
? __('This option is disabled while you still have unstaged changes')
|
|
||||||
: '';
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
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 RepoEditor from './repo_editor.vue';
|
||||||
import FindFile from './file_finder/index.vue';
|
import FindFile from './file_finder/index.vue';
|
||||||
import RightPane from './panes/right.vue';
|
import RightPane from './panes/right.vue';
|
||||||
|
import ErrorMessage from './error_message.vue';
|
||||||
|
|
||||||
const originalStopCallback = Mousetrap.stopCallback;
|
const originalStopCallback = Mousetrap.stopCallback;
|
||||||
|
|
||||||
|
@ -18,6 +19,7 @@ export default {
|
||||||
RepoEditor,
|
RepoEditor,
|
||||||
FindFile,
|
FindFile,
|
||||||
RightPane,
|
RightPane,
|
||||||
|
ErrorMessage,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState([
|
...mapState([
|
||||||
|
@ -28,6 +30,7 @@ export default {
|
||||||
'fileFindVisible',
|
'fileFindVisible',
|
||||||
'emptyStateSvgPath',
|
'emptyStateSvgPath',
|
||||||
'currentProjectId',
|
'currentProjectId',
|
||||||
|
'errorMessage',
|
||||||
]),
|
]),
|
||||||
...mapGetters(['activeFile', 'hasChanges']),
|
...mapGetters(['activeFile', 'hasChanges']),
|
||||||
},
|
},
|
||||||
|
@ -72,6 +75,10 @@ export default {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<article class="ide">
|
<article class="ide">
|
||||||
|
<error-message
|
||||||
|
v-if="errorMessage"
|
||||||
|
:message="errorMessage"
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
class="ide-view"
|
class="ide-view"
|
||||||
>
|
>
|
||||||
|
|
|
@ -28,7 +28,7 @@ export default {
|
||||||
]),
|
]),
|
||||||
...mapState('commit', ['commitMessage', 'submitCommitLoading']),
|
...mapState('commit', ['commitMessage', 'submitCommitLoading']),
|
||||||
...mapGetters(['lastOpenedFile', 'hasChanges', 'someUncommitedChanges', 'activeFile']),
|
...mapGetters(['lastOpenedFile', 'hasChanges', 'someUncommitedChanges', 'activeFile']),
|
||||||
...mapGetters('commit', ['commitButtonDisabled', 'discardDraftButtonDisabled']),
|
...mapGetters('commit', ['discardDraftButtonDisabled']),
|
||||||
showStageUnstageArea() {
|
showStageUnstageArea() {
|
||||||
return !!(this.someUncommitedChanges || this.lastCommitMsg || !this.unusedSeal);
|
return !!(this.someUncommitedChanges || this.lastCommitMsg || !this.unusedSeal);
|
||||||
},
|
},
|
||||||
|
|
|
@ -95,14 +95,6 @@ router.beforeEach((to, from, next) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
flash(
|
|
||||||
'Error while loading the branch files. Please try again.',
|
|
||||||
'alert',
|
|
||||||
document,
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
throw e;
|
throw e;
|
||||||
});
|
});
|
||||||
} else if (to.params.mrid) {
|
} else if (to.params.mrid) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import VueResource from 'vue-resource';
|
import VueResource from 'vue-resource';
|
||||||
|
import axios from '~/lib/utils/axios_utils';
|
||||||
import Api from '~/api';
|
import Api from '~/api';
|
||||||
|
|
||||||
Vue.use(VueResource);
|
Vue.use(VueResource);
|
||||||
|
@ -69,11 +70,7 @@ export default {
|
||||||
},
|
},
|
||||||
getFiles(projectUrl, branchId) {
|
getFiles(projectUrl, branchId) {
|
||||||
const url = `${projectUrl}/files/${branchId}`;
|
const url = `${projectUrl}/files/${branchId}`;
|
||||||
return Vue.http.get(url, {
|
return axios.get(url, { params: { format: 'json' } });
|
||||||
params: {
|
|
||||||
format: 'json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
lastCommitPipelines({ getters }) {
|
lastCommitPipelines({ getters }) {
|
||||||
const commitSha = getters.lastCommit.id;
|
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 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/tree';
|
||||||
export * from './actions/file';
|
export * from './actions/file';
|
||||||
export * from './actions/project';
|
export * from './actions/project';
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
import _ from 'underscore';
|
||||||
import flash from '~/flash';
|
import flash from '~/flash';
|
||||||
import { __ } from '~/locale';
|
import { __, sprintf } from '~/locale';
|
||||||
import service from '../../services';
|
import service from '../../services';
|
||||||
|
import api from '../../../api';
|
||||||
import * as types from '../mutation_types';
|
import * as types from '../mutation_types';
|
||||||
|
import router from '../../ide_router';
|
||||||
|
|
||||||
export const getProjectData = ({ commit, state }, { namespace, projectId, force = false } = {}) =>
|
export const getProjectData = ({ commit, state }, { namespace, projectId, force = false } = {}) =>
|
||||||
new Promise((resolve, reject) => {
|
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) => {
|
new Promise((resolve, reject) => {
|
||||||
if (
|
if (
|
||||||
typeof state.projects[`${projectId}`] === 'undefined' ||
|
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 });
|
commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id });
|
||||||
resolve(data);
|
resolve(data);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(e => {
|
||||||
flash(
|
if (e.response.status === 404) {
|
||||||
__('Error loading branch data. Please try again.'),
|
dispatch('showBranchNotFoundError', branchId);
|
||||||
'alert',
|
} else {
|
||||||
document,
|
flash(
|
||||||
null,
|
__('Error loading branch data. Please try again.'),
|
||||||
false,
|
'alert',
|
||||||
true,
|
document,
|
||||||
);
|
null,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
reject(new Error(`Branch not loaded - ${projectId}/${branchId}`));
|
reject(new Error(`Branch not loaded - ${projectId}/${branchId}`));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -80,3 +90,37 @@ export const refreshLastCommitData = ({ commit }, { projectId, branchId } = {})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
flash(__('Error loading last commit.'), 'alert', document, null, false, true);
|
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 { normalizeHeaders } from '~/lib/utils/common_utils';
|
||||||
import flash from '~/flash';
|
import flash from '~/flash';
|
||||||
|
import { __ } from '../../../locale';
|
||||||
import service from '../../services';
|
import service from '../../services';
|
||||||
import * as types from '../mutation_types';
|
import * as types from '../mutation_types';
|
||||||
import { findEntry } from '../utils';
|
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));
|
.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) => {
|
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];
|
const selectedProject = state.projects[projectId];
|
||||||
commit(types.CREATE_TREE, { treePath: `${projectId}/${branchId}` });
|
commit(types.CREATE_TREE, { treePath: `${projectId}/${branchId}` });
|
||||||
|
|
||||||
service
|
service
|
||||||
.getFiles(selectedProject.web_url, branchId)
|
.getFiles(selectedProject.web_url, branchId)
|
||||||
.then(res => res.json())
|
.then(({ data }) => {
|
||||||
.then(data => {
|
|
||||||
const worker = new FilesDecoratorWorker();
|
const worker = new FilesDecoratorWorker();
|
||||||
worker.addEventListener('message', e => {
|
worker.addEventListener('message', e => {
|
||||||
const { entries, treeList } = e.data;
|
const { entries, treeList } = e.data;
|
||||||
|
@ -99,7 +103,18 @@ export const getFiles = ({ state, commit }, { projectId, branchId } = {}) =>
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.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);
|
reject(e);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -82,10 +82,13 @@ export const getStagedFilesCountForPath = state => path =>
|
||||||
getChangesCountForFiles(state.stagedFiles, path);
|
getChangesCountForFiles(state.stagedFiles, path);
|
||||||
|
|
||||||
export const lastCommit = (state, getters) => {
|
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;
|
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
|
// prevent babel-plugin-rewire from generating an invalid default during karma tests
|
||||||
export default () => {};
|
export default () => {};
|
||||||
|
|
|
@ -103,17 +103,24 @@ export const updateFilesAfterCommit = ({ commit, dispatch, rootState }, { data }
|
||||||
|
|
||||||
export const commitChanges = ({ commit, state, getters, dispatch, rootState, rootGetters }) => {
|
export const commitChanges = ({ commit, state, getters, dispatch, rootState, rootGetters }) => {
|
||||||
const newBranch = state.commitAction !== consts.COMMIT_TO_CURRENT_BRANCH;
|
const newBranch = state.commitAction !== consts.COMMIT_TO_CURRENT_BRANCH;
|
||||||
const payload = createCommitPayload({
|
const stageFilesPromise = rootState.stagedFiles.length
|
||||||
branch: getters.branchName,
|
? Promise.resolve()
|
||||||
newBranch,
|
: dispatch('stageAllChanges', null, { root: true });
|
||||||
state,
|
|
||||||
rootState,
|
|
||||||
});
|
|
||||||
|
|
||||||
commit(types.UPDATE_LOADING, true);
|
commit(types.UPDATE_LOADING, true);
|
||||||
|
|
||||||
return service
|
return stageFilesPromise
|
||||||
.commit(rootState.currentProjectId, payload)
|
.then(() => {
|
||||||
|
const payload = createCommitPayload({
|
||||||
|
branch: getters.branchName,
|
||||||
|
newBranch,
|
||||||
|
getters,
|
||||||
|
state,
|
||||||
|
rootState,
|
||||||
|
});
|
||||||
|
|
||||||
|
return service.commit(rootState.currentProjectId, payload);
|
||||||
|
})
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
commit(types.UPDATE_LOADING, false);
|
commit(types.UPDATE_LOADING, false);
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { sprintf, n__ } from '../../../../locale';
|
||||||
import * as consts from './constants';
|
import * as consts from './constants';
|
||||||
|
|
||||||
const BRANCH_SUFFIX_COUNT = 5;
|
const BRANCH_SUFFIX_COUNT = 5;
|
||||||
|
@ -5,9 +6,6 @@ const BRANCH_SUFFIX_COUNT = 5;
|
||||||
export const discardDraftButtonDisabled = state =>
|
export const discardDraftButtonDisabled = state =>
|
||||||
state.commitMessage === '' || state.submitCommitLoading;
|
state.commitMessage === '' || state.submitCommitLoading;
|
||||||
|
|
||||||
export const commitButtonDisabled = (state, getters, rootState) =>
|
|
||||||
getters.discardDraftButtonDisabled || !rootState.stagedFiles.length;
|
|
||||||
|
|
||||||
export const newBranchName = (state, _, rootState) =>
|
export const newBranchName = (state, _, rootState) =>
|
||||||
`${gon.current_username}-${rootState.currentBranchId}-patch-${`${new Date().getTime()}`.substr(
|
`${gon.current_username}-${rootState.currentBranchId}-patch-${`${new Date().getTime()}`.substr(
|
||||||
-BRANCH_SUFFIX_COUNT,
|
-BRANCH_SUFFIX_COUNT,
|
||||||
|
@ -28,5 +26,18 @@ export const branchName = (state, getters, rootState) => {
|
||||||
return rootState.currentBranchId;
|
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
|
// prevent babel-plugin-rewire from generating an invalid default during karma tests
|
||||||
export default () => {};
|
export default () => {};
|
||||||
|
|
|
@ -72,3 +72,5 @@ export const SET_RIGHT_PANE = 'SET_RIGHT_PANE';
|
||||||
|
|
||||||
export const CLEAR_PROJECTS = 'CLEAR_PROJECTS';
|
export const CLEAR_PROJECTS = 'CLEAR_PROJECTS';
|
||||||
export const RESET_OPEN_FILES = 'RESET_OPEN_FILES';
|
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) {
|
[types.RESET_OPEN_FILES](state) {
|
||||||
Object.assign(state, { openFiles: [] });
|
Object.assign(state, { openFiles: [] });
|
||||||
},
|
},
|
||||||
|
[types.SET_ERROR_MESSAGE](state, errorMessage) {
|
||||||
|
Object.assign(state, { errorMessage });
|
||||||
|
},
|
||||||
...projectMutations,
|
...projectMutations,
|
||||||
...mergeRequestMutation,
|
...mergeRequestMutation,
|
||||||
...fileMutations,
|
...fileMutations,
|
||||||
|
|
|
@ -25,4 +25,5 @@ export default () => ({
|
||||||
fileFindVisible: false,
|
fileFindVisible: false,
|
||||||
rightPane: null,
|
rightPane: null,
|
||||||
links: {},
|
links: {},
|
||||||
|
errorMessage: null,
|
||||||
});
|
});
|
||||||
|
|
|
@ -105,9 +105,9 @@ export const setPageTitle = title => {
|
||||||
document.title = title;
|
document.title = title;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createCommitPayload = ({ branch, newBranch, state, rootState }) => ({
|
export const createCommitPayload = ({ branch, getters, newBranch, state, rootState }) => ({
|
||||||
branch,
|
branch,
|
||||||
commit_message: state.commitMessage,
|
commit_message: state.commitMessage || getters.preBuiltCommitMessage,
|
||||||
actions: rootState.stagedFiles.map(f => ({
|
actions: rootState.stagedFiles.map(f => ({
|
||||||
action: f.tempFile ? 'create' : 'update',
|
action: f.tempFile ? 'create' : 'update',
|
||||||
file_path: f.path,
|
file_path: f.path,
|
||||||
|
|
|
@ -164,7 +164,7 @@ export default class Job extends LogOutputBehaviours {
|
||||||
// eslint-disable-next-line class-methods-use-this
|
// eslint-disable-next-line class-methods-use-this
|
||||||
shouldHideSidebarForViewport() {
|
shouldHideSidebarForViewport() {
|
||||||
const bootstrapBreakpoint = bp.getBreakpointSize();
|
const bootstrapBreakpoint = bp.getBreakpointSize();
|
||||||
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
|
return bootstrapBreakpoint === 'xs';
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleSidebar(shouldHide) {
|
toggleSidebar(shouldHide) {
|
||||||
|
|
|
@ -200,6 +200,7 @@ export default {
|
||||||
:class="getAwardClassBindings(awardList, awardName)"
|
:class="getAwardClassBindings(awardList, awardName)"
|
||||||
:title="awardTitle(awardList)"
|
:title="awardTitle(awardList)"
|
||||||
class="btn award-control"
|
class="btn award-control"
|
||||||
|
data-boundary="viewport"
|
||||||
data-placement="bottom"
|
data-placement="bottom"
|
||||||
type="button"
|
type="button"
|
||||||
@click="handleAward(awardName)">
|
@click="handleAward(awardName)">
|
||||||
|
@ -217,6 +218,7 @@ export default {
|
||||||
class="award-control btn js-add-award"
|
class="award-control btn js-add-award"
|
||||||
title="Add reaction"
|
title="Add reaction"
|
||||||
aria-label="Add reaction"
|
aria-label="Add reaction"
|
||||||
|
data-boundary="viewport"
|
||||||
data-placement="bottom"
|
data-placement="bottom"
|
||||||
type="button">
|
type="button">
|
||||||
<span
|
<span
|
||||||
|
|
|
@ -48,7 +48,7 @@ export default class Wikis {
|
||||||
|
|
||||||
static sidebarCanCollapse() {
|
static sidebarCanCollapse() {
|
||||||
const bootstrapBreakpoint = bp.getBreakpointSize();
|
const bootstrapBreakpoint = bp.getBreakpointSize();
|
||||||
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
|
return bootstrapBreakpoint === 'xs';
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSidebar() {
|
renderSidebar() {
|
||||||
|
|
|
@ -113,7 +113,7 @@ export default {
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="currentRequest"
|
v-if="currentRequest"
|
||||||
class="container-fluid container-limited"
|
class="d-flex container-fluid container-limited"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
id="peek-view-host"
|
id="peek-view-host"
|
||||||
|
@ -179,6 +179,7 @@ export default {
|
||||||
v-if="currentRequest"
|
v-if="currentRequest"
|
||||||
:current-request="currentRequest"
|
:current-request="currentRequest"
|
||||||
:requests="requests"
|
:requests="requests"
|
||||||
|
class="ml-auto"
|
||||||
@change-current-request="changeCurrentRequest"
|
@change-current-request="changeCurrentRequest"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -35,10 +35,7 @@ export default {
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div id="peek-request-selector">
|
||||||
id="peek-request-selector"
|
|
||||||
class="float-right"
|
|
||||||
>
|
|
||||||
<select v-model="currentRequestId">
|
<select v-model="currentRequestId">
|
||||||
<option
|
<option
|
||||||
v-for="request in requests"
|
v-for="request in requests"
|
||||||
|
|
|
@ -61,7 +61,13 @@ export default class Profile {
|
||||||
url: this.form.attr('action'),
|
url: this.form.attr('action'),
|
||||||
data: formData,
|
data: formData,
|
||||||
})
|
})
|
||||||
.then(({ data }) => flash(data.message, 'notice'))
|
.then(({ data }) => {
|
||||||
|
if (avatarBlob != null) {
|
||||||
|
this.updateHeaderAvatar();
|
||||||
|
}
|
||||||
|
|
||||||
|
flash(data.message, 'notice');
|
||||||
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
// Enable submit button after requests ends
|
// Enable submit button after requests ends
|
||||||
|
@ -70,6 +76,10 @@ export default class Profile {
|
||||||
.catch(error => flash(error.message));
|
.catch(error => flash(error.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateHeaderAvatar() {
|
||||||
|
$('.header-user-avatar').attr('src', this.avatarGlCrop.dataURL);
|
||||||
|
}
|
||||||
|
|
||||||
setRepoRadio() {
|
setRepoRadio() {
|
||||||
const multiEditRadios = $('input[name="user[multi_file]"]');
|
const multiEditRadios = $('input[name="user[multi_file]"]');
|
||||||
if (this.newRepoActivated || this.newRepoActivated === 'true') {
|
if (this.newRepoActivated || this.newRepoActivated === 'true') {
|
||||||
|
|
|
@ -104,6 +104,10 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 3px;
|
top: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> gl-emoji {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.award-menu-holder {
|
.award-menu-holder {
|
||||||
|
|
|
@ -4,4 +4,5 @@ gl-emoji {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
|
line-height: 0.9;
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.flash-action {
|
.flash-action {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: $gl-font-weight-normal;
|
font-weight: $gl-font-weight-normal;
|
||||||
|
|
|
@ -233,7 +233,7 @@ $md-area-border: #ddd;
|
||||||
/*
|
/*
|
||||||
* Code
|
* Code
|
||||||
*/
|
*/
|
||||||
$code_font_size: 12px;
|
$code_font_size: 90%;
|
||||||
$code_line_height: 1.6;
|
$code_line_height: 1.6;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -93,6 +93,10 @@
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
vertical-align: text-top;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.light-well {
|
.light-well {
|
||||||
|
|
|
@ -102,7 +102,9 @@ pre.code,
|
||||||
// Diff line
|
// Diff line
|
||||||
.line_holder {
|
.line_holder {
|
||||||
|
|
||||||
&.match .line_content {
|
&.match .line_content,
|
||||||
|
.new-nonewline.line_content,
|
||||||
|
.old-nonewline.line_content {
|
||||||
@include matchLine;
|
@include matchLine;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -698,7 +698,7 @@
|
||||||
&.diff-files-changed-merge-request {
|
&.diff-files-changed-merge-request {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 90px;
|
top: 90px;
|
||||||
z-index: 190;
|
z-index: 200;
|
||||||
margin: $gl-padding 0;
|
margin: $gl-padding 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
@ -706,6 +706,7 @@
|
||||||
&.is-stuck {
|
&.is-stuck {
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
|
border-top: 1px solid $white-dark;
|
||||||
border-bottom: 1px solid $white-dark;
|
border-bottom: 1px solid $white-dark;
|
||||||
|
|
||||||
.diff-stats-additions-deletions-expanded,
|
.diff-stats-additions-deletions-expanded,
|
||||||
|
|
|
@ -721,7 +721,7 @@ ul.notes {
|
||||||
.line-resolve-all {
|
.line-resolve-all {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 6px 10px;
|
padding: 5px 10px 6px;
|
||||||
background-color: $gray-light;
|
background-color: $gray-light;
|
||||||
border: 1px solid $border-color;
|
border: 1px solid $border-color;
|
||||||
border-radius: $border-radius-default;
|
border-radius: $border-radius-default;
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 2000;
|
z-index: 2000;
|
||||||
overflow-x: hidden;
|
|
||||||
|
|
||||||
height: $performance-bar-height;
|
height: $performance-bar-height;
|
||||||
background: $black;
|
background: $black;
|
||||||
|
@ -82,7 +81,7 @@
|
||||||
|
|
||||||
.view {
|
.view {
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
float: left;
|
flex-shrink: 0;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
module Types
|
module Types
|
||||||
class BaseObject < GraphQL::Schema::Object
|
class BaseObject < GraphQL::Schema::Object
|
||||||
prepend Gitlab::Graphql::Present
|
prepend Gitlab::Graphql::Present
|
||||||
|
prepend Gitlab::Graphql::ExposePermissions
|
||||||
|
|
||||||
field_class Types::BaseField
|
field_class Types::BaseField
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
module Types
|
module Types
|
||||||
class MergeRequestType < BaseObject
|
class MergeRequestType < BaseObject
|
||||||
|
expose_permissions Types::PermissionTypes::MergeRequest
|
||||||
|
|
||||||
present_using MergeRequestPresenter
|
present_using MergeRequestPresenter
|
||||||
|
|
||||||
graphql_name 'MergeRequest'
|
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
|
module Types
|
||||||
class ProjectType < BaseObject
|
class ProjectType < BaseObject
|
||||||
|
expose_permissions Types::PermissionTypes::Project
|
||||||
|
|
||||||
graphql_name 'Project'
|
graphql_name 'Project'
|
||||||
|
|
||||||
field :id, GraphQL::ID_TYPE, null: false
|
field :id, GraphQL::ID_TYPE, null: false
|
||||||
|
|
|
@ -168,6 +168,10 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
|
||||||
.can_push_to_branch?(source_branch)
|
.can_push_to_branch?(source_branch)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def can_remove_source_branch?
|
||||||
|
source_branch_exists? && merge_request.can_remove_source_branch?(current_user)
|
||||||
|
end
|
||||||
|
|
||||||
def mergeable_discussions_state
|
def mergeable_discussions_state
|
||||||
# This avoids calling MergeRequest#mergeable_discussions_state without
|
# This avoids calling MergeRequest#mergeable_discussions_state without
|
||||||
# considering the state of the MR first. If a MR isn't mergeable, we can
|
# 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 :current_user do
|
||||||
expose :can_remove_source_branch do |merge_request|
|
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
|
end
|
||||||
|
|
||||||
expose :can_revert_on_current_merge_request do |merge_request|
|
expose :can_revert_on_current_merge_request do |merge_request|
|
||||||
|
|
|
@ -6,9 +6,9 @@ module MergeRequests
|
||||||
#
|
#
|
||||||
class PostMergeService < MergeRequests::BaseService
|
class PostMergeService < MergeRequests::BaseService
|
||||||
def execute(merge_request)
|
def execute(merge_request)
|
||||||
|
merge_request.mark_as_merged
|
||||||
close_issues(merge_request)
|
close_issues(merge_request)
|
||||||
todo_service.merge_merge_request(merge_request, current_user)
|
todo_service.merge_merge_request(merge_request, current_user)
|
||||||
merge_request.mark_as_merged
|
|
||||||
create_event(merge_request)
|
create_event(merge_request)
|
||||||
create_note(merge_request)
|
create_note(merge_request)
|
||||||
notification_service.merge_mr(merge_request, current_user)
|
notification_service.merge_mr(merge_request, current_user)
|
||||||
|
|
|
@ -18,10 +18,18 @@ module MergeRequests
|
||||||
return false
|
return false
|
||||||
end
|
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)
|
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)
|
merge_request.update_attributes(rebase_commit_sha: rebase_sha)
|
||||||
|
|
||||||
|
Gitlab::GitLogger.info("#{log_prefix} rebase SHA saved: #{rebase_sha}")
|
||||||
|
|
||||||
true
|
true
|
||||||
rescue => e
|
rescue => e
|
||||||
log_error(REBASE_ERROR, save_message_on_model: true)
|
log_error(REBASE_ERROR, save_message_on_model: true)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
- project = local_assigns.fetch(:project)
|
- project = local_assigns.fetch(:project)
|
||||||
- expanded = Rails.env.test?
|
- 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
|
.settings-header
|
||||||
%h4
|
%h4
|
||||||
Export project
|
Export project
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
- expanded = expand_deploy_tokens_section?(@new_deploy_token)
|
- 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
|
.settings-header
|
||||||
%h4= s_('DeployTokens|Deploy Tokens')
|
%h4= s_('DeployTokens|Deploy Tokens')
|
||||||
%button.btn.js-settings-toggle.qa-expand-deploy-keys{ type: 'button' }
|
%button.btn.js-settings-toggle.qa-expand-deploy-keys{ type: 'button' }
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
- expanded = Rails.env.test?
|
- expanded = Rails.env.test?
|
||||||
|
|
||||||
.project-edit-container
|
.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
|
.settings-header
|
||||||
%h4
|
%h4
|
||||||
General project
|
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"
|
= 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"
|
= 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
|
.settings-header
|
||||||
%h4
|
%h4
|
||||||
Permissions
|
Permissions
|
||||||
|
@ -82,7 +82,7 @@
|
||||||
|
|
||||||
= render_if_exists 'projects/issues_settings'
|
= 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
|
.settings-header
|
||||||
%h4
|
%h4
|
||||||
Merge request
|
Merge request
|
||||||
|
@ -101,7 +101,7 @@
|
||||||
|
|
||||||
= render 'export', project: @project
|
= 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
|
.settings-header
|
||||||
%h4
|
%h4
|
||||||
Advanced
|
Advanced
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
- if environment.external_url && can?(current_user, :read_environment, environment)
|
- 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
|
= 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 deployment
|
||||||
|
|
|
@ -3,5 +3,5 @@
|
||||||
- return unless can?(current_user, :read_environment, environment)
|
- return unless can?(current_user, :read_environment, environment)
|
||||||
|
|
||||||
= link_to environment_metrics_path(environment), title: 'See metrics', class: 'btn metrics-button' do
|
= link_to environment_metrics_path(environment), title: 'See metrics', class: 'btn metrics-button' do
|
||||||
= icon('area-chart')
|
= sprite_icon('chart')
|
||||||
Monitoring
|
Monitoring
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
- if environment.has_terminals? && can?(current_user, :admin_environment, @project)
|
- if environment.has_terminals? && can?(current_user, :admin_environment, @project)
|
||||||
= link_to terminal_project_environment_path(@project, environment), class: 'btn terminal-button' do
|
= link_to terminal_project_environment_path(@project, environment), class: 'btn terminal-button' do
|
||||||
= icon('terminal')
|
= sprite_icon('terminal')
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
.nav-controls
|
.nav-controls
|
||||||
- if @environment.external_url.present?
|
- if @environment.external_url.present?
|
||||||
= link_to @environment.external_url, class: 'btn btn-default', target: '_blank', rel: 'noopener noreferrer nofollow' do
|
= 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
|
= render 'projects/deployments/actions', deployment: @environment.last_deployment
|
||||||
|
|
||||||
.terminal-container{ class: container_class }
|
.terminal-container{ class: container_class }
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
- expanded = Rails.env.test?
|
- 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
|
.settings-header
|
||||||
%h4
|
%h4
|
||||||
Push to a remote repository
|
Push to a remote repository
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
- expanded = Rails.env.test?
|
- 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
|
.settings-header
|
||||||
%h4
|
%h4
|
||||||
Protected Tags
|
Protected Tags
|
||||||
|
|
|
@ -6,13 +6,13 @@
|
||||||
1.
|
1.
|
||||||
= link_to 'https://docs.mattermost.com/developer/slash-commands.html#enabling-custom-commands', target: '_blank', rel: 'noopener noreferrer nofollow' do
|
= link_to 'https://docs.mattermost.com/developer/slash-commands.html#enabling-custom-commands', target: '_blank', rel: 'noopener noreferrer nofollow' do
|
||||||
Enable custom slash commands
|
Enable custom slash commands
|
||||||
= icon('external-link')
|
= sprite_icon('external-link', size: 16)
|
||||||
on your Mattermost installation
|
on your Mattermost installation
|
||||||
%li
|
%li
|
||||||
2.
|
2.
|
||||||
= link_to 'https://docs.mattermost.com/developer/slash-commands.html#set-up-a-custom-command', target: '_blank', rel: 'noopener noreferrer nofollow' do
|
= 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
|
Add a slash command
|
||||||
= icon('external-link')
|
= sprite_icon('external-link', size: 16)
|
||||||
in your Mattermost team with these options:
|
in your Mattermost team with these options:
|
||||||
%hr
|
%hr
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
project by entering slash commands in Mattermost.
|
project by entering slash commands in Mattermost.
|
||||||
= link_to help_page_path('user/project/integrations/mattermost_slash_commands.md'), target: '_blank' do
|
= link_to help_page_path('user/project/integrations/mattermost_slash_commands.md'), target: '_blank' do
|
||||||
View documentation
|
View documentation
|
||||||
= icon('external-link')
|
= sprite_icon('external-link', size: 16)
|
||||||
%p.inline
|
%p.inline
|
||||||
See list of available commands in Mattermost after setting up this service,
|
See list of available commands in Mattermost after setting up this service,
|
||||||
by entering
|
by entering
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
project by entering slash commands in Slack.
|
project by entering slash commands in Slack.
|
||||||
= link_to help_page_path('user/project/integrations/slack_slash_commands.md'), target: '_blank' do
|
= link_to help_page_path('user/project/integrations/slack_slash_commands.md'), target: '_blank' do
|
||||||
View documentation
|
View documentation
|
||||||
= icon('external-link')
|
= sprite_icon('external-link', size: 16)
|
||||||
%p.inline
|
%p.inline
|
||||||
See list of available commands in Slack after setting up this service,
|
See list of available commands in Slack after setting up this service,
|
||||||
by entering
|
by entering
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
1.
|
1.
|
||||||
= link_to 'https://my.slack.com/services/new/slash-commands', target: '_blank', rel: 'noreferrer noopener nofollow' do
|
= link_to 'https://my.slack.com/services/new/slash-commands', target: '_blank', rel: 'noreferrer noopener nofollow' do
|
||||||
Add a slash command
|
Add a slash command
|
||||||
= icon('external-link')
|
= sprite_icon('external-link', size: 16)
|
||||||
in your Slack team with these options:
|
in your Slack team with these options:
|
||||||
|
|
||||||
%hr
|
%hr
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
.settings-content
|
.settings-content
|
||||||
= render 'ci/variables/index', save_endpoint: project_variables_path(@project)
|
= 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
|
.settings-header
|
||||||
%h4
|
%h4
|
||||||
Pipeline triggers
|
Pipeline triggers
|
||||||
|
|
|
@ -21,8 +21,9 @@
|
||||||
= sprite_icon('star-o')
|
= 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') }
|
%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')
|
= sprite_icon('star')
|
||||||
|
- if can?(current_user, :admin_label, label)
|
||||||
%li.inline
|
%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')
|
= sprite_icon('pencil')
|
||||||
%li.inline
|
%li.inline
|
||||||
.dropdown
|
.dropdown
|
||||||
|
@ -42,9 +43,10 @@
|
||||||
container: 'body',
|
container: 'body',
|
||||||
toggle: 'modal' } }
|
toggle: 'modal' } }
|
||||||
= _('Promote to group label')
|
= _('Promote to group label')
|
||||||
%li
|
- if can?(current_user, :admin_label, label)
|
||||||
%span{ data: { toggle: 'modal', target: "#modal-delete-label-#{label.id}" } }
|
%li
|
||||||
%button.text-danger.remove-row{ type: 'button' }= _('Delete')
|
%span{ data: { toggle: 'modal', target: "#modal-delete-label-#{label.id}" } }
|
||||||
|
%button.text-danger.remove-row{ type: 'button' }= _('Delete')
|
||||||
- if current_user
|
- if current_user
|
||||||
%li.inline.label-subscription
|
%li.inline.label-subscription
|
||||||
- if can_subscribe_to_label_in_different_levels?(label)
|
- if can_subscribe_to_label_in_different_levels?(label)
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
- cronjob:remove_old_web_hook_logs
|
- cronjob:remove_old_web_hook_logs
|
||||||
- cronjob:remove_unreferenced_lfs_objects
|
- cronjob:remove_unreferenced_lfs_objects
|
||||||
- cronjob:repository_archive_cache
|
- cronjob:repository_archive_cache
|
||||||
- cronjob:repository_check_batch
|
- cronjob:repository_check_dispatch
|
||||||
- cronjob:requests_profiles
|
- cronjob:requests_profiles
|
||||||
- cronjob:schedule_update_user_activity
|
- cronjob:schedule_update_user_activity
|
||||||
- cronjob:stuck_ci_jobs
|
- cronjob:stuck_ci_jobs
|
||||||
|
@ -71,6 +71,7 @@
|
||||||
- pipeline_processing:update_head_pipeline_for_merge_request
|
- pipeline_processing:update_head_pipeline_for_merge_request
|
||||||
|
|
||||||
- repository_check:repository_check_clear
|
- repository_check:repository_check_clear
|
||||||
|
- repository_check:repository_check_batch
|
||||||
- repository_check:repository_check_single_repository
|
- repository_check:repository_check_single_repository
|
||||||
|
|
||||||
- default
|
- 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
|
module RepositoryCheck
|
||||||
class BatchWorker
|
class BatchWorker
|
||||||
include ApplicationWorker
|
include ApplicationWorker
|
||||||
include CronjobQueue
|
include RepositoryCheckQueue
|
||||||
|
|
||||||
RUN_TIME = 3600
|
RUN_TIME = 3600
|
||||||
BATCH_SIZE = 10_000
|
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::CurrentSettings.repository_checks_enabled
|
||||||
|
return unless Gitlab::ShardHealthCache.healthy_shard?(shard_name)
|
||||||
|
|
||||||
start = Time.now
|
start = Time.now
|
||||||
|
|
||||||
|
@ -39,18 +44,22 @@ module RepositoryCheck
|
||||||
end
|
end
|
||||||
|
|
||||||
def never_checked_project_ids(batch_size)
|
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)
|
.where('created_at < ?', 24.hours.ago)
|
||||||
.limit(batch_size).pluck(:id)
|
.limit(batch_size).pluck(:id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def old_checked_project_ids(batch_size)
|
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)
|
.where('last_repository_check_at < ?', 1.month.ago)
|
||||||
.reorder(last_repository_check_at: :asc)
|
.reorder(last_repository_check_at: :asc)
|
||||||
.limit(batch_size).pluck(:id)
|
.limit(batch_size).pluck(:id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def projects_on_shard
|
||||||
|
Project.where(repository_storage: shard_name)
|
||||||
|
end
|
||||||
|
|
||||||
def try_obtain_lease(id)
|
def try_obtain_lease(id)
|
||||||
# Use a 24-hour timeout because on servers/projects where 'git fsck' is
|
# 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.
|
# 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
|
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
|
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).
|
# (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
|
# Otherwise, ssh host will be set to the `host:` value above
|
||||||
# ssh_host: ssh.host_example.com
|
# 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['expire_build_artifacts_worker']['job_class'] = 'ExpireBuildArtifactsWorker'
|
||||||
Settings.cron_jobs['repository_check_worker'] ||= Settingslogic.new({})
|
Settings.cron_jobs['repository_check_worker'] ||= Settingslogic.new({})
|
||||||
Settings.cron_jobs['repository_check_worker']['cron'] ||= '20 * * * *'
|
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'] ||= Settingslogic.new({})
|
||||||
Settings.cron_jobs['admin_email_worker']['cron'] ||= '0 0 * * 0'
|
Settings.cron_jobs['admin_email_worker']['cron'] ||= '0 0 * * 0'
|
||||||
Settings.cron_jobs['admin_email_worker']['job_class'] = 'AdminEmailWorker'
|
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_path = Settings.gitlab['repository_downloads_path'].to_s.gsub(%r{/$}, '')
|
||||||
repository_downloads_full_path = File.expand_path(repository_downloads_path, Settings.gitlab['user_home'])
|
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
|
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{/$}, '')) }
|
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')
|
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/)
|
!!(name =~ /\A[a-zA-Z0-9\-_]+\z/)
|
||||||
end
|
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)
|
def storage_validation_error(message)
|
||||||
raise "#{message}. Please fix this in your gitlab.yml before starting GitLab."
|
raise "#{message}. Please fix this in your gitlab.yml before starting GitLab."
|
||||||
end
|
end
|
||||||
|
@ -37,17 +23,4 @@ def validate_storages_config
|
||||||
end
|
end
|
||||||
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_config
|
||||||
validate_storages_paths unless Rails.env.test? || ENV['SKIP_STORAGE_VALIDATION'] == 'true'
|
|
||||||
|
|
|
@ -18,12 +18,17 @@ Doorkeeper::OpenidConnect.configure do
|
||||||
end
|
end
|
||||||
|
|
||||||
subject do |user|
|
subject do |user|
|
||||||
# hash the user's ID with the Rails secret_key_base to avoid revealing it
|
user.id
|
||||||
Digest::SHA256.hexdigest "#{user.id}-#{Rails.application.secrets.secret_key_base}"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
claims do
|
claims do
|
||||||
with_options scope: :openid do |o|
|
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(:name) { |user| user.name }
|
||||||
o.claim(:nickname) { |user| user.username }
|
o.claim(:nickname) { |user| user.username }
|
||||||
o.claim(:email) { |user| user.public_email }
|
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