Merge remote-tracking branch 'upstream/master' into gem/sm/bump-google-api-client-gem-from-0-8-6-to-0-13-6
* upstream/master: (186 commits) Resolve "Precompiled assets with digest strings are ignored in CI" Fix navigation dropdown close animation on mobile screens Add (partial) index on Labels.template Add grpc.log for Gitaly Fix gitaly-proto version in Gemfile.lock refactor issues_controller_spec `update` action Remove edit action for issues Add documentation to summarise project archiving Fix for Gitaly nil encoding issue Fix broken certificate-authority-data with kubectl >= 1.8.0 Improve performance of filtering notes in NotesController Bump gitaly-proto version to v0.39.0 Fix specs for project creation and update services Add CHANGELOG Doesn't check if path exists on disk when renaming a hashed project [ci skip] Fix archive spec descrptions Only copy old/new code when selecting left/right side of parallel diff Fix gitlab-rake gitlab:import:repos task Send extra Gitaly params for `send_git_archive` if needed Update GitLab Pages to v0.6.0 ...
This commit is contained in:
commit
9e42df3cd1
415 changed files with 10462 additions and 5846 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -63,4 +63,5 @@ eslint-report.html
|
|||
/.gitlab_workhorse_secret
|
||||
/webpack-report/
|
||||
/locale/**/LC_MESSAGES
|
||||
/locale/**/*.time_stamp
|
||||
/.rspec
|
||||
|
|
|
@ -404,6 +404,7 @@ docs lint:
|
|||
before_script: []
|
||||
script:
|
||||
- scripts/lint-doc.sh
|
||||
- scripts/lint-changelog-yaml
|
||||
- mv doc/ /nanoc/content/
|
||||
- cd /nanoc
|
||||
# Build HTML from Markdown
|
||||
|
|
|
@ -85,6 +85,8 @@ entry.
|
|||
- [FIXED] Fixed merge request changes bar jumping.
|
||||
- [FIXED] Improve migrations using triggers.
|
||||
- [FIXED] Fix ConvDev Index nav item and Monitoring submenu regression.
|
||||
- [FIXED] disabling notifications globally now properly turns off group/project added
|
||||
emails !13325
|
||||
- [DEPRECATED] Deprecate custom SSH client configuration for the git user. !13930
|
||||
- [CHANGED] allow all users to delete their account. !13636 (Jacopo Beschi @jacopo-beschi)
|
||||
- [CHANGED] Use full path of project's avatar in webhooks. !13649 (Vitaliy @blackst0ne Klachkov)
|
||||
|
@ -193,6 +195,13 @@ entry.
|
|||
- Added type to CHANGELOG entries. (Jacopo Beschi @jacopo-beschi)
|
||||
- [BUGIFX] Improves subgroup creation permissions. !13418
|
||||
|
||||
## 9.5.6 (2017-09-29)
|
||||
|
||||
- [FIXED] Fix MR ready to merge buttons/controls at mobile breakpoint. !14242
|
||||
- [FIXED] Fix errors thrown in merge request widget with external CI service/integration.
|
||||
- [FIXED] Update x/x discussions resolved checkmark icon to be green when all discussions resolved.
|
||||
- [FIXED] Fix 500 error on merged merge requests when GitLab is restored from a backup.
|
||||
|
||||
## 9.5.5 (2017-09-18)
|
||||
|
||||
- [SECURITY] Upgrade mail and nokogiri gems due to security issues. !13662 (Markus Koller)
|
||||
|
|
|
@ -49,7 +49,7 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._
|
|||
Thank you for your interest in contributing to GitLab. This guide details how
|
||||
to contribute to GitLab in a way that is efficient for everyone.
|
||||
|
||||
Looking for something to work on? Look for the label [Accepting Merge Requests](#i-want-to-contribute).
|
||||
Looking for something to work on? Look for issues with the label [Accepting Merge Requests](#i-want-to-contribute).
|
||||
|
||||
GitLab comes into two flavors, GitLab Community Edition (CE) our free and open
|
||||
source edition, and GitLab Enterprise Edition (EE) which is our commercial
|
||||
|
@ -101,7 +101,7 @@ the remaining issues on the GitHub issue tracker.
|
|||
## I want to contribute!
|
||||
|
||||
If you want to contribute to GitLab, but are not sure where to start,
|
||||
look for [issues with the label `Accepting Merge Requests` and weight < 5][accepting-mrs-weight].
|
||||
look for [issues with the label `Accepting Merge Requests` and small weight][accepting-mrs-weight].
|
||||
These issues will be of reasonable size and challenge, for anyone to start
|
||||
contributing to GitLab.
|
||||
|
||||
|
@ -209,8 +209,7 @@ We add the ~"Accepting Merge Requests" label to:
|
|||
|
||||
- Low priority ~bug issues (i.e. we do not add it to the bugs that we want to
|
||||
solve in the ~"Next Patch Release")
|
||||
- Small ~"feature proposal" that do not need ~UX / ~"Product work", or for which
|
||||
the ~UX / ~"Product work" is already done
|
||||
- Small ~"feature proposal"
|
||||
- Small ~"technical debt" issues
|
||||
|
||||
After adding the ~"Accepting Merge Requests" label, we try to estimate the
|
||||
|
@ -223,6 +222,17 @@ know how difficult the issue is. Additionally:
|
|||
- We encourage people that have never contributed to any open source project to
|
||||
look for ["Accepting Merge Requests" issues with a weight of 1][firt-timers]
|
||||
|
||||
If you've decided that you would like to work on an issue, please @-mention
|
||||
the [appropriate product manager](https://about.gitlab.com/handbook/product/#who-to-talk-to-for-what)
|
||||
as soon as possible. The product manager will then pull in appropriate GitLab team
|
||||
members to further discuss scope, design, and technical considerations. This will
|
||||
ensure that that your contribution is aligned with the GitLab product and minimize
|
||||
any rework and delay in getting it merged into master.
|
||||
|
||||
GitLab team members who apply the ~"Accepting Merge Requests" label to an issue
|
||||
should update the issue description with a responsible product manager, inviting
|
||||
any potential community contributor to @-mention per above.
|
||||
|
||||
[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=Accepting+Merge+Requests&scope=all&sort=weight_asc&state=opened
|
||||
[firt-timers]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=Accepting+Merge+Requests&scope=all&sort=upvotes_desc&state=opened&weight=1
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.40.0
|
||||
0.43.0
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.5.1
|
||||
0.6.0
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
5.9.2
|
||||
5.9.3
|
||||
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -398,7 +398,7 @@ group :ed25519 do
|
|||
end
|
||||
|
||||
# Gitaly GRPC client
|
||||
gem 'gitaly-proto', '~> 0.33.0', require: 'gitaly'
|
||||
gem 'gitaly-proto', '~> 0.39.0', require: 'gitaly'
|
||||
|
||||
gem 'toml-rb', '~> 0.3.15', require: false
|
||||
|
||||
|
|
|
@ -273,7 +273,7 @@ GEM
|
|||
po_to_json (>= 1.0.0)
|
||||
rails (>= 3.2.0)
|
||||
gherkin-ruby (0.3.2)
|
||||
gitaly-proto (0.33.0)
|
||||
gitaly-proto (0.39.0)
|
||||
google-protobuf (~> 3.1)
|
||||
grpc (~> 1.0)
|
||||
github-linguist (4.7.6)
|
||||
|
@ -1027,7 +1027,7 @@ DEPENDENCIES
|
|||
gettext (~> 3.2.2)
|
||||
gettext_i18n_rails (~> 1.8.0)
|
||||
gettext_i18n_rails_js (~> 1.2.0)
|
||||
gitaly-proto (~> 0.33.0)
|
||||
gitaly-proto (~> 0.39.0)
|
||||
github-linguist (~> 4.7.0)
|
||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||
gitlab-markup (~> 1.6.2)
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"iconCount":134,"icons":["abuse","account","admin","angle-double-left","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-right","assignee","bold","book","branch","calendar","cancel","chevron-down","chevron-left","chevron-right","chevron-up","clock","code","comment-dots","comment-next","comment","comments","commit","credit-card","disk","doc_code","doc_image","doc_text","download","duplicate","earth","eye-slash","eye","file-additions","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","merge-request-close-m","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","star-o","star","stop","talic","task-done","template","thump-down","thump-up","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]}
|
||||
{"iconCount":135,"spriteSize":58718,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-right","assignee","bold","book","branch","calendar","cancel","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","comment-dots","comment-next","comment","comments","commit","credit-card","disk","doc_code","doc_image","doc_text","download","duplicate","earth","eye-slash","eye","file-additions","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","star-o","star","stop","talic","task-done","template","thump-down","thump-up","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]}
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
File diff suppressed because it is too large
Load diff
|
@ -40,10 +40,10 @@ export default () => {
|
|||
class="text-center"
|
||||
v-if="error">
|
||||
<span v-if="loadError">
|
||||
An error occured whilst loading the file. Please try again later.
|
||||
An error occurred whilst loading the file. Please try again later.
|
||||
</span>
|
||||
<span v-else>
|
||||
An error occured whilst parsing the file.
|
||||
An error occurred whilst parsing the file.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -48,10 +48,10 @@ export default () => {
|
|||
class="text-center"
|
||||
v-if="error">
|
||||
<span v-if="loadError">
|
||||
An error occured whilst loading the file. Please try again later.
|
||||
An error occurred whilst loading the file. Please try again later.
|
||||
</span>
|
||||
<span v-else>
|
||||
An error occured whilst decoding the file.
|
||||
An error occurred whilst decoding the file.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -68,7 +68,7 @@ export default {
|
|||
<div class="flash-container"
|
||||
v-if="error">
|
||||
<div class="flash-alert">
|
||||
An error occured. Please try again.
|
||||
An error occurred. Please try again.
|
||||
</div>
|
||||
</div>
|
||||
<label class="label-light"
|
||||
|
|
|
@ -167,7 +167,7 @@ window.Build = (function () {
|
|||
Build.prototype.getBuildTrace = function () {
|
||||
return $.ajax({
|
||||
url: `${this.pageUrl}/trace.json`,
|
||||
data: this.state,
|
||||
data: { state: this.state },
|
||||
})
|
||||
.done((log) => {
|
||||
setCiStatusFavicon(`${this.pageUrl}/status.json`);
|
||||
|
|
|
@ -298,7 +298,7 @@ class CopyAsGFM {
|
|||
const documentFragment = getSelectedFragment();
|
||||
if (!documentFragment) return;
|
||||
|
||||
const el = transformer(documentFragment.cloneNode(true));
|
||||
const el = transformer(documentFragment.cloneNode(true), e.currentTarget);
|
||||
if (!el) return;
|
||||
|
||||
e.preventDefault();
|
||||
|
@ -338,55 +338,64 @@ class CopyAsGFM {
|
|||
}
|
||||
|
||||
static transformGFMSelection(documentFragment) {
|
||||
const gfmEls = documentFragment.querySelectorAll('.md, .wiki');
|
||||
switch (gfmEls.length) {
|
||||
const gfmElements = documentFragment.querySelectorAll('.md, .wiki');
|
||||
switch (gfmElements.length) {
|
||||
case 0: {
|
||||
return documentFragment;
|
||||
}
|
||||
case 1: {
|
||||
return gfmEls[0];
|
||||
return gfmElements[0];
|
||||
}
|
||||
default: {
|
||||
const allGfmEl = document.createElement('div');
|
||||
const allGfmElement = document.createElement('div');
|
||||
|
||||
for (let i = 0; i < gfmEls.length; i += 1) {
|
||||
const lineEl = gfmEls[i];
|
||||
allGfmEl.appendChild(lineEl);
|
||||
allGfmEl.appendChild(document.createTextNode('\n\n'));
|
||||
for (let i = 0; i < gfmElements.length; i += 1) {
|
||||
const gfmElement = gfmElements[i];
|
||||
allGfmElement.appendChild(gfmElement);
|
||||
allGfmElement.appendChild(document.createTextNode('\n\n'));
|
||||
}
|
||||
|
||||
return allGfmEl;
|
||||
return allGfmElement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static transformCodeSelection(documentFragment) {
|
||||
const lineEls = documentFragment.querySelectorAll('.line');
|
||||
static transformCodeSelection(documentFragment, target) {
|
||||
let lineSelector = '.line';
|
||||
|
||||
let codeEl;
|
||||
if (lineEls.length > 1) {
|
||||
codeEl = document.createElement('pre');
|
||||
codeEl.className = 'code highlight';
|
||||
if (target) {
|
||||
const lineClass = ['left-side', 'right-side'].filter(name => target.classList.contains(name))[0];
|
||||
if (lineClass) {
|
||||
lineSelector = `.line_content.${lineClass} ${lineSelector}`;
|
||||
}
|
||||
}
|
||||
|
||||
const lang = lineEls[0].getAttribute('lang');
|
||||
const lineElements = documentFragment.querySelectorAll(lineSelector);
|
||||
|
||||
let codeElement;
|
||||
if (lineElements.length > 1) {
|
||||
codeElement = document.createElement('pre');
|
||||
codeElement.className = 'code highlight';
|
||||
|
||||
const lang = lineElements[0].getAttribute('lang');
|
||||
if (lang) {
|
||||
codeEl.setAttribute('lang', lang);
|
||||
codeElement.setAttribute('lang', lang);
|
||||
}
|
||||
} else {
|
||||
codeEl = document.createElement('code');
|
||||
codeElement = document.createElement('code');
|
||||
}
|
||||
|
||||
if (lineEls.length > 0) {
|
||||
for (let i = 0; i < lineEls.length; i += 1) {
|
||||
const lineEl = lineEls[i];
|
||||
codeEl.appendChild(lineEl);
|
||||
codeEl.appendChild(document.createTextNode('\n'));
|
||||
if (lineElements.length > 0) {
|
||||
for (let i = 0; i < lineElements.length; i += 1) {
|
||||
const lineElement = lineElements[i];
|
||||
codeElement.appendChild(lineElement);
|
||||
codeElement.appendChild(document.createTextNode('\n'));
|
||||
}
|
||||
} else {
|
||||
codeEl.appendChild(documentFragment);
|
||||
codeElement.appendChild(documentFragment);
|
||||
}
|
||||
|
||||
return codeEl;
|
||||
return codeElement;
|
||||
}
|
||||
|
||||
static nodeToGFM(node, respectWhitespaceParam = false) {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<script>
|
||||
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
|
||||
import limitWarning from './limit_warning_component.vue';
|
||||
import totalTime from './total_time_component.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
@ -8,6 +10,8 @@
|
|||
},
|
||||
components: {
|
||||
userAvatarImage,
|
||||
limitWarning,
|
||||
totalTime,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<script>
|
||||
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
|
||||
import limitWarning from './limit_warning_component.vue';
|
||||
import totalTime from './total_time_component.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
@ -8,6 +10,8 @@
|
|||
},
|
||||
components: {
|
||||
userAvatarImage,
|
||||
limitWarning,
|
||||
totalTime,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,21 +1,25 @@
|
|||
<script>
|
||||
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
|
||||
import iconCommit from '../svg/icon_commit.svg';
|
||||
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
|
||||
import iconCommit from '../svg/icon_commit.svg';
|
||||
import limitWarning from './limit_warning_component.vue';
|
||||
import totalTime from './total_time_component.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
items: Array,
|
||||
stage: Object,
|
||||
},
|
||||
components: {
|
||||
userAvatarImage,
|
||||
},
|
||||
computed: {
|
||||
iconCommit() {
|
||||
return iconCommit;
|
||||
export default {
|
||||
props: {
|
||||
items: Array,
|
||||
stage: Object,
|
||||
},
|
||||
},
|
||||
};
|
||||
components: {
|
||||
userAvatarImage,
|
||||
totalTime,
|
||||
limitWarning,
|
||||
},
|
||||
computed: {
|
||||
iconCommit() {
|
||||
return iconCommit;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<script>
|
||||
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
|
||||
import limitWarning from './limit_warning_component.vue';
|
||||
import totalTime from './total_time_component.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
@ -8,6 +10,8 @@
|
|||
},
|
||||
components: {
|
||||
userAvatarImage,
|
||||
totalTime,
|
||||
limitWarning,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<script>
|
||||
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
|
||||
import iconBranch from '../svg/icon_branch.svg';
|
||||
import limitWarning from './limit_warning_component.vue';
|
||||
import totalTime from './total_time_component.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
@ -9,6 +11,8 @@
|
|||
},
|
||||
components: {
|
||||
userAvatarImage,
|
||||
totalTime,
|
||||
limitWarning,
|
||||
},
|
||||
computed: {
|
||||
iconBranch() {
|
||||
|
|
|
@ -1,21 +1,27 @@
|
|||
<script>
|
||||
import iconBuildStatus from '../svg/icon_build_status.svg';
|
||||
import iconBranch from '../svg/icon_branch.svg';
|
||||
import iconBuildStatus from '../svg/icon_build_status.svg';
|
||||
import iconBranch from '../svg/icon_branch.svg';
|
||||
import limitWarning from './limit_warning_component.vue';
|
||||
import totalTime from './total_time_component.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
items: Array,
|
||||
stage: Object,
|
||||
},
|
||||
computed: {
|
||||
iconBuildStatus() {
|
||||
return iconBuildStatus;
|
||||
export default {
|
||||
props: {
|
||||
items: Array,
|
||||
stage: Object,
|
||||
},
|
||||
iconBranch() {
|
||||
return iconBranch;
|
||||
components: {
|
||||
totalTime,
|
||||
limitWarning,
|
||||
},
|
||||
},
|
||||
};
|
||||
computed: {
|
||||
iconBuildStatus() {
|
||||
return iconBuildStatus;
|
||||
},
|
||||
iconBranch() {
|
||||
return iconBranch;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
|
|
|
@ -3,14 +3,12 @@
|
|||
import Vue from 'vue';
|
||||
import Cookies from 'js-cookie';
|
||||
import Translate from '../vue_shared/translate';
|
||||
import limitWarningComponent from './components/limit_warning_component.vue';
|
||||
import stageCodeComponent from './components/stage_code_component.vue';
|
||||
import stagePlanComponent from './components/stage_plan_component.vue';
|
||||
import stageComponent from './components/stage_component.vue';
|
||||
import stageReviewComponent from './components/stage_review_component.vue';
|
||||
import stageStagingComponent from './components/stage_staging_component.vue';
|
||||
import stageTestComponent from './components/stage_test_component.vue';
|
||||
import totalTime from './components/total_time_component.vue';
|
||||
import CycleAnalyticsService from './cycle_analytics_service';
|
||||
import CycleAnalyticsStore from './cycle_analytics_store';
|
||||
|
||||
|
@ -133,8 +131,4 @@ $(() => {
|
|||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Register global components
|
||||
Vue.component('limit-warning', limitWarningComponent);
|
||||
Vue.component('total-time', totalTime);
|
||||
});
|
||||
|
|
|
@ -24,7 +24,8 @@ class Diff {
|
|||
if (!isBound) {
|
||||
$(document)
|
||||
.on('click', '.js-unfold', this.handleClickUnfold.bind(this))
|
||||
.on('click', '.diff-line-num a', this.handleClickLineNum.bind(this));
|
||||
.on('click', '.diff-line-num a', this.handleClickLineNum.bind(this))
|
||||
.on('mousedown', 'td.line_content.parallel', this.handleParallelLineDown.bind(this));
|
||||
isBound = true;
|
||||
}
|
||||
|
||||
|
@ -100,6 +101,18 @@ class Diff {
|
|||
this.highlightSelectedLine();
|
||||
}
|
||||
|
||||
handleParallelLineDown(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))[0];
|
||||
if (lineClass) {
|
||||
table.addClass(`${lineClass}-selected`);
|
||||
}
|
||||
}
|
||||
|
||||
diffViewType() {
|
||||
return $('.inline-parallel-buttons a.active').data('view-type');
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
/* global NotificationsDropdown */
|
||||
/* global GroupAvatar */
|
||||
/* global LineHighlighter */
|
||||
/* global ProjectFork */
|
||||
/* global BuildArtifacts */
|
||||
/* global GroupsSelect */
|
||||
/* global Search */
|
||||
|
@ -476,7 +475,9 @@ import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils';
|
|||
shortcut_handler = true;
|
||||
break;
|
||||
case 'projects:forks:new':
|
||||
new ProjectFork();
|
||||
import(/* webpackChunkName: 'project_fork' */ './project_fork')
|
||||
.then(fork => fork.default())
|
||||
.catch(() => {});
|
||||
break;
|
||||
case 'projects:artifacts:browse':
|
||||
new ShortcutsNavigation();
|
||||
|
|
|
@ -163,7 +163,7 @@ export default {
|
|||
|
||||
this.service.postAction(endpoint)
|
||||
.then(() => this.fetchEnvironments())
|
||||
.catch(() => new Flash('An error occured while making the request.'));
|
||||
.catch(() => new Flash('An error occurred while making the request.'));
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -158,7 +158,7 @@ export default {
|
|||
|
||||
this.service.postAction(endpoint)
|
||||
.then(() => this.fetchEnvironments())
|
||||
.catch(() => new Flash('An error occured while making the request.'));
|
||||
.catch(() => new Flash('An error occurred while making the request.'));
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -14,7 +14,7 @@ class DropdownEmoji extends gl.FilteredSearchDropdown {
|
|||
loadingTemplate: this.loadingTemplate,
|
||||
onError() {
|
||||
/* eslint-disable no-new */
|
||||
new Flash('An error occured fetching the dropdown data.');
|
||||
new Flash('An error occurred fetching the dropdown data.');
|
||||
/* eslint-enable no-new */
|
||||
},
|
||||
},
|
||||
|
|
|
@ -17,7 +17,7 @@ class DropdownNonUser extends gl.FilteredSearchDropdown {
|
|||
preprocessing,
|
||||
onError() {
|
||||
/* eslint-disable no-new */
|
||||
new Flash('An error occured fetching the dropdown data.');
|
||||
new Flash('An error occurred fetching the dropdown data.');
|
||||
/* eslint-enable no-new */
|
||||
},
|
||||
},
|
||||
|
|
|
@ -26,7 +26,7 @@ class DropdownUser extends gl.FilteredSearchDropdown {
|
|||
},
|
||||
onError() {
|
||||
/* eslint-disable no-new */
|
||||
new Flash('An error occured fetching the dropdown data.');
|
||||
new Flash('An error occurred fetching the dropdown data.');
|
||||
/* eslint-enable no-new */
|
||||
},
|
||||
},
|
||||
|
|
|
@ -36,7 +36,7 @@ class FilteredSearchManager {
|
|||
.catch((error) => {
|
||||
if (error.name === 'RecentSearchesServiceError') return undefined;
|
||||
// eslint-disable-next-line no-new
|
||||
new window.Flash('An error occured while parsing recent searches');
|
||||
new window.Flash('An error occurred while parsing recent searches');
|
||||
// Gracefully fail to empty array
|
||||
return [];
|
||||
})
|
||||
|
|
|
@ -67,10 +67,13 @@ const PARTICIPANTS_ROW_COUNT = 7;
|
|||
originalText = $(this).data("original-text");
|
||||
if (currentText === originalText) {
|
||||
$(this).text(lessText);
|
||||
|
||||
if (gl.lazyLoader) gl.lazyLoader.loadCheck();
|
||||
} else {
|
||||
$(this).text(originalText);
|
||||
}
|
||||
return $(".js-participants-hidden").toggle();
|
||||
|
||||
$(".js-participants-hidden").toggle();
|
||||
};
|
||||
|
||||
return IssuableContext;
|
||||
|
|
|
@ -71,6 +71,7 @@ export const handleLocationHash = () => {
|
|||
// This is required to handle non-unicode characters in hash
|
||||
hash = decodeURIComponent(hash);
|
||||
|
||||
const target = document.getElementById(hash) || document.getElementById(`user-content-${hash}`);
|
||||
const fixedTabs = document.querySelector('.js-tabs-affix');
|
||||
const fixedDiffStats = document.querySelector('.js-diff-files-changed.is-stuck');
|
||||
const fixedNav = document.querySelector('.navbar-gitlab');
|
||||
|
@ -78,25 +79,19 @@ export const handleLocationHash = () => {
|
|||
let adjustment = 0;
|
||||
if (fixedNav) adjustment -= fixedNav.offsetHeight;
|
||||
|
||||
// scroll to user-generated markdown anchor if we cannot find a match
|
||||
if (document.getElementById(hash) === null) {
|
||||
const target = document.getElementById(`user-content-${hash}`);
|
||||
if (target && target.scrollIntoView) {
|
||||
target.scrollIntoView(true);
|
||||
window.scrollBy(0, adjustment);
|
||||
}
|
||||
} else {
|
||||
// only adjust for fixedTabs when not targeting user-generated content
|
||||
if (fixedTabs) {
|
||||
adjustment -= fixedTabs.offsetHeight;
|
||||
}
|
||||
|
||||
if (fixedDiffStats) {
|
||||
adjustment -= fixedDiffStats.offsetHeight;
|
||||
}
|
||||
|
||||
window.scrollBy(0, adjustment);
|
||||
if (target && target.scrollIntoView) {
|
||||
target.scrollIntoView(true);
|
||||
}
|
||||
|
||||
if (fixedTabs) {
|
||||
adjustment -= fixedTabs.offsetHeight;
|
||||
}
|
||||
|
||||
if (fixedDiffStats) {
|
||||
adjustment -= fixedDiffStats.offsetHeight;
|
||||
}
|
||||
|
||||
window.scrollBy(0, adjustment);
|
||||
};
|
||||
|
||||
// Check if element scrolled into viewport from above or below
|
||||
|
|
|
@ -55,7 +55,7 @@ window.dateFormat = dateFormat;
|
|||
if (!timeagoInstance) {
|
||||
const localeRemaining = function(number, index) {
|
||||
return [
|
||||
[s__('Timeago|less than a minute ago'), s__('Timeago|a while')],
|
||||
[s__('Timeago|less than a minute ago'), s__('Timeago|in a while')],
|
||||
[s__('Timeago|less than a minute ago'), s__('Timeago|%s seconds remaining')],
|
||||
[s__('Timeago|about a minute ago'), s__('Timeago|1 minute remaining')],
|
||||
[s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')],
|
||||
|
@ -73,7 +73,7 @@ window.dateFormat = dateFormat;
|
|||
};
|
||||
locale = function(number, index) {
|
||||
return [
|
||||
[s__('Timeago|less than a minute ago'), s__('Timeago|a while')],
|
||||
[s__('Timeago|less than a minute ago'), s__('Timeago|in a while')],
|
||||
[s__('Timeago|less than a minute ago'), s__('Timeago|in %s seconds')],
|
||||
[s__('Timeago|about a minute ago'), s__('Timeago|in 1 minute')],
|
||||
[s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')],
|
||||
|
|
|
@ -124,7 +124,6 @@ import './preview_markdown';
|
|||
import './project';
|
||||
import './project_avatar';
|
||||
import './project_find_file';
|
||||
import './project_fork';
|
||||
import './project_import';
|
||||
import './project_label_subscription';
|
||||
import './project_new';
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
},
|
||||
deleteHandler() {
|
||||
// eslint-disable-next-line no-alert
|
||||
if (confirm('Are you sure you want to delete this list?')) {
|
||||
if (confirm('Are you sure you want to delete this comment?')) {
|
||||
this.isDeleting = true;
|
||||
|
||||
this.deleteNote(this.note)
|
||||
|
|
|
@ -28,8 +28,7 @@
|
|||
popoverOptions() {
|
||||
return {
|
||||
html: true,
|
||||
delay: { hide: 600 },
|
||||
trigger: 'hover',
|
||||
trigger: 'focus',
|
||||
placement: 'top',
|
||||
title: '<div class="autodevops-title">This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b></div>',
|
||||
content: `<a class="autodevops-link" href="${this.autoDevopsHelpPath}" target="_blank" rel="noopener noreferrer nofollow">Learn more about Auto DevOps</a>`,
|
||||
|
@ -75,6 +74,7 @@
|
|||
</span>
|
||||
<a
|
||||
v-if="pipeline.flags.auto_devops"
|
||||
tabindex="0"
|
||||
class="js-pipeline-url-autodevops label label-info autodevops-badge"
|
||||
v-popover="popoverOptions"
|
||||
role="button">
|
||||
|
|
|
@ -97,7 +97,7 @@ export default {
|
|||
postAction(endpoint) {
|
||||
this.service.postAction(endpoint)
|
||||
.then(() => eventHub.$emit('refreshPipelines'))
|
||||
.catch(() => new Flash('An error occured while making the request.'));
|
||||
.catch(() => new Flash('An error occurred while making the request.'));
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,13 +1,8 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, max-len */
|
||||
(function() {
|
||||
this.ProjectFork = (function() {
|
||||
function ProjectFork() {
|
||||
$('.fork-thumbnail a').on('click', function() {
|
||||
$('.fork-namespaces').hide();
|
||||
return $('.save-project-loader').show();
|
||||
});
|
||||
}
|
||||
export default () => {
|
||||
$('.fork-thumbnail a').on('click', function forkThumbnailClicked() {
|
||||
if ($(this).hasClass('disabled')) return false;
|
||||
|
||||
return ProjectFork;
|
||||
})();
|
||||
}).call(window);
|
||||
$('.fork-namespaces').hide();
|
||||
return $('.save-project-loader').show();
|
||||
});
|
||||
};
|
||||
|
|
|
@ -19,7 +19,7 @@ export default class ProjectsService {
|
|||
|
||||
getSearchedProjects(searchQuery) {
|
||||
return this.projectsPath.get({
|
||||
simple: false,
|
||||
simple: true,
|
||||
per_page: 20,
|
||||
membership: !!gon.current_user_id,
|
||||
order_by: 'last_activity_at',
|
||||
|
|
|
@ -37,14 +37,14 @@ export default {
|
|||
content: f.newContent,
|
||||
}));
|
||||
const payload = {
|
||||
branch: Store.targetBranch,
|
||||
branch: Store.currentBranch,
|
||||
commit_message: commitMessage,
|
||||
actions,
|
||||
};
|
||||
Store.submitCommitsLoading = true;
|
||||
Service.commitFiles(payload)
|
||||
.then(this.resetCommitState)
|
||||
.catch(() => Flash('An error occured while committing your changes'));
|
||||
.catch(() => Flash('An error occurred while committing your changes'));
|
||||
},
|
||||
|
||||
resetCommitState() {
|
||||
|
@ -105,7 +105,7 @@ export default {
|
|||
</label>
|
||||
<div class="col-md-6">
|
||||
<span class="help-block">
|
||||
{{targetBranch}}
|
||||
{{currentBranch}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -26,16 +26,6 @@ export default {
|
|||
this.editMode = !this.editMode;
|
||||
Store.toggleBlobView();
|
||||
},
|
||||
toggleProjectRefsForm() {
|
||||
$('.project-refs-form').toggleClass('disabled', this.editMode);
|
||||
$('.js-tree-ref-target-holder').toggle(this.editMode);
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
editMode() {
|
||||
this.toggleProjectRefsForm();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -95,7 +95,7 @@ export default RepoFile;
|
|||
</div>
|
||||
</td>
|
||||
|
||||
<td class="hidden-xs">
|
||||
<td class="hidden-xs text-right">
|
||||
<span
|
||||
class="commit-update"
|
||||
:title="tooltipTitle(file.lastCommitUpdate)">
|
||||
|
|
|
@ -49,7 +49,7 @@ export default {
|
|||
v-else
|
||||
class="vertical-center render-error">
|
||||
<p class="text-center">
|
||||
The source could not be displayed because a rendering error occured. You can <a :href="activeFile.raw_path">download</a> it instead.
|
||||
The source could not be displayed because a rendering error occurred. You can <a :href="activeFile.raw_path">download</a> it instead.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -37,17 +37,24 @@ export default {
|
|||
let file = clickedFile;
|
||||
if (file.loading) return;
|
||||
file.loading = true;
|
||||
|
||||
if (file.type === 'tree' && file.opened) {
|
||||
file = Store.removeChildFilesOfTree(file);
|
||||
file.loading = false;
|
||||
} else {
|
||||
Service.url = file.url;
|
||||
Helper.getContent(file)
|
||||
.then(() => {
|
||||
file.loading = false;
|
||||
Helper.scrollTabsRight();
|
||||
})
|
||||
.catch(Helper.loadingError);
|
||||
const openFile = Helper.getFileFromPath(file.url);
|
||||
if (openFile) {
|
||||
file.loading = false;
|
||||
Store.setActiveFiles(openFile);
|
||||
} else {
|
||||
Service.url = file.url;
|
||||
Helper.getContent(file)
|
||||
.then(() => {
|
||||
file.loading = false;
|
||||
Helper.scrollTabsRight();
|
||||
})
|
||||
.catch(Helper.loadingError);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -68,7 +75,7 @@ export default {
|
|||
<tr>
|
||||
<th class="name">Name</th>
|
||||
<th class="hidden-sm hidden-xs last-commit">Last Commit</th>
|
||||
<th class="hidden-xs last-update">Last Update</th>
|
||||
<th class="hidden-xs last-update text-right">Last Update</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
|
@ -58,13 +58,13 @@ const RepoHelper = {
|
|||
return langs.find(lang => lang.extensions && lang.extensions.indexOf(`.${ext}`) > -1);
|
||||
},
|
||||
|
||||
setDirectoryOpen(tree) {
|
||||
setDirectoryOpen(tree, title) {
|
||||
const file = tree;
|
||||
if (!file) return undefined;
|
||||
|
||||
file.opened = true;
|
||||
file.icon = 'fa-folder-open';
|
||||
RepoHelper.updateHistoryEntry(file.url, file.name);
|
||||
RepoHelper.updateHistoryEntry(file.url, title);
|
||||
return file;
|
||||
},
|
||||
|
||||
|
@ -135,6 +135,8 @@ const RepoHelper = {
|
|||
return Service.getContent()
|
||||
.then((response) => {
|
||||
const data = response.data;
|
||||
if (response.headers && response.headers['page-title']) data.pageTitle = response.headers['page-title'];
|
||||
|
||||
Store.isTree = RepoHelper.isTree(data);
|
||||
if (!Store.isTree) {
|
||||
if (!file) file = data;
|
||||
|
@ -168,7 +170,7 @@ const RepoHelper = {
|
|||
} else {
|
||||
// it's a tree
|
||||
if (!file) Store.isRoot = RepoHelper.isRoot(Service.url);
|
||||
file = RepoHelper.setDirectoryOpen(file);
|
||||
file = RepoHelper.setDirectoryOpen(file, data.pageTitle || data.name);
|
||||
const newDirectory = RepoHelper.dataToListOfFiles(data);
|
||||
Store.addFilesToDirectory(file, Store.files, newDirectory);
|
||||
Store.prevURL = Service.blobURLtoParentTree(Service.url);
|
||||
|
@ -255,7 +257,7 @@ const RepoHelper = {
|
|||
history.pushState({ key: RepoHelper.key }, '', url);
|
||||
|
||||
if (title) {
|
||||
document.title = `${title} · GitLab`;
|
||||
document.title = title;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -263,6 +265,10 @@ const RepoHelper = {
|
|||
return Store.openedFiles.find(openedFile => Store.activeFile.url === openedFile.url);
|
||||
},
|
||||
|
||||
getFileFromPath(path) {
|
||||
return Store.openedFiles.find(file => file.url === path);
|
||||
},
|
||||
|
||||
loadingError() {
|
||||
Flash('Unable to load this content at this time.');
|
||||
},
|
||||
|
|
|
@ -11,10 +11,6 @@ function initDropdowns() {
|
|||
}
|
||||
|
||||
function addEventsForNonVueEls() {
|
||||
$(document).on('change', '.dropdown', () => {
|
||||
Store.targetBranch = $('.project-refs-target-form input[name="ref"]').val();
|
||||
});
|
||||
|
||||
window.onbeforeunload = function confirmUnload(e) {
|
||||
const hasChanged = Store.openedFiles
|
||||
.some(file => file.changed);
|
||||
|
|
|
@ -32,7 +32,6 @@ const RepoStore = {
|
|||
isCommitable: false,
|
||||
binary: false,
|
||||
currentBranch: '',
|
||||
targetBranch: 'new-branch',
|
||||
commitMessage: '',
|
||||
binaryTypes: {
|
||||
png: false,
|
||||
|
@ -84,7 +83,7 @@ const RepoStore = {
|
|||
}).catch(Helper.loadingError);
|
||||
}
|
||||
|
||||
if (!file.loading) Helper.updateHistoryEntry(file.url, file.name);
|
||||
if (!file.loading) Helper.updateHistoryEntry(file.url, file.pageTitle || file.name);
|
||||
RepoStore.binary = file.binary;
|
||||
},
|
||||
|
||||
|
|
|
@ -29,30 +29,32 @@ import Cookies from 'js-cookie';
|
|||
$('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading);
|
||||
$('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded);
|
||||
|
||||
$document.on('click', '.js-sidebar-toggle', function(e, triggered) {
|
||||
var $allGutterToggleIcons, $this, $thisIcon;
|
||||
e.preventDefault();
|
||||
$this = $(this);
|
||||
$thisIcon = $this.find('i');
|
||||
$allGutterToggleIcons = $('.js-sidebar-toggle i');
|
||||
if ($thisIcon.hasClass('fa-angle-double-right')) {
|
||||
$allGutterToggleIcons.removeClass('fa-angle-double-right').addClass('fa-angle-double-left');
|
||||
$('aside.right-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
|
||||
$('.page-with-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
|
||||
} else {
|
||||
$allGutterToggleIcons.removeClass('fa-angle-double-left').addClass('fa-angle-double-right');
|
||||
$('aside.right-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
|
||||
$('.page-with-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
|
||||
|
||||
if (gl.lazyLoader) gl.lazyLoader.loadCheck();
|
||||
}
|
||||
if (!triggered) {
|
||||
return Cookies.set("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'));
|
||||
}
|
||||
});
|
||||
$document.on('click', '.js-sidebar-toggle', this.sidebarToggleClicked);
|
||||
return $(document).off('click', '.js-issuable-todo').on('click', '.js-issuable-todo', this.toggleTodo);
|
||||
};
|
||||
|
||||
Sidebar.prototype.sidebarToggleClicked = function (e, triggered) {
|
||||
var $allGutterToggleIcons, $this, $thisIcon;
|
||||
e.preventDefault();
|
||||
$this = $(this);
|
||||
$thisIcon = $this.find('i');
|
||||
$allGutterToggleIcons = $('.js-sidebar-toggle i');
|
||||
if ($thisIcon.hasClass('fa-angle-double-right')) {
|
||||
$allGutterToggleIcons.removeClass('fa-angle-double-right').addClass('fa-angle-double-left');
|
||||
$('aside.right-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
|
||||
$('.page-with-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
|
||||
} else {
|
||||
$allGutterToggleIcons.removeClass('fa-angle-double-left').addClass('fa-angle-double-right');
|
||||
$('aside.right-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
|
||||
$('.page-with-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
|
||||
|
||||
if (gl.lazyLoader) gl.lazyLoader.loadCheck();
|
||||
}
|
||||
if (!triggered) {
|
||||
Cookies.set("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'));
|
||||
}
|
||||
};
|
||||
|
||||
Sidebar.prototype.toggleTodo = function(e) {
|
||||
var $btnText, $this, $todoLoading, ajaxType, url;
|
||||
$this = $(e.currentTarget);
|
||||
|
|
|
@ -287,6 +287,7 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
|
|||
|
||||
onClearInputClick(e) {
|
||||
e.preventDefault();
|
||||
this.wrap.toggleClass('has-value', !!e.target.value);
|
||||
return this.searchInput.val('').focus();
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ class SidebarMoveIssue {
|
|||
data: (searchTerm, callback) => {
|
||||
this.mediator.fetchAutocompleteProjects(searchTerm)
|
||||
.then(callback)
|
||||
.catch(() => new Flash('An error occured while fetching projects autocomplete.'));
|
||||
.catch(() => new Flash('An error occurred while fetching projects autocomplete.'));
|
||||
},
|
||||
renderRow: project => `
|
||||
<li>
|
||||
|
@ -73,7 +73,7 @@ class SidebarMoveIssue {
|
|||
|
||||
this.mediator.moveIssue()
|
||||
.catch(() => {
|
||||
Flash('An error occured while moving the issue.');
|
||||
Flash('An error occurred while moving the issue.');
|
||||
this.$confirmButton
|
||||
.enable()
|
||||
.removeClass('is-loading');
|
||||
|
|
|
@ -41,7 +41,7 @@ export default class SidebarMediator {
|
|||
this.store.setAssigneeData(data);
|
||||
this.store.setTimeTrackingData(data);
|
||||
})
|
||||
.catch(() => new Flash('Error occured when fetching sidebar data'));
|
||||
.catch(() => new Flash('Error occurred when fetching sidebar data'));
|
||||
}
|
||||
|
||||
fetchAutocompleteProjects(searchTerm) {
|
||||
|
|
|
@ -27,7 +27,7 @@ export default {
|
|||
<button
|
||||
v-if="showDisabledButton"
|
||||
type="button"
|
||||
class="btn btn-success btn-sm"
|
||||
class="js-disabled-merge-button btn btn-success btn-sm"
|
||||
disabled="true">
|
||||
Merge
|
||||
</button>
|
||||
|
|
|
@ -16,9 +16,9 @@ export default {
|
|||
<div class="media-body">
|
||||
<mr-widget-author-and-time
|
||||
actionText="Closed by"
|
||||
:author="mr.closedBy"
|
||||
:dateTitle="mr.updatedAt"
|
||||
:dateReadable="mr.closedAt"
|
||||
:author="mr.closedEvent.author"
|
||||
:dateTitle="mr.closedEvent.updatedAt"
|
||||
:dateReadable="mr.closedEvent.formattedUpdatedAt"
|
||||
/>
|
||||
<section class="mr-info-list">
|
||||
<p>
|
||||
|
|
|
@ -10,27 +10,37 @@ export default {
|
|||
},
|
||||
template: `
|
||||
<div class="mr-widget-body media">
|
||||
<status-icon status="failed" showDisabledButton />
|
||||
<status-icon
|
||||
status="failed"
|
||||
showDisabledButton />
|
||||
<div class="media-body space-children">
|
||||
<span class="bold">
|
||||
There are merge conflicts<span v-if="!mr.canMerge">.</span>
|
||||
<span v-if="!mr.canMerge">
|
||||
Resolve these conflicts or ask someone with write access to this repository to merge it locally
|
||||
</span>
|
||||
<span
|
||||
v-if="mr.shouldBeRebased"
|
||||
class="bold">
|
||||
Fast-forward merge is not possible.
|
||||
To merge this request, first rebase locally.
|
||||
</span>
|
||||
<a
|
||||
v-if="mr.canMerge && mr.conflictResolutionPath"
|
||||
:href="mr.conflictResolutionPath"
|
||||
class="btn btn-default btn-xs js-resolve-conflicts-button">
|
||||
Resolve conflicts
|
||||
</a>
|
||||
<a
|
||||
v-if="mr.canMerge"
|
||||
class="btn btn-default btn-xs js-merge-locally-button"
|
||||
data-toggle="modal"
|
||||
href="#modal_merge_info">
|
||||
Merge locally
|
||||
</a>
|
||||
<template v-else>
|
||||
<span class="bold">
|
||||
There are merge conflicts<span v-if="!mr.canMerge">.</span>
|
||||
<span v-if="!mr.canMerge">
|
||||
Resolve these conflicts or ask someone with write access to this repository to merge it locally
|
||||
</span>
|
||||
</span>
|
||||
<a
|
||||
v-if="mr.canMerge && mr.conflictResolutionPath"
|
||||
:href="mr.conflictResolutionPath"
|
||||
class="js-resolve-conflicts-button btn btn-default btn-xs">
|
||||
Resolve conflicts
|
||||
</a>
|
||||
<a
|
||||
v-if="mr.canMerge"
|
||||
class="js-merge-locally-button btn btn-default btn-xs"
|
||||
data-toggle="modal"
|
||||
href="#modal_merge_info">
|
||||
Merge locally
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
|
|
|
@ -69,9 +69,9 @@ export default {
|
|||
<div class="space-children">
|
||||
<mr-widget-author-and-time
|
||||
actionText="Merged by"
|
||||
:author="mr.mergedBy"
|
||||
:dateTitle="mr.updatedAt"
|
||||
:dateReadable="mr.mergedAt" />
|
||||
:author="mr.mergedEvent.author"
|
||||
:date-title="mr.mergedEvent.updatedAt"
|
||||
:date-readable="mr.mergedEvent.formattedUpdatedAt" />
|
||||
<a
|
||||
v-if="mr.canRevertInCurrentMR"
|
||||
v-tooltip
|
||||
|
|
|
@ -284,10 +284,16 @@ export default {
|
|||
:mr="mr"
|
||||
:is-merge-button-disabled="isMergeButtonDisabled" />
|
||||
|
||||
<span
|
||||
v-if="mr.ffOnlyEnabled"
|
||||
class="js-fast-forward-message">
|
||||
Fast-forward merge without a merge commit
|
||||
</span>
|
||||
<button
|
||||
v-else
|
||||
@click="toggleCommitMessageEditor"
|
||||
:disabled="isMergeButtonDisabled"
|
||||
class="btn btn-default btn-xs"
|
||||
class="js-modify-commit-message-button btn btn-default btn-xs"
|
||||
type="button">
|
||||
Modify commit message
|
||||
</button>
|
||||
|
|
|
@ -37,10 +37,8 @@ export default class MergeRequestStore {
|
|||
}
|
||||
|
||||
this.updatedAt = data.updated_at;
|
||||
this.mergedAt = MergeRequestStore.getEventDate(data.merge_event);
|
||||
this.closedAt = MergeRequestStore.getEventDate(data.closed_event);
|
||||
this.mergedBy = MergeRequestStore.getAuthorObject(data.merge_event);
|
||||
this.closedBy = MergeRequestStore.getAuthorObject(data.closed_event);
|
||||
this.mergedEvent = MergeRequestStore.getEventObject(data.merge_event);
|
||||
this.closedEvent = MergeRequestStore.getEventObject(data.closed_event);
|
||||
this.setToMWPSBy = MergeRequestStore.getAuthorObject({ author: data.merge_user || {} });
|
||||
this.mergeUserId = data.merge_user_id;
|
||||
this.currentUserId = gon.current_user_id;
|
||||
|
@ -57,6 +55,8 @@ export default class MergeRequestStore {
|
|||
this.onlyAllowMergeIfPipelineSucceeds = data.only_allow_merge_if_pipeline_succeeds || false;
|
||||
this.mergeWhenPipelineSucceeds = data.merge_when_pipeline_succeeds || false;
|
||||
this.mergePath = data.merge_path;
|
||||
this.ffOnlyEnabled = data.ff_only_enabled;
|
||||
this.shouldBeRebased = !!data.should_be_rebased;
|
||||
this.statusPath = data.status_path;
|
||||
this.emailPatchesPath = data.email_patches_path;
|
||||
this.plainDiffPath = data.plain_diff_path;
|
||||
|
@ -118,6 +118,14 @@ export default class MergeRequestStore {
|
|||
}
|
||||
}
|
||||
|
||||
static getEventObject(event) {
|
||||
return {
|
||||
author: MergeRequestStore.getAuthorObject(event),
|
||||
updatedAt: gl.utils.formatDate(MergeRequestStore.getEventUpdatedAtDate(event)),
|
||||
formattedUpdatedAt: MergeRequestStore.getEventDate(event),
|
||||
};
|
||||
}
|
||||
|
||||
static getAuthorObject(event) {
|
||||
if (!event) {
|
||||
return {};
|
||||
|
@ -131,6 +139,14 @@ export default class MergeRequestStore {
|
|||
};
|
||||
}
|
||||
|
||||
static getEventUpdatedAtDate(event) {
|
||||
if (!event) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return event.updated_at;
|
||||
}
|
||||
|
||||
static getEventDate(event) {
|
||||
const timeagoInstance = new Timeago();
|
||||
|
||||
|
@ -138,7 +154,7 @@ export default class MergeRequestStore {
|
|||
return '';
|
||||
}
|
||||
|
||||
return timeagoInstance.format(event.updated_at);
|
||||
return timeagoInstance.format(MergeRequestStore.getEventUpdatedAtDate(event));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -873,6 +873,13 @@
|
|||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
header.navbar-gitlab-new .header-content .dropdown {
|
||||
.dropdown-menu {
|
||||
left: 0;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include new-style-dropdown('.breadcrumbs-list .dropdown ');
|
||||
|
|
|
@ -126,7 +126,7 @@
|
|||
.search-input-wrap {
|
||||
.search-icon,
|
||||
.clear-icon {
|
||||
color: rgba($color-200, .8);
|
||||
fill: rgba($color-200, .8);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,7 +141,7 @@
|
|||
|
||||
.search-input-wrap {
|
||||
.search-icon {
|
||||
color: rgba($color-200, .8);
|
||||
fill: rgba($color-200, .8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -252,7 +252,7 @@ body {
|
|||
|
||||
.search-input-wrap {
|
||||
.search-icon {
|
||||
color: $theme-gray-200;
|
||||
fill: $theme-gray-200;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
|
|
|
@ -109,8 +109,7 @@ header {
|
|||
|
||||
.user-counter {
|
||||
svg {
|
||||
height: 16px;
|
||||
width: 23px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,16 +132,16 @@ header {
|
|||
}
|
||||
|
||||
&.navbar-gitlab-new {
|
||||
.fa-times {
|
||||
.close-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.menu-expanded {
|
||||
.fa-ellipsis-v {
|
||||
.more-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.fa-times {
|
||||
.close-icon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,9 @@
|
|||
}
|
||||
|
||||
svg {
|
||||
&.s8 { @include svg-size(8px); }
|
||||
&.s16 { @include svg-size(16px); }
|
||||
&.s18 { @include svg-size(18px); }
|
||||
&.s24 { @include svg-size(24px); }
|
||||
&.s32 { @include svg-size(32px); }
|
||||
&.s48 { @include svg-size(48px); }
|
||||
|
|
|
@ -229,6 +229,10 @@ ul.content-list {
|
|||
.label-default {
|
||||
color: $gl-text-color-secondary;
|
||||
}
|
||||
|
||||
.avatar-cell {
|
||||
align-self: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.panel > .content-list > li {
|
||||
|
|
|
@ -120,17 +120,24 @@ header.navbar-gitlab-new {
|
|||
.container-fluid {
|
||||
.navbar-toggle {
|
||||
min-width: 45px;
|
||||
padding: 4px $gl-padding;
|
||||
padding: 0 $gl-padding;
|
||||
margin-right: -7px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
color: currentColor;
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&.active {
|
||||
color: currentColor;
|
||||
background-color: transparent;
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -279,10 +286,6 @@ header.navbar-gitlab-new {
|
|||
}
|
||||
}
|
||||
|
||||
.admin-icon i {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.caret-down {
|
||||
height: 11px;
|
||||
width: 11px;
|
||||
|
|
|
@ -56,8 +56,8 @@ $new-sidebar-collapsed-width: 50px;
|
|||
color: $hover-color;
|
||||
|
||||
.settings-avatar {
|
||||
i {
|
||||
color: $hover-color;
|
||||
svg {
|
||||
fill: $hover-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,12 +76,9 @@ $new-sidebar-collapsed-width: 50px;
|
|||
.settings-avatar {
|
||||
background-color: $white-light;
|
||||
|
||||
i {
|
||||
font-size: 20px;
|
||||
width: 100%;
|
||||
color: $gl-text-color-secondary;
|
||||
text-align: center;
|
||||
align-self: center;
|
||||
svg {
|
||||
fill: $gl-text-color-secondary;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -177,16 +174,16 @@ $new-sidebar-collapsed-width: 50px;
|
|||
.nav-icon-container {
|
||||
display: flex;
|
||||
margin-right: 8px;
|
||||
|
||||
svg {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.fly-out-top-item {
|
||||
display: none;
|
||||
}
|
||||
|
||||
svg {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-sidebar-inner-scroll {
|
||||
|
@ -354,18 +351,22 @@ $new-sidebar-collapsed-width: 50px;
|
|||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
i {
|
||||
font-size: 20px;
|
||||
svg {
|
||||
fill: $gl-text-color-secondary;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.fa-angle-double-right {
|
||||
.icon-angle-double-right {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $border-color;
|
||||
color: $gl-text-color;
|
||||
|
||||
svg {
|
||||
fill: $gl-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -407,15 +408,16 @@ $new-sidebar-collapsed-width: 50px;
|
|||
|
||||
.toggle-sidebar-button {
|
||||
width: $new-sidebar-collapsed-width - 2px;
|
||||
padding: 16px 18px;
|
||||
padding: 16px;
|
||||
|
||||
.collapse-text,
|
||||
.fa-angle-double-left {
|
||||
.icon-angle-double-left {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.fa-angle-double-right {
|
||||
.icon-angle-double-right {
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,6 +77,18 @@
|
|||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
&.left-side-selected {
|
||||
td.line_content.parallel.right-side {
|
||||
@include user-select(none);
|
||||
}
|
||||
}
|
||||
|
||||
&.right-side-selected {
|
||||
td.line_content.parallel.left-side {
|
||||
@include user-select(none);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tr.line_holder.parallel {
|
||||
|
|
|
@ -516,7 +516,7 @@ a.deploy-project-label {
|
|||
text-align: center;
|
||||
width: 169px;
|
||||
|
||||
&:hover,
|
||||
&:hover:not(.disabled),
|
||||
&.forked {
|
||||
background-color: $row-hover;
|
||||
border-color: $row-hover-border;
|
||||
|
@ -543,6 +543,15 @@ a.deploy-project-label {
|
|||
padding-top: $gl-padding;
|
||||
color: $gl-text-color;
|
||||
|
||||
&.disabled {
|
||||
opacity: .3;
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.caption {
|
||||
min-height: 30px;
|
||||
padding: $gl-padding 0;
|
||||
|
|
|
@ -81,17 +81,10 @@ input[type="checkbox"]:hover {
|
|||
.clear-icon {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 0;
|
||||
|
||||
&::before {
|
||||
font-family: FontAwesome;
|
||||
font-weight: $gl-font-weight-normal;
|
||||
font-style: normal;
|
||||
}
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
@extend .fa-search;
|
||||
transition: color $default-transition-duration;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
|
@ -99,7 +92,6 @@ input[type="checkbox"]:hover {
|
|||
}
|
||||
|
||||
.clear-icon {
|
||||
@extend .fa-times;
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@ class ApplicationController < ActionController::Base
|
|||
|
||||
around_action :set_locale
|
||||
|
||||
after_action :set_page_title_header, if: -> { request.format == :json }
|
||||
|
||||
protect_from_forgery with: :exception
|
||||
|
||||
helper_method :can?, :current_application_settings
|
||||
|
@ -335,4 +337,9 @@ class ApplicationController < ActionController::Base
|
|||
sign_in user, store: false
|
||||
end
|
||||
end
|
||||
|
||||
def set_page_title_header
|
||||
# Per https://tools.ietf.org/html/rfc5987, headers need to be ISO-8859-1, not UTF-8
|
||||
response.headers['Page-Title'] = page_title('GitLab').encode('ISO-8859-1')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -59,6 +59,7 @@ module AuthenticatesWithTwoFactor
|
|||
sign_in(user)
|
||||
else
|
||||
user.increment_failed_attempts!
|
||||
Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=OTP")
|
||||
flash.now[:alert] = 'Invalid two-factor code.'
|
||||
prompt_for_two_factor(user)
|
||||
end
|
||||
|
@ -75,6 +76,7 @@ module AuthenticatesWithTwoFactor
|
|||
sign_in(user)
|
||||
else
|
||||
user.increment_failed_attempts!
|
||||
Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=U2F")
|
||||
flash.now[:alert] = 'Authentication via U2F device failed.'
|
||||
prompt_for_two_factor(user)
|
||||
end
|
||||
|
|
|
@ -15,9 +15,9 @@ module NotesActions
|
|||
|
||||
notes = notes_finder.execute
|
||||
.inc_relations_for_view
|
||||
.reject { |n| n.cross_reference_not_visible_for?(current_user) }
|
||||
|
||||
notes = prepare_notes_for_rendering(notes)
|
||||
notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
|
||||
|
||||
notes_json[:notes] =
|
||||
if noteable.discussions_rendered_on_frontend?
|
||||
|
|
|
@ -14,6 +14,7 @@ class ConfirmationsController < Devise::ConfirmationsController
|
|||
if signed_in?(resource_name)
|
||||
after_sign_in(resource)
|
||||
else
|
||||
Gitlab::AppLogger.info("Email Confirmed: username=#{resource.username} email=#{resource.email} ip=#{request.remote_ip}")
|
||||
flash[:notice] += " Please sign in."
|
||||
new_session_path(resource_name)
|
||||
end
|
||||
|
|
|
@ -41,6 +41,8 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
format.json do
|
||||
page_title @blob.path, @ref, @project.name_with_namespace
|
||||
|
||||
show_json
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,7 +16,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
before_action :authorize_create_issue!, only: [:new, :create]
|
||||
|
||||
# Allow modify issue
|
||||
before_action :authorize_update_issue!, only: [:edit, :update, :move]
|
||||
before_action :authorize_update_issue!, only: [:update, :move]
|
||||
|
||||
# Allow create a new branch and empty WIP merge request from current issue
|
||||
before_action :authorize_create_merge_request!, only: [:create_merge_request]
|
||||
|
@ -63,10 +63,6 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
respond_with(@issue)
|
||||
end
|
||||
|
||||
def edit
|
||||
respond_with(@issue)
|
||||
end
|
||||
|
||||
def show
|
||||
@noteable = @issue
|
||||
@note = @project.notes.new(noteable: @issue)
|
||||
|
@ -126,10 +122,6 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
@issue = Issues::UpdateService.new(project, current_user, update_params).execute(issue)
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
recaptcha_check_with_fallback { render :edit }
|
||||
end
|
||||
|
||||
format.json do
|
||||
render_issue_json
|
||||
end
|
||||
|
|
|
@ -35,6 +35,8 @@ class Projects::TreeController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
format.json do
|
||||
page_title @path.presence || _("Files"), @ref, @project.name_with_namespace
|
||||
|
||||
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38261
|
||||
Gitlab::GitalyClient.allow_n_plus_1_calls do
|
||||
render json: TreeSerializer.new(project: @project, repository: @repository, ref: @ref).represent(@tree)
|
||||
|
|
|
@ -344,6 +344,7 @@ class ProjectsController < Projects::ApplicationController
|
|||
:tag_list,
|
||||
:visibility_level,
|
||||
:template_name,
|
||||
:merge_method,
|
||||
|
||||
project_feature_attributes: %i[
|
||||
builds_access_level
|
||||
|
|
|
@ -42,10 +42,12 @@ class RegistrationsController < Devise::RegistrationsController
|
|||
end
|
||||
|
||||
def after_sign_up_path_for(user)
|
||||
Gitlab::AppLogger.info("User Created: username=#{user.username} email=#{user.email} ip=#{request.remote_ip} confirmed:#{user.confirmed?}")
|
||||
user.confirmed? ? dashboard_projects_path : users_almost_there_path
|
||||
end
|
||||
|
||||
def after_inactive_sign_up_path_for(_resource)
|
||||
def after_inactive_sign_up_path_for(resource)
|
||||
Gitlab::AppLogger.info("User Created: username=#{resource.username} email=#{resource.email} ip=#{request.remote_ip} confirmed:false")
|
||||
users_almost_there_path
|
||||
end
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ class SessionsController < Devise::SessionsController
|
|||
before_action :auto_sign_in_with_provider, only: [:new]
|
||||
before_action :load_recaptcha
|
||||
|
||||
after_action :log_failed_login, only: [:new]
|
||||
|
||||
def new
|
||||
set_minimum_password_length
|
||||
@ldap_servers = Gitlab::LDAP::Config.available_servers
|
||||
|
@ -29,12 +31,13 @@ class SessionsController < Devise::SessionsController
|
|||
end
|
||||
# hide the signed-in notification
|
||||
flash[:notice] = nil
|
||||
log_audit_event(current_user, with: authentication_method)
|
||||
log_audit_event(current_user, resource, with: authentication_method)
|
||||
log_user_activity(current_user)
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
Gitlab::AppLogger.info("User Logout: username=#{current_user.username} ip=#{request.remote_ip}")
|
||||
super
|
||||
# hide the signed_out notice
|
||||
flash[:notice] = nil
|
||||
|
@ -42,6 +45,16 @@ class SessionsController < Devise::SessionsController
|
|||
|
||||
private
|
||||
|
||||
def log_failed_login
|
||||
return unless failed_login?
|
||||
|
||||
Gitlab::AppLogger.info("Failed Login: username=#{user_params[:login]} ip=#{request.remote_ip}")
|
||||
end
|
||||
|
||||
def failed_login?
|
||||
(options = env["warden.options"]) && options[:action] == "unauthenticated"
|
||||
end
|
||||
|
||||
def login_counter
|
||||
@login_counter ||= Gitlab::Metrics.counter(:user_session_logins_total, 'User sign in count')
|
||||
end
|
||||
|
@ -123,7 +136,8 @@ class SessionsController < Devise::SessionsController
|
|||
user.invalidate_otp_backup_code!(user_params[:otp_attempt])
|
||||
end
|
||||
|
||||
def log_audit_event(user, options = {})
|
||||
def log_audit_event(user, resource, options = {})
|
||||
Gitlab::AppLogger.info("Successful Login: username=#{resource.username} ip=#{request.remote_ip} method=#{options[:with]} admin=#{resource.admin?}")
|
||||
AuditEventService.new(user, user, options)
|
||||
.for_authentication.security_event
|
||||
end
|
||||
|
|
20
app/finders/concerns/custom_attributes_filter.rb
Normal file
20
app/finders/concerns/custom_attributes_filter.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
module CustomAttributesFilter
|
||||
def by_custom_attributes(items)
|
||||
return items unless params[:custom_attributes].is_a?(Hash)
|
||||
return items unless Ability.allowed?(current_user, :read_custom_attribute)
|
||||
|
||||
association = items.reflect_on_association(:custom_attributes)
|
||||
attributes_table = association.klass.arel_table
|
||||
attributable_table = items.model.arel_table
|
||||
|
||||
custom_attributes = association.klass.select('true').where(
|
||||
attributes_table[association.foreign_key]
|
||||
.eq(attributable_table[association.association_primary_key])
|
||||
)
|
||||
|
||||
# perform a subquery for each attribute to be filtered
|
||||
params[:custom_attributes].inject(items) do |scope, (key, value)|
|
||||
scope.where('EXISTS (?)', custom_attributes.where(key: key, value: value))
|
||||
end
|
||||
end
|
||||
end
|
|
@ -15,6 +15,7 @@
|
|||
#
|
||||
class UsersFinder
|
||||
include CreatedAtFilter
|
||||
include CustomAttributesFilter
|
||||
|
||||
attr_accessor :current_user, :params
|
||||
|
||||
|
@ -32,6 +33,7 @@ class UsersFinder
|
|||
users = by_external_identity(users)
|
||||
users = by_external(users)
|
||||
users = by_created_at(users)
|
||||
users = by_custom_attributes(users)
|
||||
|
||||
users
|
||||
end
|
||||
|
|
|
@ -10,11 +10,7 @@ module BreadcrumbsHelper
|
|||
def breadcrumb_title_link
|
||||
return @breadcrumb_link if @breadcrumb_link
|
||||
|
||||
if controller.available_action?(:index)
|
||||
url_for(action: "index")
|
||||
else
|
||||
request.path
|
||||
end
|
||||
request.path
|
||||
end
|
||||
|
||||
def breadcrumb_title(title)
|
||||
|
@ -25,7 +21,7 @@ module BreadcrumbsHelper
|
|||
|
||||
def breadcrumb_list_item(link)
|
||||
content_tag "li" do
|
||||
link + icon("angle-right", class: "breadcrumbs-list-angle")
|
||||
link + sprite_icon("angle-right", size: 8, css_class: "breadcrumbs-list-angle")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -33,19 +33,21 @@ module DiffHelper
|
|||
end
|
||||
|
||||
def diff_match_line(old_pos, new_pos, text: '', view: :inline, bottom: false)
|
||||
content = content_tag :td, text, class: "line_content match #{view == :inline ? '' : view}"
|
||||
cls = ['diff-line-num', 'unfold', 'js-unfold']
|
||||
cls << 'js-unfold-bottom' if bottom
|
||||
content_line_class = %w[line_content match]
|
||||
content_line_class << 'parallel' if view == :parallel
|
||||
|
||||
line_num_class = %w[diff-line-num unfold js-unfold]
|
||||
line_num_class << 'js-unfold-bottom' if bottom
|
||||
|
||||
html = ''
|
||||
if old_pos
|
||||
html << content_tag(:td, '...', class: cls + ['old_line'], data: { linenumber: old_pos })
|
||||
html << content unless view == :inline
|
||||
html << content_tag(:td, '...', class: [*line_num_class, 'old_line'], data: { linenumber: old_pos })
|
||||
html << content_tag(:td, text, class: [*content_line_class, 'left-side']) if view == :parallel
|
||||
end
|
||||
|
||||
if new_pos
|
||||
html << content_tag(:td, '...', class: cls + ['new_line'], data: { linenumber: new_pos })
|
||||
html << content
|
||||
html << content_tag(:td, '...', class: [*line_num_class, 'new_line'], data: { linenumber: new_pos })
|
||||
html << content_tag(:td, text, class: [*content_line_class, ('right-side' if view == :parallel)])
|
||||
end
|
||||
|
||||
html.html_safe
|
||||
|
|
|
@ -24,9 +24,9 @@ module IconsHelper
|
|||
end
|
||||
|
||||
def sprite_icon(icon_name, size: nil, css_class: nil)
|
||||
css_classes = size ? "s#{size}" : nil
|
||||
css_classes = size ? "s#{size}" : ""
|
||||
css_classes << " #{css_class}" unless css_class.blank?
|
||||
content_tag(:svg, content_tag(:use, "", { "xlink:href" => "#{image_path('icons.svg')}##{icon_name}" } ), class: css_classes)
|
||||
content_tag(:svg, content_tag(:use, "", { "xlink:href" => "#{image_path('icons.svg')}##{icon_name}" } ), class: css_classes.empty? ? nil : css_classes)
|
||||
end
|
||||
|
||||
def audit_icon(names, options = {})
|
||||
|
|
|
@ -248,16 +248,25 @@ module IssuablesHelper
|
|||
Gitlab::IssuablesCountForState.new(finder)[state]
|
||||
end
|
||||
|
||||
def close_issuable_url(issuable)
|
||||
issuable_url(issuable, close_reopen_params(issuable, :close))
|
||||
def close_issuable_path(issuable)
|
||||
issuable_path(issuable, close_reopen_params(issuable, :close))
|
||||
end
|
||||
|
||||
def reopen_issuable_url(issuable)
|
||||
issuable_url(issuable, close_reopen_params(issuable, :reopen))
|
||||
def reopen_issuable_path(issuable)
|
||||
issuable_path(issuable, close_reopen_params(issuable, :reopen))
|
||||
end
|
||||
|
||||
def close_reopen_issuable_url(issuable, should_inverse = false)
|
||||
issuable.closed? ^ should_inverse ? reopen_issuable_url(issuable) : close_issuable_url(issuable)
|
||||
def close_reopen_issuable_path(issuable, should_inverse = false)
|
||||
issuable.closed? ^ should_inverse ? reopen_issuable_path(issuable) : close_issuable_path(issuable)
|
||||
end
|
||||
|
||||
def issuable_path(issuable, *options)
|
||||
case issuable
|
||||
when Issue
|
||||
issue_path(issuable, *options)
|
||||
when MergeRequest
|
||||
merge_request_path(issuable, *options)
|
||||
end
|
||||
end
|
||||
|
||||
def issuable_url(issuable, *options)
|
||||
|
|
|
@ -9,7 +9,7 @@ module PageLayoutHelper
|
|||
end
|
||||
|
||||
# Segments are seperated by middot
|
||||
@page_title.join(" \u00b7 ")
|
||||
@page_title.join(" · ")
|
||||
end
|
||||
|
||||
# Define or get a description for the current page
|
||||
|
|
|
@ -21,11 +21,14 @@ module ProjectsHelper
|
|||
classes = %W[avatar avatar-inline s#{opts[:size]}]
|
||||
classes << opts[:avatar_class] if opts[:avatar_class]
|
||||
|
||||
image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: classes, alt: '')
|
||||
avatar = avatar_icon(author, opts[:size])
|
||||
src = opts[:lazy_load] ? nil : avatar
|
||||
|
||||
image_tag(src, width: opts[:size], class: classes, alt: '', "data-src" => avatar)
|
||||
end
|
||||
|
||||
def link_to_member(project, author, opts = {}, &block)
|
||||
default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name", tooltip: false }
|
||||
default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name", tooltip: false, lazy_load: false }
|
||||
opts = default_opts.merge(opts)
|
||||
|
||||
return "(deleted)" unless author
|
||||
|
|
|
@ -73,7 +73,7 @@ class GpgKey < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def verified_and_belongs_to_email?(email)
|
||||
emails_with_verified_status.fetch(email, false)
|
||||
emails_with_verified_status.fetch(email.downcase, false)
|
||||
end
|
||||
|
||||
def update_invalid_gpg_signatures
|
||||
|
|
|
@ -524,6 +524,14 @@ class MergeRequest < ActiveRecord::Base
|
|||
true
|
||||
end
|
||||
|
||||
def ff_merge_possible?
|
||||
project.repository.ancestor?(target_branch_sha, diff_head_sha)
|
||||
end
|
||||
|
||||
def should_be_rebased?
|
||||
project.ff_merge_must_be_possible? && !ff_merge_possible?
|
||||
end
|
||||
|
||||
def can_cancel_merge_when_pipeline_succeeds?(current_user)
|
||||
can_be_merged_by?(current_user) || self.author == current_user
|
||||
end
|
||||
|
|
|
@ -64,6 +64,7 @@ class Project < ActiveRecord::Base
|
|||
|
||||
# Storage specific hooks
|
||||
after_initialize :use_hashed_storage
|
||||
after_create :check_repository_absence!
|
||||
after_create :ensure_storage_path_exists
|
||||
after_save :ensure_storage_path_exists, if: :namespace_id_changed?
|
||||
|
||||
|
@ -72,6 +73,7 @@ class Project < ActiveRecord::Base
|
|||
attr_accessor :old_path_with_namespace
|
||||
attr_accessor :template_name
|
||||
attr_writer :pipeline_status
|
||||
attr_accessor :skip_disk_validation
|
||||
|
||||
alias_attribute :title, :name
|
||||
|
||||
|
@ -227,7 +229,7 @@ class Project < ActiveRecord::Base
|
|||
validates :import_url, importable_url: true, if: [:external_import?, :import_url_changed?]
|
||||
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
|
||||
validate :check_limit, on: :create
|
||||
validate :can_create_repository?, on: [:create, :update], if: ->(project) { !project.persisted? || project.renamed? }
|
||||
validate :check_repository_path_availability, on: :update, if: ->(project) { project.renamed? }
|
||||
validate :avatar_type,
|
||||
if: ->(project) { project.avatar.present? && project.avatar_changed? }
|
||||
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
|
||||
|
@ -245,6 +247,9 @@ class Project < ActiveRecord::Base
|
|||
scope :pending_delete, -> { where(pending_delete: true) }
|
||||
scope :without_deleted, -> { where(pending_delete: false) }
|
||||
|
||||
scope :with_hashed_storage, -> { where('storage_version >= 1') }
|
||||
scope :with_legacy_storage, -> { where(storage_version: [nil, 0]) }
|
||||
|
||||
scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) }
|
||||
scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }
|
||||
|
||||
|
@ -1015,12 +1020,15 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
|
||||
# Check if repository already exists on disk
|
||||
def can_create_repository?
|
||||
def check_repository_path_availability
|
||||
return true if skip_disk_validation
|
||||
return false unless repository_storage_path
|
||||
|
||||
expires_full_path_cache # we need to clear cache to validate renames correctly
|
||||
|
||||
if gitlab_shell.exists?(repository_storage_path, "#{disk_path}.git")
|
||||
# Check if repository with same path already exists on disk we can
|
||||
# skip this for the hashed storage because the path does not change
|
||||
if legacy_storage? && repository_with_same_path_already_exists?
|
||||
errors.add(:base, 'There is already a repository with that name on disk')
|
||||
return false
|
||||
end
|
||||
|
@ -1032,7 +1040,7 @@ class Project < ActiveRecord::Base
|
|||
# Forked import is handled asynchronously
|
||||
return if forked? && !force
|
||||
|
||||
if gitlab_shell.add_repository(repository_storage_path, disk_path)
|
||||
if gitlab_shell.add_repository(repository_storage, disk_path)
|
||||
repository.after_create
|
||||
true
|
||||
else
|
||||
|
@ -1550,18 +1558,72 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def legacy_storage?
|
||||
self.storage_version.nil?
|
||||
[nil, 0].include?(self.storage_version)
|
||||
end
|
||||
|
||||
def hashed_storage?
|
||||
self.storage_version && self.storage_version >= 1
|
||||
end
|
||||
|
||||
def renamed?
|
||||
persisted? && path_changed?
|
||||
end
|
||||
|
||||
def merge_method
|
||||
if self.merge_requests_ff_only_enabled
|
||||
:ff
|
||||
elsif self.merge_requests_rebase_enabled
|
||||
:rebase_merge
|
||||
else
|
||||
:merge
|
||||
end
|
||||
end
|
||||
|
||||
def merge_method=(method)
|
||||
case method.to_s
|
||||
when "ff"
|
||||
self.merge_requests_ff_only_enabled = true
|
||||
self.merge_requests_rebase_enabled = true
|
||||
when "rebase_merge"
|
||||
self.merge_requests_ff_only_enabled = false
|
||||
self.merge_requests_rebase_enabled = true
|
||||
when "merge"
|
||||
self.merge_requests_ff_only_enabled = false
|
||||
self.merge_requests_rebase_enabled = false
|
||||
end
|
||||
end
|
||||
|
||||
def ff_merge_must_be_possible?
|
||||
self.merge_requests_ff_only_enabled || self.merge_requests_rebase_enabled
|
||||
end
|
||||
|
||||
def migrate_to_hashed_storage!
|
||||
return if hashed_storage?
|
||||
|
||||
update!(repository_read_only: true)
|
||||
|
||||
if repo_reference_count > 0 || wiki_reference_count > 0
|
||||
ProjectMigrateHashedStorageWorker.perform_in(Gitlab::ReferenceCounter::REFERENCE_EXPIRE_TIME, id)
|
||||
else
|
||||
ProjectMigrateHashedStorageWorker.perform_async(id)
|
||||
end
|
||||
end
|
||||
|
||||
def storage_version=(value)
|
||||
super
|
||||
|
||||
@storage = nil if storage_version_changed?
|
||||
end
|
||||
|
||||
def gl_repository(is_wiki:)
|
||||
Gitlab::GlRepository.gl_repository(self, is_wiki)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def storage
|
||||
@storage ||=
|
||||
if self.storage_version && self.storage_version >= 1
|
||||
if hashed_storage?
|
||||
Storage::HashedProject.new(self)
|
||||
else
|
||||
Storage::LegacyProject.new(self)
|
||||
|
@ -1574,6 +1636,27 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def repo_reference_count
|
||||
Gitlab::ReferenceCounter.new(gl_repository(is_wiki: false)).value
|
||||
end
|
||||
|
||||
def wiki_reference_count
|
||||
Gitlab::ReferenceCounter.new(gl_repository(is_wiki: true)).value
|
||||
end
|
||||
|
||||
def check_repository_absence!
|
||||
return if skip_disk_validation
|
||||
|
||||
if repository_storage_path.blank? || repository_with_same_path_already_exists?
|
||||
errors.add(:base, 'There is already a repository with that name on disk')
|
||||
throw :abort
|
||||
end
|
||||
end
|
||||
|
||||
def repository_with_same_path_already_exists?
|
||||
gitlab_shell.exists?(repository_storage_path, "#{disk_path}.git")
|
||||
end
|
||||
|
||||
# set last_activity_at to the same as created_at
|
||||
def set_last_activity_at
|
||||
update_column(:last_activity_at, self.created_at)
|
||||
|
|
|
@ -174,7 +174,7 @@ class ProjectWiki
|
|||
private
|
||||
|
||||
def init_repo(disk_path)
|
||||
gitlab_shell.add_repository(project.repository_storage_path, disk_path)
|
||||
gitlab_shell.add_repository(project.repository_storage, disk_path)
|
||||
end
|
||||
|
||||
def commit_details(action, message = nil, title = nil)
|
||||
|
|
|
@ -34,7 +34,10 @@ class Repository
|
|||
CACHED_METHODS = %i(size commit_count rendered_readme contribution_guide
|
||||
changelog license_blob license_key gitignore koding_yml
|
||||
gitlab_ci_yml branch_names tag_names branch_count
|
||||
tag_count avatar exists? empty? root_ref).freeze
|
||||
tag_count avatar exists? empty? root_ref has_visible_content?).freeze
|
||||
|
||||
# Methods that use cache_method but only memoize the value
|
||||
MEMOIZED_CACHED_METHODS = %i(license empty_repo?).freeze
|
||||
|
||||
# Certain method caches should be refreshed when certain types of files are
|
||||
# changed. This Hash maps file types (as returned by Gitlab::FileDetector) to
|
||||
|
@ -91,12 +94,6 @@ class Repository
|
|||
)
|
||||
end
|
||||
|
||||
# we need to have this method here because it is not cached in ::Git and
|
||||
# the method is called multiple times for every request
|
||||
def has_visible_content?
|
||||
branch_count > 0
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class.name}:#{@disk_path}>"
|
||||
end
|
||||
|
@ -275,7 +272,7 @@ class Repository
|
|||
end
|
||||
|
||||
def expire_branches_cache
|
||||
expire_method_caches(%i(branch_names branch_count))
|
||||
expire_method_caches(%i(branch_names branch_count has_visible_content?))
|
||||
@local_branches = nil
|
||||
@branch_exists_memo = nil
|
||||
end
|
||||
|
@ -346,7 +343,7 @@ class Repository
|
|||
def expire_emptiness_caches
|
||||
return unless empty?
|
||||
|
||||
expire_method_caches(%i(empty?))
|
||||
expire_method_caches(%i(empty? has_visible_content?))
|
||||
end
|
||||
|
||||
def lookup_cache
|
||||
|
@ -523,9 +520,10 @@ class Repository
|
|||
delegate :tag_names, to: :raw_repository
|
||||
cache_method :tag_names, fallback: []
|
||||
|
||||
delegate :branch_count, :tag_count, to: :raw_repository
|
||||
delegate :branch_count, :tag_count, :has_visible_content?, to: :raw_repository
|
||||
cache_method :branch_count, fallback: 0
|
||||
cache_method :tag_count, fallback: 0
|
||||
cache_method :has_visible_content?, fallback: false
|
||||
|
||||
def avatar
|
||||
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38327
|
||||
|
@ -852,6 +850,25 @@ class Repository
|
|||
end
|
||||
end
|
||||
|
||||
def ff_merge(user, source, target_branch, merge_request: nil)
|
||||
our_commit = rugged.branches[target_branch].target
|
||||
their_commit =
|
||||
if source.is_a?(Gitlab::Git::Commit)
|
||||
source.raw_commit
|
||||
else
|
||||
rugged.lookup(source)
|
||||
end
|
||||
|
||||
raise 'Invalid merge target' if our_commit.nil?
|
||||
raise 'Invalid merge source' if their_commit.nil?
|
||||
|
||||
with_branch(user, target_branch) do |start_commit|
|
||||
merge_request&.update(in_progress_merge_commit_sha: their_commit.oid)
|
||||
|
||||
their_commit.oid
|
||||
end
|
||||
end
|
||||
|
||||
def revert(
|
||||
user, commit, branch_name, message,
|
||||
start_branch_name: nil, start_project: project)
|
||||
|
|
|
@ -4,6 +4,7 @@ module Storage
|
|||
delegate :gitlab_shell, :repository_storage_path, to: :project
|
||||
|
||||
ROOT_PATH_PREFIX = '@hashed'.freeze
|
||||
STORAGE_VERSION = 1
|
||||
|
||||
def initialize(project)
|
||||
@project = project
|
||||
|
|
|
@ -130,6 +130,8 @@ class User < ActiveRecord::Base
|
|||
has_many :assigned_issues, class_name: "Issue", through: :issue_assignees, source: :issue
|
||||
has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
||||
has_many :custom_attributes, class_name: 'UserCustomAttribute'
|
||||
|
||||
#
|
||||
# Validations
|
||||
#
|
||||
|
@ -690,7 +692,11 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def ldap_user?
|
||||
identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"])
|
||||
if identities.loaded?
|
||||
identities.find { |identity| identity.provider.start_with?('ldap') && !identity.extern_uid.nil? }
|
||||
else
|
||||
identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"])
|
||||
end
|
||||
end
|
||||
|
||||
def ldap_identity
|
||||
|
@ -1061,6 +1067,12 @@ class User < ActiveRecord::Base
|
|||
user_synced_attributes_metadata&.read_only?(attribute)
|
||||
end
|
||||
|
||||
# override, from Devise
|
||||
def lock_access!
|
||||
Gitlab::AppLogger.info("Account Locked: username=#{username}")
|
||||
super
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# override, from Devise::Validatable
|
||||
|
|
6
app/models/user_custom_attribute.rb
Normal file
6
app/models/user_custom_attribute.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
class UserCustomAttribute < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
|
||||
validates :user_id, :key, :value, presence: true
|
||||
validates :key, uniqueness: { scope: [:user_id] }
|
||||
end
|
|
@ -11,6 +11,8 @@ class GlobalPolicy < BasePolicy
|
|||
with_options scope: :user, score: 0
|
||||
condition(:access_locked) { @user.access_locked? }
|
||||
|
||||
condition(:can_create_fork, scope: :user) { @user.manageable_namespaces.any? { |namespace| @user.can?(:create_projects, namespace) } }
|
||||
|
||||
rule { anonymous }.policy do
|
||||
prevent :log_in
|
||||
prevent :access_api
|
||||
|
@ -40,6 +42,10 @@ class GlobalPolicy < BasePolicy
|
|||
enable :create_group
|
||||
end
|
||||
|
||||
rule { can_create_fork }.policy do
|
||||
enable :create_fork
|
||||
end
|
||||
|
||||
rule { access_locked }.policy do
|
||||
prevent :log_in
|
||||
end
|
||||
|
@ -47,4 +53,9 @@ class GlobalPolicy < BasePolicy
|
|||
rule { ~(anonymous & restricted_public_level) }.policy do
|
||||
enable :read_users_list
|
||||
end
|
||||
|
||||
rule { admin }.policy do
|
||||
enable :read_custom_attribute
|
||||
enable :update_custom_attribute
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
class NamespacePolicy < BasePolicy
|
||||
rule { anonymous }.prevent_all
|
||||
|
||||
condition(:personal_project, scope: :subject) { @subject.kind == 'user' }
|
||||
condition(:can_create_personal_project, scope: :user) { @user.can_create_project? }
|
||||
condition(:owner) { @subject.owner == @user }
|
||||
|
||||
rule { owner | admin }.policy do
|
||||
enable :create_projects
|
||||
enable :admin_namespace
|
||||
end
|
||||
|
||||
rule { personal_project & ~can_create_personal_project }.prevent :create_projects
|
||||
end
|
||||
|
|
|
@ -13,6 +13,11 @@ class MergeRequestEntity < IssuableEntity
|
|||
expose :target_branch
|
||||
expose :target_project_id
|
||||
|
||||
expose :should_be_rebased?, as: :should_be_rebased
|
||||
expose :ff_only_enabled do |merge_request|
|
||||
merge_request.project.merge_requests_ff_only_enabled
|
||||
end
|
||||
|
||||
# Events
|
||||
expose :merge_event, using: EventEntity
|
||||
expose :closed_event, using: EventEntity
|
||||
|
|
24
app/services/merge_requests/ff_merge_service.rb
Normal file
24
app/services/merge_requests/ff_merge_service.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
module MergeRequests
|
||||
# MergeService class
|
||||
#
|
||||
# Do git fast-forward merge and in case of success
|
||||
# mark merge request as merged and execute all hooks and notifications
|
||||
# Executed when you do fast-forward merge via GitLab UI
|
||||
#
|
||||
class FfMergeService < MergeRequests::MergeService
|
||||
private
|
||||
|
||||
def commit
|
||||
repository.ff_merge(current_user,
|
||||
source,
|
||||
merge_request.target_branch,
|
||||
merge_request: merge_request)
|
||||
rescue Gitlab::Git::HooksService::PreReceiveError => e
|
||||
raise MergeError, e.message
|
||||
rescue StandardError => e
|
||||
raise MergeError, "Something went wrong during merge: #{e.message}"
|
||||
ensure
|
||||
merge_request.update(in_progress_merge_commit_sha: nil)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -11,6 +11,11 @@ module MergeRequests
|
|||
attr_reader :merge_request, :source
|
||||
|
||||
def execute(merge_request)
|
||||
if project.merge_requests_ff_only_enabled && !self.is_a?(FfMergeService)
|
||||
FfMergeService.new(project, current_user, params).execute(merge_request)
|
||||
return
|
||||
end
|
||||
|
||||
@merge_request = merge_request
|
||||
|
||||
unless @merge_request.mergeable?
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue