Commit d6e7c693 authored by Valery Sizov's avatar Valery Sizov

Merge CE to EE with all the conflicts [ckip ci]

parents 726ae97f 14046b9c
/coverage/
/coverage-javascript/ /coverage-javascript/
/public/ /public/
/tmp/ /tmp/
......
{ {
"env": { "env": {
"jquery": true,
"browser": true, "browser": true,
"es6": true "es6": true
}, },
"extends": "airbnb", "extends": "airbnb-base",
"globals": { "globals": {
"$": false,
"_": false, "_": false,
"gl": false, "gl": false,
"gon": false, "gon": false
"jQuery": false
}, },
"plugins": [ "plugins": [
"filenames" "filenames"
......
...@@ -227,6 +227,29 @@ rake brakeman: *exec ...@@ -227,6 +227,29 @@ rake brakeman: *exec
rake flay: *exec rake flay: *exec
license_finder: *exec license_finder: *exec
rake downtime_check: *exec rake downtime_check: *exec
<<<<<<< HEAD
=======
rake ee_compat_check:
<<: *exec
only:
- branches@gitlab-org/gitlab-ce
except:
- master
- tags
- /^[\d-]+-stable(-ee)?$/
allow_failure: yes
cache:
key: "ruby231-ee_compat_check_repo"
paths:
- ee_compat_check/repo/
- vendor/ruby
artifacts:
name: "${CI_BUILD_NAME}_${CI_BUILD_REF_NAME}_${CI_BUILD_REF}"
when: on_failure
expire_in: 10d
paths:
- ee_compat_check/patches/*.patch
>>>>>>> 14046b9c734e5e6506d63276f39f3f9d770c3699
rake db:migrate:reset: rake db:migrate:reset:
stage: test stage: test
......
...@@ -73,7 +73,7 @@ gem 'github-linguist', '~> 4.7.0', require: 'linguist' ...@@ -73,7 +73,7 @@ gem 'github-linguist', '~> 4.7.0', require: 'linguist'
# API # API
gem 'grape', '~> 0.15.0' gem 'grape', '~> 0.15.0'
gem 'grape-entity', '~> 0.4.2' gem 'grape-entity', '~> 0.6.0'
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors' gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
# Pagination # Pagination
...@@ -90,10 +90,8 @@ gem 'dropzonejs-rails', '~> 0.7.1' ...@@ -90,10 +90,8 @@ gem 'dropzonejs-rails', '~> 0.7.1'
# for backups # for backups
gem 'fog-aws', '~> 0.9' gem 'fog-aws', '~> 0.9'
gem 'fog-azure', '~> 0.0'
gem 'fog-core', '~> 1.40' gem 'fog-core', '~> 1.40'
gem 'fog-local', '~> 0.3' gem 'fog-local', '~> 0.3'
gem 'fog-google', '~> 0.3'
gem 'fog-openstack', '~> 0.1' gem 'fog-openstack', '~> 0.1'
gem 'fog-rackspace', '~> 0.1.1' gem 'fog-rackspace', '~> 0.1.1'
...@@ -145,7 +143,7 @@ gem 'acts-as-taggable-on', '~> 4.0' ...@@ -145,7 +143,7 @@ gem 'acts-as-taggable-on', '~> 4.0'
# Background jobs # Background jobs
gem 'sidekiq', '~> 4.2' gem 'sidekiq', '~> 4.2'
gem 'sidekiq-cron', '~> 0.4.0' gem 'sidekiq-cron', '~> 0.4.4'
gem 'redis-namespace', '~> 1.5.2' gem 'redis-namespace', '~> 1.5.2'
gem 'sidekiq-limit_fetch', '~> 3.4' gem 'sidekiq-limit_fetch', '~> 3.4'
...@@ -322,6 +320,8 @@ group :development, :test do ...@@ -322,6 +320,8 @@ group :development, :test do
gem 'knapsack', '~> 1.11.0' gem 'knapsack', '~> 1.11.0'
gem 'activerecord_sane_schema_dumper', '0.2' gem 'activerecord_sane_schema_dumper', '0.2'
gem 'stackprof', '~> 0.2.10'
end end
group :test do group :test do
......
...@@ -66,21 +66,6 @@ GEM ...@@ -66,21 +66,6 @@ GEM
descendants_tracker (~> 0.0.4) descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0) ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
azure (0.7.5)
addressable (~> 2.3)
azure-core (~> 0.1)
faraday (~> 0.9)
faraday_middleware (~> 0.10)
json (~> 1.8)
mime-types (>= 1, < 3.0)
nokogiri (~> 1.6)
systemu (~> 2.6)
thor (~> 0.19)
uuid (~> 2.0)
azure-core (0.1.2)
faraday (~> 0.9)
faraday_middleware (~> 0.10)
nokogiri (~> 1.6)
babel-source (5.8.35) babel-source (5.8.35)
babel-transpiler (0.7.0) babel-transpiler (0.7.0)
babel-source (>= 4.0, < 6) babel-source (>= 4.0, < 6)
...@@ -230,19 +215,10 @@ GEM ...@@ -230,19 +215,10 @@ GEM
fog-json (~> 1.0) fog-json (~> 1.0)
fog-xml (~> 0.1) fog-xml (~> 0.1)
ipaddress (~> 0.8) ipaddress (~> 0.8)
fog-azure (0.0.2)
azure (~> 0.6)
fog-core (~> 1.27)
fog-json (~> 1.0)
fog-xml (~> 0.1)
fog-core (1.42.0) fog-core (1.42.0)
builder builder
excon (~> 0.49) excon (~> 0.49)
formatador (~> 0.2) formatador (~> 0.2)
fog-google (0.3.2)
fog-core
fog-json
fog-xml
fog-json (1.0.2) fog-json (1.0.2)
fog-core (~> 1.0) fog-core (~> 1.0)
multi_json (~> 1.10) multi_json (~> 1.10)
...@@ -338,7 +314,7 @@ GEM ...@@ -338,7 +314,7 @@ GEM
rack-accept rack-accept
rack-mount rack-mount
virtus (>= 1.0.0) virtus (>= 1.0.0)
grape-entity (0.4.8) grape-entity (0.6.0)
activesupport activesupport
multi_json (>= 1.3.2) multi_json (>= 1.3.2)
gssapi (1.2.0) gssapi (1.2.0)
...@@ -421,8 +397,6 @@ GEM ...@@ -421,8 +397,6 @@ GEM
rb-inotify (>= 0.9) rb-inotify (>= 0.9)
loofah (2.0.3) loofah (2.0.3)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
macaddr (1.7.1)
systemu (~> 2.6.2)
mail (2.6.4) mail (2.6.4)
mime-types (>= 1.16, < 4) mime-types (>= 1.16, < 4)
mail_room (0.9.0) mail_room (0.9.0)
...@@ -700,10 +674,10 @@ GEM ...@@ -700,10 +674,10 @@ GEM
connection_pool (~> 2.2, >= 2.2.0) connection_pool (~> 2.2, >= 2.2.0)
rack-protection (~> 1.5) rack-protection (~> 1.5)
redis (~> 3.2, >= 3.2.1) redis (~> 3.2, >= 3.2.1)
sidekiq-cron (0.4.0) sidekiq-cron (0.4.4)
redis-namespace (>= 1.5.2) redis-namespace (>= 1.5.2)
rufus-scheduler (>= 2.0.24) rufus-scheduler (>= 2.0.24)
sidekiq (>= 4.0.0) sidekiq (>= 4.2.1)
sidekiq-limit_fetch (3.4.0) sidekiq-limit_fetch (3.4.0)
sidekiq (>= 4) sidekiq (>= 4)
simplecov (0.12.0) simplecov (0.12.0)
...@@ -741,6 +715,7 @@ GEM ...@@ -741,6 +715,7 @@ GEM
actionpack (>= 4.0) actionpack (>= 4.0)
activesupport (>= 4.0) activesupport (>= 4.0)
sprockets (>= 3.0.0) sprockets (>= 3.0.0)
stackprof (0.2.10)
state_machines (0.4.0) state_machines (0.4.0)
state_machines-activemodel (0.4.0) state_machines-activemodel (0.4.0)
activemodel (>= 4.1, < 5.1) activemodel (>= 4.1, < 5.1)
...@@ -752,7 +727,6 @@ GEM ...@@ -752,7 +727,6 @@ GEM
sys-filesystem (1.1.6) sys-filesystem (1.1.6)
ffi ffi
sysexits (1.2.0) sysexits (1.2.0)
systemu (2.6.5)
teaspoon (1.1.5) teaspoon (1.1.5)
railties (>= 3.2.5, < 6) railties (>= 3.2.5, < 6)
teaspoon-jasmine (2.2.0) teaspoon-jasmine (2.2.0)
...@@ -792,11 +766,14 @@ GEM ...@@ -792,11 +766,14 @@ GEM
get_process_mem (~> 0) get_process_mem (~> 0)
unicorn (>= 4, < 6) unicorn (>= 4, < 6)
uniform_notifier (1.10.0) uniform_notifier (1.10.0)
<<<<<<< HEAD
uuid (2.3.8) uuid (2.3.8)
macaddr (~> 1.0) macaddr (~> 1.0)
validates_hostname (1.0.5) validates_hostname (1.0.5)
activerecord (>= 3.0) activerecord (>= 3.0)
activesupport (>= 3.0) activesupport (>= 3.0)
=======
>>>>>>> 14046b9c734e5e6506d63276f39f3f9d770c3699
version_sorter (2.1.0) version_sorter (2.1.0)
virtus (1.0.5) virtus (1.0.5)
axiom-types (~> 0.1) axiom-types (~> 0.1)
...@@ -878,9 +855,7 @@ DEPENDENCIES ...@@ -878,9 +855,7 @@ DEPENDENCIES
ffaker (~> 2.0.0) ffaker (~> 2.0.0)
flay (~> 2.6.1) flay (~> 2.6.1)
fog-aws (~> 0.9) fog-aws (~> 0.9)
fog-azure (~> 0.0)
fog-core (~> 1.40) fog-core (~> 1.40)
fog-google (~> 0.3)
fog-local (~> 0.3) fog-local (~> 0.3)
fog-openstack (~> 0.1) fog-openstack (~> 0.1)
fog-rackspace (~> 0.1.1) fog-rackspace (~> 0.1.1)
...@@ -900,8 +875,12 @@ DEPENDENCIES ...@@ -900,8 +875,12 @@ DEPENDENCIES
gollum-rugged_adapter (~> 0.4.2) gollum-rugged_adapter (~> 0.4.2)
gon (~> 6.1.0) gon (~> 6.1.0)
grape (~> 0.15.0) grape (~> 0.15.0)
<<<<<<< HEAD
grape-entity (~> 0.4.2) grape-entity (~> 0.4.2)
gssapi gssapi
=======
grape-entity (~> 0.6.0)
>>>>>>> 14046b9c734e5e6506d63276f39f3f9d770c3699
haml_lint (~> 0.18.2) haml_lint (~> 0.18.2)
hamlit (~> 2.6.1) hamlit (~> 2.6.1)
health_check (~> 2.2.0) health_check (~> 2.2.0)
...@@ -989,7 +968,7 @@ DEPENDENCIES ...@@ -989,7 +968,7 @@ DEPENDENCIES
sham_rack (~> 1.3.6) sham_rack (~> 1.3.6)
shoulda-matchers (~> 2.8.0) shoulda-matchers (~> 2.8.0)
sidekiq (~> 4.2) sidekiq (~> 4.2)
sidekiq-cron (~> 0.4.0) sidekiq-cron (~> 0.4.4)
sidekiq-limit_fetch (~> 3.4) sidekiq-limit_fetch (~> 3.4)
simplecov (= 0.12.0) simplecov (= 0.12.0)
slack-notifier (~> 1.2.0) slack-notifier (~> 1.2.0)
...@@ -1001,6 +980,7 @@ DEPENDENCIES ...@@ -1001,6 +980,7 @@ DEPENDENCIES
spring-commands-teaspoon (~> 0.0.2) spring-commands-teaspoon (~> 0.0.2)
sprockets (~> 3.7.0) sprockets (~> 3.7.0)
sprockets-es6 (~> 0.9.2) sprockets-es6 (~> 0.9.2)
stackprof (~> 0.2.10)
state_machines-activerecord (~> 0.4.0) state_machines-activerecord (~> 0.4.0)
sys-filesystem (~> 1.1.6) sys-filesystem (~> 1.1.6)
teaspoon (~> 1.1.0) teaspoon (~> 1.1.0)
......
# GitLab # GitLab
[![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) [![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
[![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](http://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) [![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby)
[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
[![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42) [![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42)
...@@ -114,7 +114,7 @@ For more information please see the [architecture documentation](https://docs.gi ...@@ -114,7 +114,7 @@ For more information please see the [architecture documentation](https://docs.gi
## UX design ## UX design
Please adhere to the [UX Guide](doc/development/ux_guide/readme.md) when creating designs and implementing code. Please adhere to the [UX Guide](doc/development/ux_guide/index.md) when creating designs and implementing code.
## Third-party applications ## Third-party applications
......
...@@ -58,32 +58,11 @@ ...@@ -58,32 +58,11 @@
(function () { (function () {
document.addEventListener('page:fetch', gl.utils.cleanupBeforeFetch); document.addEventListener('page:fetch', gl.utils.cleanupBeforeFetch);
window.addEventListener('hashchange', gl.utils.shiftWindow); window.addEventListener('hashchange', gl.utils.handleLocationHash);
window.addEventListener('load', function onLoad() {
// automatically adjust scroll position for hash urls taking the height of the navbar into account window.removeEventListener('load', onLoad, false);
// https://github.com/twitter/bootstrap/issues/1768 gl.utils.handleLocationHash();
window.adjustScroll = function() { }, false);
var navbar = document.querySelector('.navbar-gitlab');
var subnav = document.querySelector('.layout-nav');
var fixedTabs = document.querySelector('.js-tabs-affix');
adjustment = 0;
if (navbar) adjustment -= navbar.offsetHeight;
if (subnav) adjustment -= subnav.offsetHeight;
if (fixedTabs) adjustment -= fixedTabs.offsetHeight;
return scrollBy(0, adjustment);
};
window.addEventListener("hashchange", adjustScroll);
window.onload = function () {
// Scroll the window to avoid the topnav bar
// https://github.com/twitter/bootstrap/issues/1768
if (location.hash) {
return setTimeout(adjustScroll, 100);
}
};
$(function () { $(function () {
var $body = $('body'); var $body = $('body');
......
...@@ -80,6 +80,7 @@ ...@@ -80,6 +80,7 @@
}, },
mounted () { mounted () {
const options = gl.issueBoards.getBoardSortableDefaultOptions({ const options = gl.issueBoards.getBoardSortableDefaultOptions({
scroll: document.querySelectorAll('.boards-list')[0],
group: 'issues', group: 'issues',
sort: false, sort: false,
disabled: this.disabled, disabled: this.disabled,
...@@ -88,13 +89,13 @@ ...@@ -88,13 +89,13 @@
const card = this.$refs.issue[e.oldIndex]; const card = this.$refs.issue[e.oldIndex];
card.showDetail = false; card.showDetail = false;
Store.moving.issue = card.issue;
Store.moving.list = card.list; Store.moving.list = card.list;
Store.moving.issue = Store.moving.list.findIssue(+e.item.dataset.issueId);
gl.issueBoards.onStart(); gl.issueBoards.onStart();
}, },
onAdd: (e) => { onAdd: (e) => {
gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue); gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue, e.newIndex);
this.$nextTick(() => { this.$nextTick(() => {
e.item.remove(); e.item.remove();
......
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
new gl.DueDateSelectors(); new gl.DueDateSelectors();
new LabelsSelect(); new LabelsSelect();
new Sidebar(); new Sidebar();
new Subscription('.subscription'); gl.Subscription.bindAll('.subscription');
} }
}); });
})(); })();
...@@ -106,9 +106,13 @@ class List { ...@@ -106,9 +106,13 @@ class List {
}); });
} }
addIssue (issue, listFrom) { addIssue (issue, listFrom, newIndex) {
if (!this.findIssue(issue.id)) { if (!this.findIssue(issue.id)) {
if (newIndex !== undefined) {
this.issues.splice(newIndex, 0, issue);
} else {
this.issues.push(issue); this.issues.push(issue);
}
if (this.label) { if (this.label) {
issue.addLabel(this.label); issue.addLabel(this.label);
......
...@@ -94,14 +94,14 @@ ...@@ -94,14 +94,14 @@
}); });
listFrom.update(); listFrom.update();
}, },
moveIssueToList (listFrom, listTo, issue) { moveIssueToList (listFrom, listTo, issue, newIndex) {
const issueTo = listTo.findIssue(issue.id), const issueTo = listTo.findIssue(issue.id),
issueLists = issue.getLists(), issueLists = issue.getLists(),
listLabels = issueLists.map( listIssue => listIssue.label ); listLabels = issueLists.map( listIssue => listIssue.label );
// Add to new lists issues if it doesn't already exist // Add to new lists issues if it doesn't already exist
if (!issueTo) { if (!issueTo) {
listTo.addIssue(issue, listFrom); listTo.addIssue(issue, listFrom, newIndex);
} }
if (listTo.type === 'done' && listFrom.type !== 'backlog') { if (listTo.type === 'done' && listFrom.type !== 'backlog') {
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, consistent-return, no-undef, no-return-assign, no-param-reassign, one-var, no-var, one-var-declaration-per-line, no-unused-vars, prefer-template, object-shorthand, comma-dangle, padded-blocks, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, consistent-return, no-undef, no-return-assign, no-param-reassign, one-var, no-var, one-var-declaration-per-line, no-unused-vars, prefer-template, object-shorthand, comma-dangle, padded-blocks, max-len, prefer-arrow-callback */
(function() { (function() {
this.CommitsList = (function() { this.CommitsList = (function() {
function CommitsList() {} function CommitsList() {}
...@@ -13,7 +13,9 @@ ...@@ -13,7 +13,9 @@
return false; return false;
} }
}); });
Pager.init(limit, false); Pager.init(limit, false, false, function() {
gl.utils.localTimeAgo($('.js-timeago'));
});
this.content = $("#commits-list"); this.content = $("#commits-list");
this.searchField = $("#commits-search"); this.searchField = $("#commits-search");
return this.initSearch(); return this.initSearch();
......
...@@ -10,10 +10,15 @@ ...@@ -10,10 +10,15 @@
}, },
template: ` template: `
<span class="total-time"> <span class="total-time">
<template v-if="Object.keys(time).length">
<template v-if="time.days">{{ time.days }} <span>{{ time.days === 1 ? 'day' : 'days' }}</span></template> <template v-if="time.days">{{ time.days }} <span>{{ time.days === 1 ? 'day' : 'days' }}</span></template>
<template v-if="time.hours">{{ time.hours }} <span>hr</span></template> <template v-if="time.hours">{{ time.hours }} <span>hr</span></template>
<template v-if="time.mins && !time.days">{{ time.mins }} <span>mins</span></template> <template v-if="time.mins && !time.days">{{ time.mins }} <span>mins</span></template>
<template v-if="time.seconds && Object.keys(time).length === 1 || time.seconds === 0">{{ time.seconds }} <span>s</span></template> <template v-if="time.seconds && Object.keys(time).length === 1 || time.seconds === 0">{{ time.seconds }} <span>s</span></template>
</template>
<template v-else>
--
</template>
</span> </span>
`, `,
}); });
......
...@@ -62,9 +62,11 @@ ...@@ -62,9 +62,11 @@
this.state.events = this.decorateEvents(events); this.state.events = this.decorateEvents(events);
}, },
decorateEvents(events) { decorateEvents(events) {
const newEvents = events; const newEvents = [];
events.forEach((item) => {
if (!item) return;
newEvents.forEach((item) => {
item.totalTime = item.total_time; item.totalTime = item.total_time;
item.author.webUrl = item.author.web_url; item.author.webUrl = item.author.web_url;
item.author.avatarUrl = item.author.avatar_url; item.author.avatarUrl = item.author.avatar_url;
...@@ -79,6 +81,8 @@ ...@@ -79,6 +81,8 @@
delete item.created_at; delete item.created_at;
delete item.short_sha; delete item.short_sha;
delete item.commit_url; delete item.commit_url;
newEvents.push(item);
}); });
return newEvents; return newEvents;
......
...@@ -57,14 +57,17 @@ class DiscussionModel { ...@@ -57,14 +57,17 @@ class DiscussionModel {
} }
updateHeadline (data) { updateHeadline (data) {
const $discussionHeadline = $(`.discussion[data-discussion-id="${this.id}"] .js-discussion-headline`); const discussionSelector = `.discussion[data-discussion-id="${this.id}"]`;
const $discussionHeadline = $(`${discussionSelector} .js-discussion-headline`);
if (data.discussion_headline_html) { if (data.discussion_headline_html) {
if ($discussionHeadline.length) { if ($discussionHeadline.length) {
$discussionHeadline.replaceWith(data.discussion_headline_html); $discussionHeadline.replaceWith(data.discussion_headline_html);
} else { } else {
$(`.discussion[data-discussion-id="${this.id}"] .discussion-header`).append(data.discussion_headline_html); $(`${discussionSelector} .discussion-header`).append(data.discussion_headline_html);
} }
gl.utils.localTimeAgo($('.js-timeago', `${discussionSelector}`));
} else { } else {
$discussionHeadline.remove(); $discussionHeadline.remove();
} }
......
...@@ -136,8 +136,18 @@ ...@@ -136,8 +136,18 @@
new TreeView(); new TreeView();
} }
break; break;
case 'projects:pipelines:builds':
case 'projects:pipelines:show': case 'projects:pipelines:show':
new gl.Pipelines(); const { controllerAction } = document.querySelector('.js-pipeline-container').dataset;
new gl.Pipelines({
initTabs: true,
tabsOptions: {
action: controllerAction,
defaultAction: 'pipelines',
parentEl: '.pipelines-tabs',
},
});
break; break;
case 'groups:activity': case 'groups:activity':
new gl.Activities(); new gl.Activities();
...@@ -216,6 +226,9 @@ ...@@ -216,6 +226,9 @@
new gl.ProtectedBranchCreate(); new gl.ProtectedBranchCreate();
new gl.ProtectedBranchEditList(); new gl.ProtectedBranchEditList();
break; break;
case 'projects:variables:index':
new gl.ProjectVariables();
break;
} }
switch (path.first()) { switch (path.first()) {
case 'admin': case 'admin':
...@@ -270,7 +283,7 @@ ...@@ -270,7 +283,7 @@
new NotificationsDropdown(); new NotificationsDropdown();
break; break;
case 'wikis': case 'wikis':
new Wikis(); new gl.Wikis();
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
new ZenMode(); new ZenMode();
new GLForm($('.wiki-form')); new GLForm($('.wiki-form'));
......
...@@ -181,7 +181,7 @@ ...@@ -181,7 +181,7 @@
<div class="environments-container"> <div class="environments-container">
<div class="environments-list-loading text-center" v-if="isLoading"> <div class="environments-list-loading text-center" v-if="isLoading">
<i class="fa fa-spinner spin"></i> <i class="fa fa-spinner fa-spin"></i>
</div> </div>
<div class="blank-state blank-state-no-icon" <div class="blank-state blank-state-no-icon"
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.environmentsList = window.gl.environmentsList || {}; window.gl.environmentsList = window.gl.environmentsList || {};
window.gl.environmentsList.timeagoInstance = new timeago(); // eslint-disable-line
gl.environmentsList.EnvironmentItem = Vue.component('environment-item', { gl.environmentsList.EnvironmentItem = Vue.component('environment-item', {
...@@ -147,15 +148,26 @@ ...@@ -147,15 +148,26 @@
this.model.last_deployment.deployable; this.model.last_deployment.deployable;
}, },
/**
* Verifies if the date to be shown is present.
*
* @returns {Boolean|Undefined}
*/
canShowDate() {
return this.model.last_deployment &&
this.model.last_deployment.deployable &&
this.model.last_deployment.deployable !== undefined;
},
/** /**
* Human readable date. * Human readable date.
* *
* @returns {String} * @returns {String}
*/ */
createdDate() { createdDate() {
const timeagoInstance = new timeago(); // eslint-disable-line return window.gl.environmentsList.timeagoInstance.format(
this.model.last_deployment.deployable.created_at,
return timeagoInstance.format(this.model.created_at); );
}, },
/** /**
...@@ -453,7 +465,7 @@ ...@@ -453,7 +465,7 @@
<td> <td>
<span <span
v-if="!isFolder && model.last_deployment" v-if="!isFolder && canShowDate"
class="environment-created-date-timeago"> class="environment-created-date-timeago">
{{createdDate}} {{createdDate}}
</span> </span>
......
...@@ -6,3 +6,19 @@ Array.prototype.first = function() { ...@@ -6,3 +6,19 @@ Array.prototype.first = function() {
Array.prototype.last = function() { Array.prototype.last = function() {
return this[this.length-1]; return this[this.length-1];
} }
Array.prototype.find = Array.prototype.find || function(predicate, ...args) {
if (!this) throw new TypeError('Array.prototype.find called on null or undefined');
if (typeof predicate !== 'function') throw new TypeError('predicate must be a function');
const list = Object(this);
const thisArg = args[1];
let value = {};
for (let i = 0; i < list.length; i += 1) {
value = list[i];
if (predicate.call(thisArg, value, i, list)) return value;
}
return undefined;
};
/* global Element */ /* global Element */
/* eslint-disable consistent-return, max-len */ /* eslint-disable consistent-return, max-len, no-empty, no-plusplus, func-names */
Element.prototype.matches = Element.prototype.matches || Element.prototype.msMatchesSelector;
Element.prototype.closest = Element.prototype.closest || function closest(selector, selectedElement = this) { Element.prototype.closest = Element.prototype.closest || function closest(selector, selectedElement = this) {
if (!selectedElement) return; if (!selectedElement) return;
return selectedElement.matches(selector) ? selectedElement : Element.prototype.closest(selector, selectedElement.parentElement); return selectedElement.matches(selector) ? selectedElement : Element.prototype.closest(selector, selectedElement.parentElement);
}; };
Element.prototype.matches = Element.prototype.matches ||
Element.prototype.matchesSelector ||
Element.prototype.mozMatchesSelector ||
Element.prototype.msMatchesSelector ||
Element.prototype.oMatchesSelector ||
Element.prototype.webkitMatchesSelector ||
function (s) {
const matches = (this.document || this.ownerDocument).querySelectorAll(s);
let i = matches.length;
while (--i >= 0 && matches.item(i) !== this) {}
return i > -1;
};
...@@ -53,6 +53,27 @@ ...@@ -53,6 +53,27 @@
} else { } else {
return value; return value;
} }
},
matcher: function (flag, subtext) {
// The below is taken from At.js source
// Tweaked to commands to start without a space only if char before is a non-word character
// https://github.com/ichord/At.js
var _a, _y, regexp, match;
subtext = subtext.split(' ').pop();
flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
_a = decodeURI("%C3%80");
_y = decodeURI("%C3%BF");
regexp = new RegExp("(?:\\B|\\W|\\s)" + flag + "(?!\\W)([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]*)|([^\\x00-\\xff]*)$", 'gi');
match = regexp.exec(subtext);
if (match) {
return match[2] || match[1];
} else {
return null;
}
} }
}, },
setup: _.debounce(function(input) { setup: _.debounce(function(input) {
...@@ -91,10 +112,12 @@ ...@@ -91,10 +112,12 @@
})(this), })(this),
insertTpl: ':${name}:', insertTpl: ':${name}:',
data: ['loading'], data: ['loading'],
startWithSpace: false,
callbacks: { callbacks: {
sorter: this.DefaultOptions.sorter, sorter: this.DefaultOptions.sorter,
filter: this.DefaultOptions.filter, filter: this.DefaultOptions.filter,
beforeInsert: this.DefaultOptions.beforeInsert beforeInsert: this.DefaultOptions.beforeInsert,
matcher: this.DefaultOptions.matcher
} }
}); });
// Team Members // Team Members
...@@ -112,11 +135,13 @@ ...@@ -112,11 +135,13 @@
insertTpl: '${atwho-at}${username}', insertTpl: '${atwho-at}${username}',
searchKey: 'search', searchKey: 'search',
data: ['loading'], data: ['loading'],
startWithSpace: false,
alwaysHighlightFirst: true, alwaysHighlightFirst: true,
callbacks: { callbacks: {
sorter: this.DefaultOptions.sorter, sorter: this.DefaultOptions.sorter,
filter: this.DefaultOptions.filter, filter: this.DefaultOptions.filter,
beforeInsert: this.DefaultOptions.beforeInsert, beforeInsert: this.DefaultOptions.beforeInsert,
matcher: this.DefaultOptions.matcher,
beforeSave: function(members) { beforeSave: function(members) {
return $.map(members, function(m) { return $.map(members, function(m) {
let title = ''; let title = '';
...@@ -157,10 +182,12 @@ ...@@ -157,10 +182,12 @@
})(this), })(this),
data: ['loading'], data: ['loading'],
insertTpl: '${atwho-at}${id}', insertTpl: '${atwho-at}${id}',
startWithSpace: false,
callbacks: { callbacks: {
sorter: this.DefaultOptions.sorter, sorter: this.DefaultOptions.sorter,
filter: this.DefaultOptions.filter, filter: this.DefaultOptions.filter,
beforeInsert: this.DefaultOptions.beforeInsert, beforeInsert: this.DefaultOptions.beforeInsert,
matcher: this.DefaultOptions.matcher,
beforeSave: function(issues) { beforeSave: function(issues) {
return $.map(issues, function(i) { return $.map(issues, function(i) {
if (i.title == null) { if (i.title == null) {
...@@ -190,7 +217,9 @@ ...@@ -190,7 +217,9 @@
})(this), })(this),
insertTpl: '${atwho-at}"${title}"', insertTpl: '${atwho-at}"${title}"',
data: ['loading'], data: ['loading'],
startWithSpace: false,
callbacks: { callbacks: {
matcher: this.DefaultOptions.matcher,
sorter: this.DefaultOptions.sorter, sorter: this.DefaultOptions.sorter,
beforeSave: function(milestones) { beforeSave: function(milestones) {
return $.map(milestones, function(m) { return $.map(milestones, function(m) {
...@@ -220,11 +249,13 @@ ...@@ -220,11 +249,13 @@
}; };
})(this), })(this),
data: ['loading'], data: ['loading'],
startWithSpace: false,
insertTpl: '${atwho-at}${id}', insertTpl: '${atwho-at}${id}',
callbacks: { callbacks: {
sorter: this.DefaultOptions.sorter, sorter: this.DefaultOptions.sorter,
filter: this.DefaultOptions.filter, filter: this.DefaultOptions.filter,
beforeInsert: this.DefaultOptions.beforeInsert, beforeInsert: this.DefaultOptions.beforeInsert,
matcher: this.DefaultOptions.matcher,
beforeSave: function(merges) { beforeSave: function(merges) {
return $.map(merges, function(m) { return $.map(merges, function(m) {
if (m.title == null) { if (m.title == null) {
...@@ -245,7 +276,9 @@ ...@@ -245,7 +276,9 @@
searchKey: 'search', searchKey: 'search',
displayTpl: this.Labels.template, displayTpl: this.Labels.template,
insertTpl: '${atwho-at}${title}', insertTpl: '${atwho-at}${title}',
startWithSpace: false,
callbacks: { callbacks: {
matcher: this.DefaultOptions.matcher,
sorter: this.DefaultOptions.sorter, sorter: this.DefaultOptions.sorter,
beforeSave: function(merges) { beforeSave: function(merges) {
var sanitizeLabelTitle; var sanitizeLabelTitle;
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, no-unused-vars, one-var-declaration-per-line, prefer-template, no-new, consistent-return, object-shorthand, comma-dangle, no-shadow, no-param-reassign, brace-style, vars-on-top, quotes, no-lonely-if, no-else-return, no-undef, semi, dot-notation, no-empty, no-return-assign, camelcase, prefer-spread, padded-blocks, max-len */ /* eslint-disable no-useless-return, func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, no-unused-vars, one-var-declaration-per-line, prefer-template, no-new, consistent-return, object-shorthand, comma-dangle, no-shadow, no-param-reassign, brace-style, vars-on-top, quotes, no-lonely-if, no-else-return, no-undef, semi, dot-notation, no-empty, no-return-assign, camelcase, prefer-spread, padded-blocks, max-len */
(function() { (function() {
this.LabelsSelect = (function() { this.LabelsSelect = (function() {
function LabelsSelect() { function LabelsSelect() {
......
/**
* Linked Tabs
*
* Handles persisting and restores the current tab selection and content.
* Reusable component for static content.
*
* ### Example Markup
*
* <ul class="nav-links tab-links">
* <li class="active">
* <a data-action="tab1" data-target="#tab1" data-toggle="tab" href="/path/tab1">
* Tab 1
* </a>
* </li>
* <li class="groups-tab">
* <a data-action="tab2" data-target="#tab2" data-toggle="tab" href="/path/tab2">
* Tab 2
* </a>
* </li>
*
*
* <div class="tab-content">
* <div class="tab-pane" id="tab1">
* Tab 1 Content
* </div>
* <div class="tab-pane" id="tab2">
* Tab 2 Content
* </div>
* </div>
*
*
* ### How to use
*
* new window.gl.LinkedTabs({
* action: "#{controller.action_name}",
* defaultAction: 'tab1',
* parentEl: '.tab-links'
* });
*/
(() => {
window.gl = window.gl || {};
window.gl.LinkedTabs = class LinkedTabs {
/**
* Binds the events and activates de default tab.
*
* @param {Object} options
*/
constructor(options) {
this.options = options || {};
this.defaultAction = this.options.defaultAction;
this.action = this.options.action || this.defaultAction;
if (this.action === 'show') {
this.action = this.defaultAction;
}
this.currentLocation = window.location;
const tabSelector = `${this.options.parentEl} a[data-toggle="tab"]`;
// since this is a custom event we need jQuery :(
$(document)
.off('shown.bs.tab', tabSelector)
.on('shown.bs.tab', tabSelector, e => this.tabShown(e));
this.activateTab(this.action);
}
/**
* Handles the `shown.bs.tab` event to set the currect url action.
*
* @param {type} evt
* @return {Function}
*/
tabShown(evt) {
const source = evt.target.getAttribute('href');
return this.setCurrentAction(source);
}
/**
* Updates the URL with the path that matched the given action.
*
* @param {String} source
* @return {String}
*/
setCurrentAction(source) {
const copySource = source;
copySource.replace(/\/+$/, '');
const newState = `${copySource}${this.currentLocation.search}${this.currentLocation.hash}`;
history.replaceState({
turbolinks: true,
url: newState,
}, document.title, newState);
return newState;
}
/**
* Given the current action activates the correct tab.
* http://getbootstrap.com/javascript/#tab-show
* Note: Will trigger `shown.bs.tab`
*/
activateTab() {
return $(`${this.options.parentEl} a[data-action='${this.action}']`).tab('show');
}
};
})();
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-param-reassign, no-else-return, quotes, object-shorthand, comma-dangle, camelcase, one-var, vars-on-top, one-var-declaration-per-line, no-return-assign, consistent-return, padded-blocks, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-param-reassign, no-else-return, quotes, object-shorthand, comma-dangle, camelcase, one-var, vars-on-top, one-var-declaration-per-line, no-return-assign, consistent-return, padded-blocks, max-len, prefer-template */
(function() { (function() {
(function(w) { (function(w) {
var base; var base;
...@@ -94,10 +94,35 @@ ...@@ -94,10 +94,35 @@
return $(document).off('scroll'); return $(document).off('scroll');
}; };
w.gl.utils.shiftWindow = function() { // automatically adjust scroll position for hash urls taking the height of the navbar into account
return w.scrollBy(0, -100); // https://github.com/twitter/bootstrap/issues/1768
}; w.gl.utils.handleLocationHash = function() {
var hash = w.gl.utils.getLocationHash();
if (!hash) return;
var navbar = document.querySelector('.navbar-gitlab');
var subnav = document.querySelector('.layout-nav');
var fixedTabs = document.querySelector('.js-tabs-affix');
var adjustment = 0;
if (navbar) adjustment -= navbar.offsetHeight;
if (subnav) adjustment -= subnav.offsetHeight;
// scroll to user-generated markdown anchor if we cannot find a match
if (document.getElementById(hash) === null) {
var 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;
}
window.scrollBy(0, adjustment);
}
};
gl.utils.updateTooltipTitle = function($tooltipEl, newTitle) { gl.utils.updateTooltipTitle = function($tooltipEl, newTitle) {
return $tooltipEl.tooltip('destroy').attr('title', newTitle).tooltip('fixTitle'); return $tooltipEl.tooltip('destroy').attr('title', newTitle).tooltip('fixTitle');
......
/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, camelcase, no-unused-expressions, quotes, max-len, one-var, one-var-declaration-per-line, default-case, prefer-template, no-undef, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, brace-style, no-lonely-if, vars-on-top, no-unused-vars, semi, indent, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape, radix, padded-blocks, max-len */ /* eslint-disable no-restricted-properties, func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, camelcase, no-unused-expressions, quotes, max-len, one-var, one-var-declaration-per-line, default-case, prefer-template, no-undef, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, brace-style, no-lonely-if, vars-on-top, no-unused-vars, semi, indent, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape, radix, padded-blocks, max-len */
/*= require autosave */ /*= require autosave */
/*= require autosize */ /*= require autosize */
...@@ -333,7 +333,7 @@ ...@@ -333,7 +333,7 @@
gl.diffNotesCompileComponents(); gl.diffNotesCompileComponents();
} }
gl.utils.localTimeAgo($('.js-timeago', note_html), false); gl.utils.localTimeAgo($('.js-timeago'), false);
return this.updateNotesCount(1); return this.updateNotesCount(1);
}; };
......
//= require lib/utils/bootstrap_linked_tabs
/* eslint-disable */ /* eslint-disable */
((global) => { ((global) => {
class Pipelines { class Pipelines {
constructor() { constructor(options) {
if (options.initTabs && options.tabsOptions) {
new global.LinkedTabs(options.tabsOptions);
}
this.addMarginToBuildColumns(); this.addMarginToBuildColumns();
} }
......
(() => {
const HIDDEN_VALUE_TEXT = '******';
class ProjectVariables {
constructor() {
this.$revealBtn = $('.js-btn-toggle-reveal-values');
this.$revealBtn.on('click', this.toggleRevealState.bind(this));
}
toggleRevealState(e) {
e.preventDefault();
const oldStatus = this.$revealBtn.attr('data-status');
let newStatus = 'hidden';
let newAction = 'Reveal Values';
if (oldStatus === 'hidden') {
newStatus = 'revealed';
newAction = 'Hide Values';
}
this.$revealBtn.attr('data-status', newStatus);
const $variables = $('.variable-value');
$variables.each((_, variable) => {
const $variable = $(variable);
let newText = HIDDEN_VALUE_TEXT;
if (newStatus === 'revealed') {
newText = $variable.attr('data-value');
}
$variable.text(newText);
});
this.$revealBtn.text(newAction);
}
}
window.gl = window.gl || {};
window.gl.ProjectVariables = ProjectVariables;
})();
/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, vars-on-top, no-unused-vars, one-var, one-var-declaration-per-line, camelcase, consistent-return, no-undef, padded-blocks, max-len */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
this.Subscription = (function() {
function Subscription(container) {
this.toggleSubscription = bind(this.toggleSubscription, this);
var $container;
this.$container = $(container);
this.url = this.$container.attr('data-url');
this.subscribe_button = this.$container.find('.js-subscribe-button');
this.subscription_status = this.$container.find('.subscription-status');
this.subscribe_button.unbind('click').click(this.toggleSubscription);
}
Subscription.prototype.toggleSubscription = function(event) {
var action, btn, current_status;
btn = $(event.currentTarget);
action = btn.find('span').text();
current_status = this.subscription_status.attr('data-status');
btn.addClass('disabled');
if ($('html').hasClass('issue-boards-page')) {
this.url = this.$container.attr('data-url');
}
return $.post(this.url, (function(_this) {
return function() {
var status;
btn.removeClass('disabled');
if ($('html').hasClass('issue-boards-page')) {
Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'subscribed', !gl.issueBoards.BoardsStore.detail.issue.subscribed);
} else {
status = current_status === 'subscribed' ? 'unsubscribed' : 'subscribed';
_this.subscription_status.attr('data-status', status);
action = status === 'subscribed' ? 'Unsubscribe' : 'Subscribe';
btn.find('span').text(action);
_this.subscription_status.find('>div').toggleClass('hidden');
if (btn.attr('data-original-title')) {
return btn.tooltip('hide').attr('data-original-title', action).tooltip('fixTitle');
}
}
};
})(this));
};
return Subscription;
})();
}).call(this);
/* global Vue */
(() => {
class Subscription {
constructor(containerElm) {
this.containerElm = containerElm;
const subscribeButton = containerElm.querySelector('.js-subscribe-button');
if (subscribeButton) {
// remove class so we don't bind twice
subscribeButton.classList.remove('js-subscribe-button');
subscribeButton.addEventListener('click', this.toggleSubscription.bind(this));
}
}
toggleSubscription(event) {
const button = event.currentTarget;
const buttonSpan = button.querySelector('span');
if (!buttonSpan || button.classList.contains('disabled')) {
return;
}
button.classList.add('disabled');
const isSubscribed = buttonSpan.innerHTML.trim().toLowerCase() !== 'subscribe';
const toggleActionUrl = this.containerElm.dataset.url;
$.post(toggleActionUrl, () => {
button.classList.remove('disabled');
// hack to allow this to work with the issue boards Vue object
if (document.querySelector('html').classList.contains('issue-boards-page')) {
Vue.set(
gl.issueBoards.BoardsStore.detail.issue,
'subscribed',
!gl.issueBoards.BoardsStore.detail.issue.subscribed,
);
} else {
buttonSpan.innerHTML = isSubscribed ? 'Subscribe' : 'Unsubscribe';
}
});
}
static bindAll(selector) {
[].forEach.call(document.querySelectorAll(selector), elm => new Subscription(elm));
}
}
window.gl = window.gl || {};
window.gl.Subscription = Subscription;
})();
/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, consistent-return, one-var, one-var-declaration-per-line, no-undef, prefer-template, padded-blocks, max-len */
/*= require latinise */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
this.Wikis = (function() {
function Wikis() {
this.slugify = bind(this.slugify, this);
$('.new-wiki-page').on('submit', (function(_this) {
return function(e) {
var field, path, slug;
$('[data-error~=slug]').addClass('hidden');
field = $('#new_wiki_path');
slug = _this.slugify(field.val());
if (slug.length > 0) {
path = field.attr('data-wikis-path');
location.href = path + '/' + slug;
return e.preventDefault();
}
};
})(this));
}
Wikis.prototype.dasherize = function(value) {
return value.replace(/[_\s]+/g, '-');
};
Wikis.prototype.slugify = function(value) {
return this.dasherize(value.trim().toLowerCase().latinise());
};
return Wikis;
})();
}).call(this);
/* eslint-disable no-param-reassign */
/* global Breakpoints */
/*= require latinise */
/*= require breakpoints */
/*= require jquery.nicescroll */
((global) => {
const dasherize = str => str.replace(/[_\s]+/g, '-');
const slugify = str => dasherize(str.trim().toLowerCase().latinise());
class Wikis {
constructor() {
this.bp = Breakpoints.get();
this.sidebarEl = document.querySelector('.js-wiki-sidebar');
this.sidebarExpanded = false;
$(this.sidebarEl).niceScroll();
const sidebarToggles = document.querySelectorAll('.js-sidebar-wiki-toggle');
for (let i = 0; i < sidebarToggles.length; i += 1) {
sidebarToggles[i].addEventListener('click', e => this.handleToggleSidebar(e));
}
this.newWikiForm = document.querySelector('form.new-wiki-page');
if (this.newWikiForm) {
this.newWikiForm.addEventListener('submit', e => this.handleNewWikiSubmit(e));
}
window.addEventListener('resize', () => this.renderSidebar());
this.renderSidebar();
}
handleNewWikiSubmit(e) {
if (!this.newWikiForm) return;
const slugInput = this.newWikiForm.querySelector('#new_wiki_path');
const slug = slugify(slugInput.value);
if (slug.length > 0) {
const wikisPath = slugInput.getAttribute('data-wikis-path');
window.location.href = `${wikisPath}/${slug}`;
e.preventDefault();
}
}
handleToggleSidebar(e) {
e.preventDefault();
this.sidebarExpanded = !this.sidebarExpanded;
this.renderSidebar();
}
sidebarCanCollapse() {
const bootstrapBreakpoint = this.bp.getBreakpointSize();
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
}
renderSidebar() {
if (!this.sidebarEl) return;
const { classList } = this.sidebarEl;
if (this.sidebarExpanded || !this.sidebarCanCollapse()) {
if (!classList.contains('right-sidebar-expanded')) {
classList.remove('right-sidebar-collapsed');
classList.add('right-sidebar-expanded');
}
} else if (classList.contains('right-sidebar-expanded')) {
classList.add('right-sidebar-collapsed');
classList.remove('right-sidebar-expanded');
}
}
}
global.Wikis = Wikis;
})(window.gl || (window.gl = {}));
...@@ -80,6 +80,7 @@ ...@@ -80,6 +80,7 @@
border-radius: 0; border-radius: 0;
border: none; border: none;
height: auto; height: auto;
width: 100%;
margin: 0; margin: 0;
align-self: center; align-self: center;
} }
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
@include btn-default; @include btn-default;
} }
@mixin btn-outline($background, $text, $border, $hover-background, $hover-text, $hover-border) { @mixin btn-outline($background, $text, $border, $hover-background, $hover-text, $hover-border, $active-background, $active-border) {
background-color: $background; background-color: $background;
color: $text; color: $text;
border-color: $border; border-color: $border;
...@@ -23,8 +23,14 @@ ...@@ -23,8 +23,14 @@
&:hover, &:hover,
&:focus { &:focus {
background-color: $hover-background; background-color: $hover-background;
color: $hover-text;
border-color: $hover-border; border-color: $hover-border;
color: $hover-text;
}
&:active {
background-color: $active-background;
border-color: $active-border;
color: $hover-text;
} }
} }
...@@ -82,11 +88,11 @@ ...@@ -82,11 +88,11 @@
} }
@mixin btn-gray { @mixin btn-gray {
@include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-light, $gray-dark, $border-gray-dark, $gl-gray-dark); @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-normal, $gray-dark, $border-gray-dark, $gl-gray-dark);
} }
@mixin btn-white { @mixin btn-white {
@include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-white-dark, $btn-white-active); @include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-white-dark, $gl-text-color);
} }
@mixin btn-with-margin { @mixin btn-with-margin {
...@@ -139,11 +145,11 @@ ...@@ -139,11 +145,11 @@
&.btn-new, &.btn-new,
&.btn-create, &.btn-create,
&.btn-save { &.btn-save {
@include btn-outline($white-light, $green-normal, $green-normal, $green-light, $white-light, $green-light); @include btn-outline($white-light, $border-green-light, $border-green-light, $green-light, $white-light, $border-green-light, $green-normal, $border-green-normal);
} }
&.btn-remove { &.btn-remove {
@include btn-outline($white-light, $red-normal, $red-normal, $red-light, $white-light, $red-light); @include btn-outline($white-light, $border-red-light, $border-red-light, $red-light, $white-light, $border-red-light, $red-normal, $border-red-normal);
} }
} }
...@@ -165,11 +171,11 @@ ...@@ -165,11 +171,11 @@
} }
&.btn-close { &.btn-close {
@include btn-outline($white-light, $orange-normal, $orange-normal, $orange-light, $white-light, $orange-light); @include btn-outline($white-light, $border-orange-light, $border-orange-light, $orange-light, $white-light, $border-orange-light, $orange-normal, $border-orange-normal);
} }
&.btn-spam { &.btn-spam {
@include btn-outline($white-light, $red-normal, $red-normal, $red-light, $white-light, $red-light); @include btn-outline($white-light, $border-red-light, $border-red-light, $red-light, $white-light, $border-red-light, $red-normal, $border-red-normal);
} }
&.btn-danger, &.btn-danger,
...@@ -199,7 +205,7 @@ ...@@ -199,7 +205,7 @@
} }
.fa-caret-down, .fa-caret-down,
.fa-caret-up { .fa-chevron-down {
margin-left: 5px; margin-left: 5px;
} }
...@@ -351,7 +357,7 @@ ...@@ -351,7 +357,7 @@
.btn-inverted { .btn-inverted {
&-secondary { &-secondary {
@include btn-outline($white-light, $blue-normal, $blue-normal, $blue-light, $white-light, $blue-light); @include btn-outline($white-light, $border-blue-light, $border-blue-light, $blue-light, $white-light, $border-blue-light, $blue-normal, $border-blue-normal);
} }
} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
padding-left: 0; padding-left: 0;
padding-right: 0; padding-right: 0;
@media (min-width: $screen-sm-min) and (max-width: $screen-lg-min) { @media (min-width: $screen-sm-min) and (max-width: $screen-md-max) {
overflow-x: scroll; overflow-x: scroll;
} }
} }
......
...@@ -8,6 +8,12 @@ ...@@ -8,6 +8,12 @@
} }
} }
@mixin chevron-active {
.fa-chevron-down {
color: $dropdown-toggle-hover-icon-color;
}
}
.open { .open {
.dropdown-menu, .dropdown-menu,
.dropdown-menu-nav { .dropdown-menu-nav {
...@@ -19,53 +25,27 @@ ...@@ -19,53 +25,27 @@
} }
} }
.dropdown-toggle,
.dropdown-menu-toggle { .dropdown-menu-toggle {
@include chevron-active;
border-color: $dropdown-toggle-hover-border-color; border-color: $dropdown-toggle-hover-border-color;
.fa {
color: $dropdown-toggle-hover-icon-color;
}
} }
} }
.dropdown-menu-toggle { .dropdown-toggle {
position: relative; padding: 6px 8px 6px 10px;
width: 160px;
padding: 6px 20px 6px 10px;
background-color: $dropdown-toggle-bg; background-color: $dropdown-toggle-bg;
color: $dropdown-toggle-color; color: $dropdown-toggle-color;
font-size: 15px; font-size: 15px;
text-align: left; text-align: left;
border: 1px solid $border-color; border: 1px solid $border-color;
border-radius: $border-radius-base; border-radius: $border-radius-base;
text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
overflow: hidden;
.fa {
position: absolute;
top: 10px;
right: 8px;
color: $dropdown-toggle-icon-color;
&.fa-spinner {
font-size: 16px;
margin-top: -8px;
}
}
&.no-outline { &.no-outline {
outline: 0; outline: 0;
} }
&:hover, {
border-color: $dropdown-toggle-hover-border-color;
.fa {
color: $dropdown-toggle-hover-icon-color;
}
}
&.large { &.large {
width: 200px; width: 200px;
} }
...@@ -86,6 +66,51 @@ ...@@ -86,6 +66,51 @@
max-width: 100%; max-width: 100%;
padding-right: 25px; padding-right: 25px;
} }
.fa {
color: $dropdown-toggle-icon-color;
}
.fa-chevron-down {
font-size: $dropdown-chevron-size;
position: relative;
top: -3px;
margin-left: 5px;
}
&:hover {
@include chevron-active;
border-color: $dropdown-toggle-hover-border-color;
}
&:focus:active {
@include chevron-active;
border-color: $dropdown-toggle-active-border-color;
}
}
.dropdown-menu-toggle {
@extend .dropdown-toggle;
padding-right: 20px;
position: relative;
width: 160px;
text-overflow: ellipsis;
overflow: hidden;
.fa {
position: absolute;
&.fa-spinner {
font-size: 16px;
margin-top: -8px;
}
}
.fa-chevron-down {
position: absolute;
top: 11px;
right: 8px;
}
} }
.dropdown-menu, .dropdown-menu,
......
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
} }
} }
@media (max-width: $screen-md-min) { @media (max-width: $screen-sm-max) {
ul.notes { ul.notes {
.flash-container.timeline-content { .flash-container.timeline-content {
margin-left: 0; margin-left: 0;
......
...@@ -98,7 +98,7 @@ label { ...@@ -98,7 +98,7 @@ label {
} }
} }
@media(max-width: $screen-sm-min) { @media(max-width: $screen-xs-max) {
padding: 0 $gl-padding; padding: 0 $gl-padding;
.control-label, .control-label,
......
...@@ -21,7 +21,6 @@ ...@@ -21,7 +21,6 @@
background: $color-darker; background: $color-darker;
} }
.sidebar-header,
.sidebar-action-buttons { .sidebar-action-buttons {
color: $color-light; color: $color-light;
background-color: lighten($color-darker, 5%); background-color: lighten($color-darker, 5%);
......
...@@ -228,7 +228,7 @@ header { ...@@ -228,7 +228,7 @@ header {
} }
.page-sidebar-pinned.right-sidebar-expanded { .page-sidebar-pinned.right-sidebar-expanded {
@media (max-width: $screen-lg-min) { @media (max-width: $screen-md-max) {
.header-content .title { .header-content .title {
width: 300px; width: 300px;
} }
......
...@@ -56,16 +56,9 @@ ...@@ -56,16 +56,9 @@
.md-header { .md-header {
.nav-links { .nav-links {
.active {
a {
border-bottom-color: #000;
}
}
a { a {
padding-top: 0; padding-top: 0;
line-height: 19px; line-height: 19px;
border-bottom: 1px solid $border-color;
&.btn.btn-xs { &.btn.btn-xs {
padding: 2px 5px; padding: 2px 5px;
......
...@@ -51,19 +51,26 @@ ...@@ -51,19 +51,26 @@
margin-bottom: -1px; margin-bottom: -1px;
font-size: 15px; font-size: 15px;
line-height: 28px; line-height: 28px;
color: #959494; color: $note-toolbar-color;
border-bottom: 2px solid transparent; border-bottom: 2px solid transparent;
&:hover, &:hover,
&:active, &:active,
&:focus { &:focus {
text-decoration: none; text-decoration: none;
border-bottom: 2px solid $gray-darkest;
color: $black;
.badge {
color: $black;
}
} }
} }
&.active a { &.active a {
border-bottom: 2px solid $link-underline-blue; border-bottom: 2px solid $link-underline-blue;
color: $black; color: $black;
font-weight: 600;
} }
.badge { .badge {
...@@ -85,14 +92,20 @@ ...@@ -85,14 +92,20 @@
li { li {
&.active a {
border-bottom: none;
color: $link-underline-blue;
}
a { a {
margin: 0; margin: 0;
padding: 11px 10px 9px; padding: 11px 10px 9px;
}
&.active a { &:hover,
&:active,
&:focus {
border-bottom: none; border-bottom: none;
color: $link-underline-blue; }
} }
} }
} }
...@@ -110,7 +123,7 @@ ...@@ -110,7 +123,7 @@
line-height: 28px; line-height: 28px;
/* Small devices (phones, tablets, 768px and lower) */ /* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-sm-min) { @media (max-width: $screen-xs-max) {
width: 100%; width: 100%;
} }
} }
...@@ -310,37 +323,9 @@ ...@@ -310,37 +323,9 @@
height: 51px; height: 51px;
li { li {
a { a {
padding-top: 10px; padding-top: 10px;
} }
a,
i {
color: $layout-link-gray;
}
&.active {
a,
i {
color: $black;
}
svg {
path,
polygon {
fill: $black;
}
}
}
&:hover {
a,
i {
color: $black;
}
}
} }
} }
} }
......
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
/** /**
* Small screen pagination * Small screen pagination
*/ */
@media (max-width: $screen-xs) { @media (max-width: $screen-xs-min) {
.gl-pagination { .gl-pagination {
.pagination li a { .pagination li a {
padding: 6px 10px; padding: 6px 10px;
...@@ -62,7 +62,7 @@ ...@@ -62,7 +62,7 @@
/** /**
* Medium screen pagination * Medium screen pagination
*/ */
@media (min-width: $screen-xs) and (max-width: $screen-md-max) { @media (min-width: $screen-xs-min) and (max-width: $screen-md-max) {
.gl-pagination { .gl-pagination {
.page { .page {
display: none; display: none;
......
...@@ -59,11 +59,6 @@ ...@@ -59,11 +59,6 @@
padding: 0 !important; padding: 0 !important;
} }
.sidebar-header {
padding: 11px 22px 12px;
font-size: 20px;
}
li { li {
&.separate-item { &.separate-item {
padding-top: 10px; padding-top: 10px;
...@@ -220,7 +215,7 @@ header.header-sidebar-pinned { ...@@ -220,7 +215,7 @@ header.header-sidebar-pinned {
padding-right: 0; padding-right: 0;
@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
&:not(.build-sidebar) { &:not(.build-sidebar):not(.wiki-sidebar) {
padding-right: $sidebar_collapsed_width; padding-right: $sidebar_collapsed_width;
} }
} }
......
...@@ -182,6 +182,7 @@ ...@@ -182,6 +182,7 @@
left: -16px; left: -16px;
position: absolute; position: absolute;
text-decoration: none; text-decoration: none;
outline: none;
&::after { &::after {
content: image-url('icon_anchor.svg'); content: image-url('icon_anchor.svg');
......
...@@ -12,67 +12,71 @@ $sidebar-breakpoint: 1024px; ...@@ -12,67 +12,71 @@ $sidebar-breakpoint: 1024px;
/* /*
* Color schema * Color schema
*/ */
$darken-normal-factor: 7%;
$darken-dark-factor: 10%;
$darken-border-factor: 5%;
$white-light: #fff; $white-light: #fff;
$white-normal: #ededed; $white-normal: darken($white-light, $darken-normal-factor);
$white-dark: #ececec; $white-dark: darken($white-light, $darken-dark-factor);
$gray-lightest: #fdfdfd; $gray-lightest: #fdfdfd;
$gray-light: #fafafa; $gray-light: #fafafa;
$gray-lighter: #f9f9f9; $gray-lighter: #f9f9f9;
$gray-normal: #f5f5f5; $gray-normal: darken($gray-light, $darken-normal-factor);
$gray-dark: #ededed; $gray-dark: darken($gray-light, $darken-dark-factor);
$gray-darker: #eee; $gray-darker: #eee;
$gray-darkest: #c9c9c9; $gray-darkest: #c9c9c9;
$green-light: #38ae67; $green-light: #3cbd70;
$green-normal: #2faa60; $green-normal: darken($green-light, $darken-normal-factor);
$green-dark: #2ca05b; $green-dark: darken($green-light, $darken-dark-factor);
$blue-light: #2ea8e5; $blue-light: #2ea8e5;
$blue-normal: #2d9fd8; $blue-normal: darken($blue-light, $darken-normal-factor);
$blue-dark: #2897ce; $blue-dark: darken($blue-light, $darken-dark-factor);
$blue-medium-light: #3498cb; $blue-medium-light: #3498cb;
$blue-medium: #2f8ebf; $blue-medium: darken($blue-medium-light, $darken-normal-factor);
$blue-medium-dark: #2d86b4; $blue-medium-dark: darken($blue-medium-light, $darken-dark-factor);
$blue-light-transparent: rgba(44, 159, 216, 0.05); $blue-light-transparent: rgba(44, 159, 216, 0.05);
$orange-light: #fc8a51; $orange-light: #fc8a51;
$orange-normal: #e75e40; $orange-normal: darken($orange-light, $darken-normal-factor);
$orange-dark: #ce5237; $orange-dark: darken($orange-light, $darken-dark-factor);
$red-light: #e52c5a; $red-light: #e52c5a;
$red-normal: #d22852; $red-normal: darken($red-light, $darken-normal-factor);
$red-dark: darken($red-normal, 5%); $red-dark: darken($red-light, $darken-dark-factor);
$black: #000; $black: #000;
$black-transparent: rgba(0, 0, 0, 0.3); $black-transparent: rgba(0, 0, 0, 0.3);
$border-white-light: #f1f2f4; $border-white-light: darken($white-light, $darken-border-factor);
$border-white-normal: #d6dae2; $border-white-normal: darken($white-normal, $darken-border-factor);
$border-white-dark: #c6cacf; $border-white-dark: darken($white-dark, $darken-border-factor);
$border-gray-light: #dcdcdc; $border-gray-light: darken($gray-light, $darken-border-factor);
$border-gray-normal: #d7d7d7; $border-gray-normal: darken($gray-normal, $darken-border-factor);
$border-gray-dark: #c6cacf; $border-gray-dark: darken($gray-dark, $darken-border-factor);
$border-green-extra-light: #9adb84; $border-green-extra-light: #9adb84;
$border-green-light: #2faa60; $border-green-light: darken($green-light, $darken-border-factor);
$border-green-normal: #2ca05b; $border-green-normal: darken($green-normal, $darken-border-factor);
$border-green-dark: #279654; $border-green-dark: darken($green-dark, $darken-border-factor);
$border-blue-light: #2d9fd8; $border-blue-light: darken($blue-light, $darken-border-factor);
$border-blue-normal: #2897ce; $border-blue-normal: darken($blue-normal, $darken-border-factor);
$border-blue-dark: #258dc1; $border-blue-dark: darken($blue-dark, $darken-border-factor);
$border-orange-light: #fc6d26; $border-orange-light: darken($orange-light, $darken-border-factor);
$border-orange-normal: #ce5237; $border-orange-normal: darken($orange-normal, $darken-border-factor);
$border-orange-dark: #c14e35; $border-orange-dark: darken($orange-dark, $darken-border-factor);
$border-red-light: #d22852; $border-red-light: darken($red-light, $darken-border-factor);
$border-red-normal: #ca264f; $border-red-normal: darken($red-normal, $darken-border-factor);
$border-red-dark: darken($border-red-normal, 5%); $border-red-dark: darken($red-dark, $darken-border-factor);
$help-well-bg: $gray-light; $help-well-bg: $gray-light;
$help-well-border: #e5e5e5; $help-well-border: #e5e5e5;
...@@ -217,7 +221,7 @@ $dropdown-bg: #fff; ...@@ -217,7 +221,7 @@ $dropdown-bg: #fff;
$dropdown-link-color: #555; $dropdown-link-color: #555;
$dropdown-link-hover-bg: $row-hover; $dropdown-link-hover-bg: $row-hover;
$dropdown-empty-row-bg: rgba(#000, .04); $dropdown-empty-row-bg: rgba(#000, .04);
$dropdown-border-color: rgba(#000, .1); $dropdown-border-color: $border-color;
$dropdown-shadow-color: rgba(#000, .1); $dropdown-shadow-color: rgba(#000, .1);
$dropdown-divider-color: rgba(#000, .1); $dropdown-divider-color: rgba(#000, .1);
$dropdown-header-color: #959494; $dropdown-header-color: #959494;
...@@ -226,13 +230,15 @@ $dropdown-input-color: #555; ...@@ -226,13 +230,15 @@ $dropdown-input-color: #555;
$dropdown-input-focus-border: $focus-border-color; $dropdown-input-focus-border: $focus-border-color;
$dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, .4); $dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, .4);
$dropdown-loading-bg: rgba(#fff, .6); $dropdown-loading-bg: rgba(#fff, .6);
$dropdown-chevron-size: 10px;
$dropdown-toggle-bg: #fff; $dropdown-toggle-bg: #fff;
$dropdown-toggle-color: #626262; $dropdown-toggle-color: #5c5c5c;
$dropdown-toggle-border-color: #eaeaea; $dropdown-toggle-border-color: #e5e5e5;
$dropdown-toggle-hover-border-color: darken($dropdown-toggle-border-color, 15%); $dropdown-toggle-hover-border-color: darken($dropdown-toggle-border-color, 13%);
$dropdown-toggle-active-border-color: darken($dropdown-toggle-border-color, 14%);
$dropdown-toggle-icon-color: #c4c4c4; $dropdown-toggle-icon-color: #c4c4c4;
$dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color; $dropdown-toggle-hover-icon-color: darken($dropdown-toggle-icon-color, 7%);
/* /*
* Buttons * Buttons
...@@ -256,7 +262,7 @@ $search-input-border-color: rgba(#4688f1, .8); ...@@ -256,7 +262,7 @@ $search-input-border-color: rgba(#4688f1, .8);
$search-input-focus-shadow-color: $dropdown-input-focus-shadow; $search-input-focus-shadow-color: $dropdown-input-focus-shadow;
$search-input-width: 220px; $search-input-width: 220px;
$location-badge-color: #aaa; $location-badge-color: #aaa;
$location-badge-bg: $gray-normal; $location-badge-bg: $dark-background-color;
$location-badge-active-bg: #4f91f8; $location-badge-active-bg: #4f91f8;
$location-icon-color: #e7e9ed; $location-icon-color: #e7e9ed;
$location-icon-active-color: #807e7e; $location-icon-active-color: #807e7e;
......
...@@ -359,7 +359,6 @@ ...@@ -359,7 +359,6 @@
} }
.issuable-header-text { .issuable-header-text {
width: 100%;
padding-right: 35px; padding-right: 35px;
> strong { > strong {
......
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
border-bottom: none; border-bottom: none;
position: relative; position: relative;
@media (max-width: $screen-sm-min) { @media (max-width: $screen-xs-max) {
padding: 6px 0 24px; padding: 6px 0 24px;
} }
} }
...@@ -61,7 +61,7 @@ ...@@ -61,7 +61,7 @@
.column { .column {
text-align: center; text-align: center;
@media (max-width: $screen-sm-min) { @media (max-width: $screen-xs-max) {
padding: 15px 0; padding: 15px 0;
} }
...@@ -78,7 +78,7 @@ ...@@ -78,7 +78,7 @@
} }
&:last-child { &:last-child {
@media (max-width: $screen-sm-min) { @media (max-width: $screen-xs-max) {
text-align: center; text-align: center;
} }
} }
...@@ -156,7 +156,7 @@ ...@@ -156,7 +156,7 @@
} }
.inner-content { .inner-content {
@media (max-width: $screen-sm-min) { @media (max-width: $screen-xs-max) {
padding: 0 28px; padding: 0 28px;
text-align: center; text-align: center;
} }
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
font-size: 34px; font-size: 34px;
} }
@media (max-width: $screen-sm-min) { @media (max-width: $screen-xs-max) {
.environments-container { .environments-container {
width: 100%; width: 100%;
overflow: auto; overflow: auto;
......
...@@ -60,14 +60,14 @@ ...@@ -60,14 +60,14 @@
padding: 50px 100px; padding: 50px 100px;
overflow: hidden; overflow: hidden;
@media (max-width: $screen-md-min) { @media (max-width: $screen-sm-max) {
padding: 50px 0; padding: 50px 0;
} }
svg { svg {
float: right; float: right;
@media (max-width: $screen-md-min) { @media (max-width: $screen-sm-max) {
float: none; float: none;
display: block; display: block;
width: 250px; width: 250px;
...@@ -82,7 +82,7 @@ ...@@ -82,7 +82,7 @@
width: 460px; width: 460px;
margin-top: 120px; margin-top: 120px;
@media (max-width: $screen-md-min) { @media (max-width: $screen-sm-max) {
float: none; float: none;
margin-top: 60px; margin-top: 60px;
width: auto; width: auto;
......
...@@ -132,7 +132,7 @@ ...@@ -132,7 +132,7 @@
display: none; display: none;
} }
.btn-clipboard { .btn-clipboard:hover {
color: $gl-gray; color: $gl-gray;
} }
} }
...@@ -235,6 +235,10 @@ ...@@ -235,6 +235,10 @@
padding-bottom: 10px; padding-bottom: 10px;
color: $sidebar-collapsed-icon-color; color: $sidebar-collapsed-icon-color;
&:hover {
color: $gl-gray;
}
span { span {
display: block; display: block;
margin-top: 0; margin-top: 0;
...@@ -244,15 +248,24 @@ ...@@ -244,15 +248,24 @@
display: none; display: none;
} }
.avatar:hover {
border-color: #999;
}
.btn-clipboard { .btn-clipboard {
border: none; border: none;
color: #999;
&:hover { &:hover {
background: transparent; background: transparent;
<<<<<<< HEAD
} }
i { i {
color: $sidebar-collapsed-icon-color; color: $sidebar-collapsed-icon-color;
=======
color: $gl-gray;
>>>>>>> 14046b9c734e5e6506d63276f39f3f9d770c3699
} }
} }
} }
......
...@@ -123,7 +123,7 @@ ...@@ -123,7 +123,7 @@
padding: 20px 0; padding: 20px 0;
} }
@media (max-width: $screen-sm-min) { @media (max-width: $screen-xs-max) {
.milestone-actions { .milestone-actions {
@include clearfix(); @include clearfix();
padding-top: $gl-vert-padding; padding-top: $gl-vert-padding;
......
...@@ -111,7 +111,7 @@ ...@@ -111,7 +111,7 @@
text-align: center; text-align: center;
font-size: 13px; font-size: 13px;
@media (max-width: $screen-md-min) { @media (max-width: $screen-sm-max) {
// On smaller devices the warning becomes the fourth item in the list, // On smaller devices the warning becomes the fourth item in the list,
// rather than centering, and grows to span the full width of the // rather than centering, and grows to span the full width of the
// comment area. // comment area.
......
...@@ -90,14 +90,14 @@ ul.notes { ...@@ -90,14 +90,14 @@ ul.notes {
} }
&.system-note-commit-list { &.system-note-commit-list {
max-height: 63px; max-height: 70px;
overflow: hidden; overflow: hidden;
display: block; display: block;
ul { ul {
margin: 3px 0 3px 15px !important; margin: 3px 0 3px 16px !important;
li { .gfm-commit {
font-family: $monospace_font; font-family: $monospace_font;
font-size: 12px; font-size: 12px;
} }
...@@ -172,6 +172,10 @@ ul.notes { ...@@ -172,6 +172,10 @@ ul.notes {
&.timeline-entry { &.timeline-entry {
padding: 14px 10px; padding: 14px 10px;
} }
.system-note {
padding: 0;
}
} }
&.is-editting { &.is-editting {
...@@ -249,7 +253,7 @@ ul.notes { ...@@ -249,7 +253,7 @@ ul.notes {
} }
.page-sidebar-pinned.right-sidebar-expanded { .page-sidebar-pinned.right-sidebar-expanded {
@media (max-width: $screen-lg-min) { @media (max-width: $screen-md-max) {
.note-header { .note-header {
.note-headline-light { .note-headline-light {
display: block; display: block;
......
...@@ -180,7 +180,7 @@ ...@@ -180,7 +180,7 @@
.modal-dialog { .modal-dialog {
width: 380px; width: 380px;
@media (max-width: $screen-sm-min) { @media (max-width: $screen-xs-max) {
width: auto; width: auto;
} }
......
...@@ -189,7 +189,7 @@ ...@@ -189,7 +189,7 @@
} }
.download-button { .download-button {
@media (max-width: $screen-lg-min) { @media (max-width: $screen-md-max) {
margin-left: 0; margin-left: 0;
} }
} }
...@@ -909,3 +909,11 @@ a.allowed-to-push { ...@@ -909,3 +909,11 @@ a.allowed-to-push {
pointer-events: none; pointer-events: none;
} }
} }
.variables-table {
table-layout: fixed;
.variable-key {
width: 30%;
}
}
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
@include make-md-column(6); @include make-md-column(6);
margin-top: 10px; margin-top: 10px;
@media (max-width: $screen-sm-min) { @media (max-width: $screen-xs-max) {
width: 100%; width: 100%;
} }
} }
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
.last-commit { .last-commit {
@include str-truncated(506px); @include str-truncated(506px);
@media (min-width: $screen-sm-max) and (max-width: $screen-md-max) { @media (min-width: $screen-md-min) and (max-width: $screen-md-max) {
@include str-truncated(450px); @include str-truncated(450px);
} }
......
...@@ -4,3 +4,128 @@ ...@@ -4,3 +4,128 @@
margin-right: auto; margin-right: auto;
padding-right: 7px; padding-right: 7px;
} }
.wiki-page-header {
@extend .top-area;
position: relative;
.wiki-page-title {
margin: 0;
font-size: 22px;
}
.wiki-last-edit-by {
color: $gl-gray-light;
strong {
color: $gl-text-color;
}
}
.light {
font-weight: normal;
color: $gl-gray-light;
}
.git-access-header {
padding: 16px 40px 11px 0;
line-height: 28px;
font-size: 18px;
}
.git-clone-holder {
width: 100%;
padding-bottom: 40px;
}
button.sidebar-toggle {
position: absolute;
right: 0;
top: 11px;
display: block;
}
@media (min-width: $screen-sm-min) {
&.has-sidebar-toggle {
padding-right: 40px;
}
.git-clone-holder {
width: 480px;
}
.nav-controls {
width: auto;
min-width: 50%;
white-space: nowrap;
}
}
@media (min-width: $screen-md-min) {
&.has-sidebar-toggle {
padding-right: 0;
}
button.sidebar-toggle {
display: none;
}
}
}
.wiki-git-access {
margin: $gl-padding 0;
h3 {
font-size: 22px;
font-weight: normal;
margin-top: 1.4em;
}
}
.right-sidebar.wiki-sidebar {
padding: $gl-padding 0;
&.right-sidebar-collapsed {
display: none;
}
.blocks-container {
padding: 0 $gl-padding;
}
.block {
width: 100%;
}
a {
color: $layout-link-gray;
&:hover,
&.active {
color: $black;
}
}
.active > a {
color: $black;
}
ul.wiki-pages,
ul.wiki-pages li {
list-style: none;
padding: 0;
margin: 0;
}
ul.wiki-pages li {
margin: 5px 0 10px;
}
.wiki-sidebar-header {
padding: 0 $gl-padding $gl-padding;
.gutter-toggle {
margin-top: 0;
}
}
}
...@@ -123,6 +123,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -123,6 +123,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:koding_enabled, :koding_enabled,
:koding_url, :koding_url,
:email_author_in_body, :email_author_in_body,
:html_emails_enabled,
:repository_checks_enabled, :repository_checks_enabled,
:metrics_packet_size, :metrics_packet_size,
:send_user_confirmation_email, :send_user_confirmation_email,
......
...@@ -11,7 +11,7 @@ class AutocompleteController < ApplicationController ...@@ -11,7 +11,7 @@ class AutocompleteController < ApplicationController
@users = @users.reorder(:name) @users = @users.reorder(:name)
@users = load_users_by_ability || @users.page(params[:page]) @users = load_users_by_ability || @users.page(params[:page])
if params[:todo_filter].present? if params[:todo_filter].present? && current_user
@users = @users.todo_authors(current_user.id, params[:todo_state_filter]) @users = @users.todo_authors(current_user.id, params[:todo_state_filter])
end end
......
module LfsHelper # This concern assumes:
include Gitlab::Routing.url_helpers # - a `#project` accessor
# - a `#user` accessor
# - a `#authentication_result` accessor
# - a `#can?(object, action, subject)` method
# - a `#ci?` method
# - a `#download_request?` method
# - a `#upload_request?` method
# - a `#has_authentication_ability?(ability)` method
module LfsRequest
extend ActiveSupport::Concern
included do
before_action :require_lfs_enabled!
before_action :lfs_check_access!
end
private
def require_lfs_enabled! def require_lfs_enabled!
return if Gitlab.config.lfs.enabled return if Gitlab.config.lfs.enabled
...@@ -17,17 +33,23 @@ module LfsHelper ...@@ -17,17 +33,23 @@ module LfsHelper
return if download_request? && lfs_download_access? return if download_request? && lfs_download_access?
return if upload_request? && lfs_upload_access? return if upload_request? && lfs_upload_access?
<<<<<<< HEAD:app/helpers/lfs_helper.rb
if project.public? || (user && user.can?(:read_project, project)) if project.public? || (user && user.can?(:read_project, project))
if project.above_size_limit? || objects_exceed_repo_limit? if project.above_size_limit? || objects_exceed_repo_limit?
render_size_error render_size_error
else else
render_lfs_forbidden render_lfs_forbidden
end end
=======
if project.public? || can?(user, :read_project, project)
lfs_forbidden!
>>>>>>> 14046b9c734e5e6506d63276f39f3f9d770c3699:app/controllers/concerns/lfs_request.rb
else else
render_lfs_not_found render_lfs_not_found
end end
end end
<<<<<<< HEAD:app/helpers/lfs_helper.rb
def lfs_download_access? def lfs_download_access?
return false unless project.lfs_enabled? return false unless project.lfs_enabled?
...@@ -51,6 +73,10 @@ module LfsHelper ...@@ -51,6 +73,10 @@ module LfsHelper
return false if project.above_size_limit? || objects_exceed_repo_limit? return false if project.above_size_limit? || objects_exceed_repo_limit?
has_authentication_ability?(:push_code) && can?(user, :push_code, project) has_authentication_ability?(:push_code) && can?(user, :push_code, project)
=======
def lfs_forbidden!
render_lfs_forbidden
>>>>>>> 14046b9c734e5e6506d63276f39f3f9d770c3699:app/controllers/concerns/lfs_request.rb
end end
def objects_exceed_repo_limit? def objects_exceed_repo_limit?
...@@ -84,6 +110,7 @@ module LfsHelper ...@@ -84,6 +110,7 @@ module LfsHelper
) )
end end
<<<<<<< HEAD:app/helpers/lfs_helper.rb
def render_size_error def render_size_error
render( render(
json: { json: {
...@@ -93,6 +120,30 @@ module LfsHelper ...@@ -93,6 +120,30 @@ module LfsHelper
content_type: "application/vnd.git-lfs+json", content_type: "application/vnd.git-lfs+json",
status: 406 status: 406
) )
=======
def lfs_download_access?
return false unless project.lfs_enabled?
ci? || lfs_deploy_token? || user_can_download_code? || build_can_download_code?
end
def lfs_upload_access?
return false unless project.lfs_enabled?
has_authentication_ability?(:push_code) && can?(user, :push_code, project)
end
def lfs_deploy_token?
authentication_result.lfs_deploy_token?(project)
end
def user_can_download_code?
has_authentication_ability?(:download_code) && can?(user, :download_code, project)
end
def build_can_download_code?
has_authentication_ability?(:build_download_code) && can?(user, :build_download_code, project)
>>>>>>> 14046b9c734e5e6506d63276f39f3f9d770c3699:app/controllers/concerns/lfs_request.rb
end end
def storage_project def storage_project
...@@ -107,4 +158,8 @@ module LfsHelper ...@@ -107,4 +158,8 @@ module LfsHelper
result result
end end
end end
def objects
@objects ||= (params[:objects] || []).to_a
end
end end
module ToggleAwardEmoji module ToggleAwardEmoji
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do
before_action :authenticate_user!, only: [:toggle_award_emoji]
end
def toggle_award_emoji def toggle_award_emoji
authenticate_user!
name = params.require(:name) name = params.require(:name)
if awardable.user_can_award?(current_user, name) if awardable.user_can_award?(current_user, name)
......
module WorkhorseRequest
extend ActiveSupport::Concern
included do
before_action :verify_workhorse_api!
end
private
def verify_workhorse_api!
Gitlab::Workhorse.verify_api_request!(request.headers)
end
end
...@@ -6,9 +6,11 @@ class HelpController < ApplicationController ...@@ -6,9 +6,11 @@ class HelpController < ApplicationController
def index def index
@help_index = File.read(Rails.root.join('doc', 'README.md')) @help_index = File.read(Rails.root.join('doc', 'README.md'))
# Prefix Markdown links with `help/` unless they already have been # Prefix Markdown links with `help/` unless they are external links
# See http://rubular.com/r/ie2MlpdUMq # See http://rubular.com/r/X3baHTbPO2
@help_index.gsub!(/(\]\()(\/?help\/)?([^\)\(]+\))/, '\1/help/\3') @help_index.gsub!(%r{(?<delim>\]\()(?!.+://)(?!/)(?<link>[^\)\(]+\))}) do
"#{$~[:delim]}#{Gitlab.config.gitlab.relative_url_root}/help/#{$~[:link]}"
end
end end
def show def show
......
...@@ -4,7 +4,6 @@ class Profiles::AvatarsController < Profiles::ApplicationController ...@@ -4,7 +4,6 @@ class Profiles::AvatarsController < Profiles::ApplicationController
@user.remove_avatar! @user.remove_avatar!
@user.save @user.save
@user.reset_events_cache
redirect_to profile_path redirect_to profile_path
end end
......
...@@ -20,7 +20,6 @@ class Projects::AvatarsController < Projects::ApplicationController ...@@ -20,7 +20,6 @@ class Projects::AvatarsController < Projects::ApplicationController
@project.remove_avatar! @project.remove_avatar!
@project.save @project.save
@project.reset_events_cache
redirect_to edit_project_path(@project) redirect_to edit_project_path(@project)
end end
......
...@@ -13,7 +13,6 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -13,7 +13,6 @@ class Projects::BlobController < Projects::ApplicationController
before_action :assign_blob_vars before_action :assign_blob_vars
before_action :commit, except: [:new, :create] before_action :commit, except: [:new, :create]
before_action :blob, except: [:new, :create] before_action :blob, except: [:new, :create]
before_action :from_merge_request, only: [:edit, :update]
before_action :require_branch_head, only: [:edit, :update] before_action :require_branch_head, only: [:edit, :update]
before_action :editor_variables, except: [:show, :preview, :diff] before_action :editor_variables, except: [:show, :preview, :diff]
before_action :validate_diff_params, only: :diff before_action :validate_diff_params, only: :diff
...@@ -39,14 +38,6 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -39,14 +38,6 @@ class Projects::BlobController < Projects::ApplicationController
def update def update
@path = params[:file_path] if params[:file_path].present? @path = params[:file_path] if params[:file_path].present?
after_edit_path =
if from_merge_request && @target_branch == @ref
diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
"##{hexdigest(@path)}"
else
namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path))
end
create_commit(Files::UpdateService, success_path: after_edit_path, create_commit(Files::UpdateService, success_path: after_edit_path,
failure_view: :edit, failure_view: :edit,
failure_path: namespace_project_blob_path(@project.namespace, @project, @id)) failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
...@@ -124,9 +115,14 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -124,9 +115,14 @@ class Projects::BlobController < Projects::ApplicationController
render_404 render_404
end end
def from_merge_request def after_edit_path
# If blob edit was initiated from merge request page from_merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.find_by(iid: params[:from_merge_request_iid])
@from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id]) if from_merge_request && @target_branch == @ref
diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
"##{hexdigest(@path)}"
else
namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path))
end
end end
def editor_variables def editor_variables
......
...@@ -36,7 +36,7 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -36,7 +36,7 @@ class Projects::BranchesController < Projects::ApplicationController
execute(branch_name, ref) execute(branch_name, ref)
if params[:issue_iid] if params[:issue_iid]
issue = @project.issues.find_by(iid: params[:issue_iid]) issue = IssuesFinder.new(current_user, project_id: @project.id).find_by(iid: params[:issue_iid])
SystemNoteService.new_issue_branch(issue, @project, current_user, branch_name) if issue SystemNoteService.new_issue_branch(issue, @project, current_user, branch_name) if issue
end end
......
...@@ -6,7 +6,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController ...@@ -6,7 +6,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
before_action :authorize_read_cycle_analytics! before_action :authorize_read_cycle_analytics!
def show def show
@cycle_analytics = ::CycleAnalytics.new(@project, from: start_date(cycle_analytics_params)) @cycle_analytics = ::CycleAnalytics.new(@project, current_user, from: start_date(cycle_analytics_params))
stats_values, cycle_analytics_json = generate_cycle_analytics_data stats_values, cycle_analytics_json = generate_cycle_analytics_data
......
...@@ -18,6 +18,14 @@ class Projects::GitHttpClientController < Projects::ApplicationController ...@@ -18,6 +18,14 @@ class Projects::GitHttpClientController < Projects::ApplicationController
private private
def download_request?
raise NotImplementedError
end
def upload_request?
raise NotImplementedError
end
def authenticate_user def authenticate_user
@authentication_result = Gitlab::Auth::Result.new @authentication_result = Gitlab::Auth::Result.new
...@@ -130,10 +138,6 @@ class Projects::GitHttpClientController < Projects::ApplicationController ...@@ -130,10 +138,6 @@ class Projects::GitHttpClientController < Projects::ApplicationController
authentication_result.ci?(project) authentication_result.ci?(project)
end end
def lfs_deploy_token?
authentication_result.lfs_deploy_token?(project)
end
def authentication_has_download_access? def authentication_has_download_access?
has_authentication_ability?(:download_code) || has_authentication_ability?(:build_download_code) has_authentication_ability?(:download_code) || has_authentication_ability?(:build_download_code)
end end
...@@ -149,8 +153,4 @@ class Projects::GitHttpClientController < Projects::ApplicationController ...@@ -149,8 +153,4 @@ class Projects::GitHttpClientController < Projects::ApplicationController
def authentication_project def authentication_project
authentication_result.project authentication_result.project
end end
def verify_workhorse_api!
Gitlab::Workhorse.verify_api_request!(request.headers)
end
end end
# This file should be identical in GitLab Community Edition and Enterprise Edition
class Projects::GitHttpController < Projects::GitHttpClientController class Projects::GitHttpController < Projects::GitHttpClientController
before_action :verify_workhorse_api! include WorkhorseRequest
# GET /foo/bar.git/info/refs?service=git-upload-pack (git pull) # GET /foo/bar.git/info/refs?service=git-upload-pack (git pull)
# GET /foo/bar.git/info/refs?service=git-receive-pack (git push) # GET /foo/bar.git/info/refs?service=git-receive-pack (git push)
...@@ -69,16 +67,25 @@ class Projects::GitHttpController < Projects::GitHttpClientController ...@@ -69,16 +67,25 @@ class Projects::GitHttpController < Projects::GitHttpClientController
end end
def render_denied def render_denied
<<<<<<< HEAD
if user && user.can?(:read_project, project) if user && user.can?(:read_project, project)
message = project.above_size_limit? ? access_check.message : 'Access denied' message = project.above_size_limit? ? access_check.message : 'Access denied'
render plain: message, status: :forbidden render plain: message, status: :forbidden
=======
if user && can?(user, :read_project, project)
render plain: access_denied_message, status: :forbidden
>>>>>>> 14046b9c734e5e6506d63276f39f3f9d770c3699
else else
# Do not leak information about project existence # Do not leak information about project existence
render_not_found render_not_found
end end
end end
def access_denied_message
'Access denied'
end
def upload_pack_allowed? def upload_pack_allowed?
return false unless Gitlab.config.gitlab_shell.upload_pack return false unless Gitlab.config.gitlab_shell.upload_pack
......
class Projects::LfsApiController < Projects::GitHttpClientController class Projects::LfsApiController < Projects::GitHttpClientController
include LfsHelper include LfsRequest
before_action :require_lfs_enabled! skip_before_action :lfs_check_access!, only: [:deprecated]
before_action :lfs_check_access!, except: [:deprecated]
def batch def batch
unless objects.present? unless objects.present?
...@@ -31,6 +30,14 @@ class Projects::LfsApiController < Projects::GitHttpClientController ...@@ -31,6 +30,14 @@ class Projects::LfsApiController < Projects::GitHttpClientController
private private
def download_request?
params[:operation] == 'download'
end
def upload_request?
params[:operation] == 'upload'
end
def existing_oids def existing_oids
@existing_oids ||= begin @existing_oids ||= begin
storage_project.lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid) storage_project.lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid)
...@@ -79,12 +86,4 @@ class Projects::LfsApiController < Projects::GitHttpClientController ...@@ -79,12 +86,4 @@ class Projects::LfsApiController < Projects::GitHttpClientController
} }
} }
end end
def download_request?
params[:operation] == 'download'
end
def upload_request?
params[:operation] == 'upload'
end
end end
class Projects::LfsStorageController < Projects::GitHttpClientController class Projects::LfsStorageController < Projects::GitHttpClientController
include LfsHelper include LfsRequest
include WorkhorseRequest
before_action :require_lfs_enabled! skip_before_action :verify_workhorse_api!, only: [:download, :upload_finalize]
before_action :lfs_check_access!
before_action :verify_workhorse_api!, only: [:upload_authorize]
def download def download
lfs_object = LfsObject.find_by_oid(oid) lfs_object = LfsObject.find_by_oid(oid)
......
...@@ -343,16 +343,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -343,16 +343,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.update(merge_error: nil) @merge_request.update(merge_error: nil)
if params[:merge_when_build_succeeds].present? if params[:merge_when_build_succeeds].present?
unless @merge_request.pipeline unless @merge_request.head_pipeline
@status = :failed @status = :failed
return return
end end
if @merge_request.pipeline.active? if @merge_request.head_pipeline.active?
MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params) MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params)
.execute(@merge_request) .execute(@merge_request)
@status = :merge_when_build_succeeds @status = :merge_when_build_succeeds
elsif @merge_request.pipeline.success? elsif @merge_request.head_pipeline.success?
# This can be triggered when a user clicks the auto merge button while # This can be triggered when a user clicks the auto merge button while
# the tests finish at about the same time # the tests finish at about the same time
MergeWorker.perform_async(@merge_request.id, current_user.id, params) MergeWorker.perform_async(@merge_request.id, current_user.id, params)
...@@ -423,7 +423,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -423,7 +423,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def ci_status def ci_status
pipeline = @merge_request.pipeline pipeline = @merge_request.head_pipeline
if pipeline if pipeline
status = pipeline.status status = pipeline.status
coverage = pipeline.try(:coverage) coverage = pipeline.try(:coverage)
...@@ -528,7 +529,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -528,7 +529,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def validates_merge_request def validates_merge_request
# Show git not found page # Show git not found page
# if there is no saved commits between source & target branch # if there is no saved commits between source & target branch
if @merge_request.commits.blank? if @merge_request.has_no_commits?
# and if target branch doesn't exist # and if target branch doesn't exist
return invalid_mr unless @merge_request.target_branch_exists? return invalid_mr unless @merge_request.target_branch_exists?
end end
...@@ -536,7 +537,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -536,7 +537,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def define_show_vars def define_show_vars
@noteable = @merge_request @noteable = @merge_request
@commits_count = @merge_request.commits.count @commits_count = @merge_request.commits_count
if @merge_request.locked_long_ago? if @merge_request.locked_long_ago?
@merge_request.unlock_mr @merge_request.unlock_mr
...@@ -571,7 +572,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -571,7 +572,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def define_widget_vars def define_widget_vars
@pipeline = @merge_request.pipeline @pipeline = @merge_request.head_pipeline
end end
def define_commit_vars def define_commit_vars
...@@ -600,8 +601,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -600,8 +601,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def define_pipelines_vars def define_pipelines_vars
@pipelines = @merge_request.all_pipelines @pipelines = @merge_request.all_pipelines
@pipeline = @merge_request.pipeline @pipeline = @merge_request.head_pipeline
@statuses = @pipeline.statuses.relevant if @pipeline.present? @statuses_count = @pipeline.present? ? @pipeline.statuses.relevant.count : 0
end end
def define_new_vars def define_new_vars
...@@ -695,7 +696,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -695,7 +696,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def merge_when_build_succeeds_active? def merge_when_build_succeeds_active?
params[:merge_when_build_succeeds].present? && params[:merge_when_build_succeeds].present? &&
@merge_request.pipeline && @merge_request.pipeline.active? @merge_request.head_pipeline && @merge_request.head_pipeline.active?
end end
def build_merge_request def build_merge_request
......
class Projects::PipelinesController < Projects::ApplicationController class Projects::PipelinesController < Projects::ApplicationController
before_action :pipeline, except: [:index, :new, :create] before_action :pipeline, except: [:index, :new, :create]
before_action :commit, only: [:show] before_action :commit, only: [:show, :builds]
before_action :authorize_read_pipeline! before_action :authorize_read_pipeline!
before_action :authorize_create_pipeline!, only: [:new, :create] before_action :authorize_create_pipeline!, only: [:new, :create]
before_action :authorize_update_pipeline!, only: [:retry, :cancel] before_action :authorize_update_pipeline!, only: [:retry, :cancel]
...@@ -32,6 +32,14 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -32,6 +32,14 @@ class Projects::PipelinesController < Projects::ApplicationController
def show def show
end end
def builds
respond_to do |format|
format.html do
render 'show'
end
end
end
def retry def retry
pipeline.retry_failed(current_user) pipeline.retry_failed(current_user)
......
...@@ -16,13 +16,7 @@ class Projects::TodosController < Projects::ApplicationController ...@@ -16,13 +16,7 @@ class Projects::TodosController < Projects::ApplicationController
@issuable ||= begin @issuable ||= begin
case params[:issuable_type] case params[:issuable_type]
when "issue" when "issue"
issue = @project.issues.find(params[:issuable_id]) IssuesFinder.new(current_user, project_id: @project.id).find(params[:issuable_id])
if can?(current_user, :read_issue, issue)
issue
else
render_404
end
when "merge_request" when "merge_request"
@project.merge_requests.find(params[:issuable_id]) @project.merge_requests.find(params[:issuable_id])
end end
......
...@@ -124,6 +124,8 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -124,6 +124,8 @@ class Projects::WikisController < Projects::ApplicationController
# Call #wiki to make sure the Wiki Repo is initialized # Call #wiki to make sure the Wiki Repo is initialized
@project_wiki.wiki @project_wiki.wiki
@sidebar_wiki_pages = @project_wiki.pages.first(15)
rescue ProjectWiki::CouldNotCreateWikiError rescue ProjectWiki::CouldNotCreateWikiError
flash[:notice] = "Could not create Wiki Repository at this time. Please try again later." flash[:notice] = "Could not create Wiki Repository at this time. Please try again later."
redirect_to project_path(@project) redirect_to project_path(@project)
......
...@@ -16,14 +16,12 @@ ...@@ -16,14 +16,12 @@
# label_name: string # label_name: string
# sort: string # sort: string
# #
require_relative 'projects_finder'
class IssuableFinder class IssuableFinder
NONE = '0' NONE = '0'
attr_accessor :current_user, :params attr_accessor :current_user, :params
def initialize(current_user, params) def initialize(current_user, params = {})
@current_user = current_user @current_user = current_user
@params = params @params = params
end end
...@@ -44,6 +42,40 @@ class IssuableFinder ...@@ -44,6 +42,40 @@ class IssuableFinder
sort(items) sort(items)
end end
def find(*params)
execute.find(*params)
end
def find_by(*params)
execute.find_by(*params)
end
# We often get counts for each state by running a query per state, and
# counting those results. This is typically slower than running one query
# (even if that query is slower than any of the individual state queries) and
# grouping and counting within that query.
#
def count_by_state
count_params = params.merge(state: nil, sort: nil)
labels_count = label_names.any? ? label_names.count : 1
finder = self.class.new(current_user, count_params)
counts = Hash.new(0)
# Searching by label includes a GROUP BY in the query, but ours will be last
# because it is added last. Searching by multiple labels also includes a row
# per issuable, so we have to count those in Ruby - which is bad, but still
# better than performing multiple queries.
#
finder.execute.reorder(nil).group(:state).count.each do |key, value|
counts[Array(key).last.to_sym] += value / labels_count
end
counts[:all] = counts.values.sum
counts[:opened] += counts[:reopened]
counts
end
def group def group
return @group if defined?(@group) return @group if defined?(@group)
......
...@@ -12,7 +12,7 @@ class NotesFinder ...@@ -12,7 +12,7 @@ class NotesFinder
when "commit" when "commit"
project.notes.for_commit_id(target_id).non_diff_notes project.notes.for_commit_id(target_id).non_diff_notes
when "issue" when "issue"
project.issues.visible_to_user(current_user).find(target_id).notes.inc_author IssuesFinder.new(current_user, project_id: project.id).find(target_id).notes.inc_author
when "merge_request" when "merge_request"
project.merge_requests.find(target_id).mr_and_commit_notes.inc_author project.merge_requests.find(target_id).mr_and_commit_notes.inc_author
when "snippet", "project_snippet" when "snippet", "project_snippet"
......
...@@ -47,7 +47,7 @@ module DropdownsHelper ...@@ -47,7 +47,7 @@ module DropdownsHelper
default_label = data_attr[:default_label] default_label = data_attr[:default_label]
content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.has_key?(:toggle_class)}", id: (options[:id] if options.has_key?(:id)), type: "button", data: data_attr) do content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.has_key?(:toggle_class)}", id: (options[:id] if options.has_key?(:id)), type: "button", data: data_attr) do
output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}") output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}")
output << icon('caret-down') output << icon('chevron-down')
output.html_safe output.html_safe
end end
end end
......
...@@ -158,8 +158,12 @@ module IssuablesHelper ...@@ -158,8 +158,12 @@ module IssuablesHelper
:author_id, :author_id,
:assignee_id, :assignee_id,
:milestone_title, :milestone_title,
<<<<<<< HEAD
:label_name, :label_name,
:weight :weight
=======
:label_name
>>>>>>> 14046b9c734e5e6506d63276f39f3f9d770c3699
] ]
end end
...@@ -190,12 +194,18 @@ module IssuablesHelper ...@@ -190,12 +194,18 @@ module IssuablesHelper
end end
def issuables_count_for_state(issuable_type, state) def issuables_count_for_state(issuable_type, state)
<<<<<<< HEAD
issuables_finder = public_send("#{issuable_type}_finder") issuables_finder = public_send("#{issuable_type}_finder")
params = issuables_finder.params.merge(state: state) params = issuables_finder.params.merge(state: state)
finder = issuables_finder.class.new(issuables_finder.current_user, params) finder = issuables_finder.class.new(issuables_finder.current_user, params)
finder.execute.page(1).total_count finder.execute.page(1).total_count
=======
@counts ||= {}
@counts[issuable_type] ||= public_send("#{issuable_type}_finder").count_by_state
@counts[issuable_type][state]
>>>>>>> 14046b9c734e5e6506d63276f39f3f9d770c3699
end end
IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page] IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page]
...@@ -205,6 +215,7 @@ module IssuablesHelper ...@@ -205,6 +215,7 @@ module IssuablesHelper
opts = params.with_indifferent_access opts = params.with_indifferent_access
opts[:state] = state opts[:state] = state
opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY) opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY)
opts.delete_if { |_, value| value.blank? }
hexdigest(['issuables_count', issuable_type, opts.sort].flatten.join('-')) hexdigest(['issuables_count', issuable_type, opts.sort].flatten.join('-'))
end end
......
...@@ -20,6 +20,11 @@ module NavHelper ...@@ -20,6 +20,11 @@ module NavHelper
end end
elsif current_path?('builds#show') elsif current_path?('builds#show')
"page-gutter build-sidebar right-sidebar-expanded" "page-gutter build-sidebar right-sidebar-expanded"
elsif current_path?('wikis#show') ||
current_path?('wikis#edit') ||
current_path?('wikis#history') ||
current_path?('wikis#git_access')
"page-gutter wiki-sidebar right-sidebar-expanded"
end end
end end
......
...@@ -4,6 +4,7 @@ module Emails ...@@ -4,6 +4,7 @@ module Emails
setup_note_mail(note_id, recipient_id) setup_note_mail(note_id, recipient_id)
@commit = @note.noteable @commit = @note.noteable
@discussion = @note.to_discussion if @note.diff_note?
@target_url = namespace_project_commit_url(*note_target_url_options) @target_url = namespace_project_commit_url(*note_target_url_options)
mail_answer_thread(@commit, mail_answer_thread(@commit,
...@@ -24,6 +25,7 @@ module Emails ...@@ -24,6 +25,7 @@ module Emails
setup_note_mail(note_id, recipient_id) setup_note_mail(note_id, recipient_id)
@merge_request = @note.noteable @merge_request = @note.noteable
@discussion = @note.to_discussion if @note.diff_note?
@target_url = namespace_project_merge_request_url(*note_target_url_options) @target_url = namespace_project_merge_request_url(*note_target_url_options)
mail_answer_thread(@merge_request, note_thread_options(recipient_id)) mail_answer_thread(@merge_request, note_thread_options(recipient_id))
end end
......
...@@ -203,7 +203,7 @@ module Ci ...@@ -203,7 +203,7 @@ module Ci
.reorder(iid: :asc) .reorder(iid: :asc)
merge_requests.find do |merge_request| merge_requests.find do |merge_request|
merge_request.commits.any? { |ci| ci.id == pipeline.sha } merge_request.commits_sha.include?(pipeline.sha)
end end
end end
......
...@@ -161,24 +161,28 @@ module Ci ...@@ -161,24 +161,28 @@ module Ci
end end
def retryable? def retryable?
builds.latest.any? do |build| builds.latest.failed_or_canceled.any?(&:retryable?)
(build.failed? || build.canceled?) && build.retryable?
end
end end
def cancelable? def cancelable?
builds.running_or_pending.any? statuses.cancelable.any?
end end
def cancel_running def cancel_running
builds.running_or_pending.each(&:cancel) Gitlab::OptimisticLocking.retry_lock(
statuses.cancelable) do |cancelable|
cancelable.each(&:cancel)
end
end end
def retry_failed(user) def retry_failed(user)
builds.latest.failed.select(&:retryable?).each do |build| Gitlab::OptimisticLocking.retry_lock(
builds.latest.failed_or_canceled) do |failed_or_canceled|
failed_or_canceled.select(&:retryable?).each do |build|
Ci::Build.retry(build, user) Ci::Build.retry(build, user)
end end
end end
end
def mark_as_processable_after_stage(stage_idx) def mark_as_processable_after_stage(stage_idx)
builds.skipped.where('stage_idx > ?', stage_idx).find_each(&:process) builds.skipped.where('stage_idx > ?', stage_idx).find_each(&:process)
...@@ -313,7 +317,7 @@ module Ci ...@@ -313,7 +317,7 @@ module Ci
def merge_requests def merge_requests
@merge_requests ||= project.merge_requests @merge_requests ||= project.merge_requests
.where(source_branch: self.ref) .where(source_branch: self.ref)
.select { |merge_request| merge_request.pipeline.try(:id) == self.id } .select { |merge_request| merge_request.head_pipeline.try(:id) == self.id }
end end
private private
......
...@@ -48,6 +48,10 @@ class Commit ...@@ -48,6 +48,10 @@ class Commit
max_lines: DIFF_HARD_LIMIT_LINES, max_lines: DIFF_HARD_LIMIT_LINES,
} }
end end
def from_hash(hash, project)
new(Gitlab::Git::Commit.new(hash), project)
end
end end
attr_accessor :raw attr_accessor :raw
......
...@@ -73,6 +73,11 @@ module HasStatus ...@@ -73,6 +73,11 @@ module HasStatus
scope :skipped, -> { where(status: 'skipped') } scope :skipped, -> { where(status: 'skipped') }
scope :running_or_pending, -> { where(status: [:running, :pending]) } scope :running_or_pending, -> { where(status: [:running, :pending]) }
scope :finished, -> { where(status: [:success, :failed, :canceled]) } scope :finished, -> { where(status: [:success, :failed, :canceled]) }
scope :failed_or_canceled, -> { where(status: [:failed, :canceled]) }
scope :cancelable, -> do
where(status: [:running, :pending, :created])
end
end end
def started? def started?
......
...@@ -2,12 +2,17 @@ module ProtectedBranchAccess ...@@ -2,12 +2,17 @@ module ProtectedBranchAccess
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
<<<<<<< HEAD
validates_uniqueness_of :group_id, scope: :protected_branch, allow_nil: true validates_uniqueness_of :group_id, scope: :protected_branch, allow_nil: true
validates_uniqueness_of :user_id, scope: :protected_branch, allow_nil: true validates_uniqueness_of :user_id, scope: :protected_branch, allow_nil: true
validates_uniqueness_of :access_level, validates_uniqueness_of :access_level,
scope: :protected_branch, scope: :protected_branch,
unless: Proc.new { |access_level| access_level.user_id? || access_level.group_id? }, unless: Proc.new { |access_level| access_level.user_id? || access_level.group_id? },
conditions: -> { where(user_id: nil, group_id: nil) } conditions: -> { where(user_id: nil, group_id: nil) }
=======
belongs_to :protected_branch
delegate :project, to: :protected_branch
>>>>>>> 14046b9c734e5e6506d63276f39f3f9d770c3699
scope :master, -> { where(access_level: Gitlab::Access::MASTER) } scope :master, -> { where(access_level: Gitlab::Access::MASTER) }
scope :developer, -> { where(access_level: Gitlab::Access::DEVELOPER) } scope :developer, -> { where(access_level: Gitlab::Access::DEVELOPER) }
...@@ -29,4 +34,10 @@ module ProtectedBranchAccess ...@@ -29,4 +34,10 @@ module ProtectedBranchAccess
self.class.human_access_levels[self.access_level] self.class.human_access_levels[self.access_level]
end end
def check_access(user)
return true if user.is_admin?
project.team.max_member_access(user.id) >= access_level
end
end end
class CycleAnalytics class CycleAnalytics
STAGES = %i[issue plan code test review staging production].freeze STAGES = %i[issue plan code test review staging production].freeze
def initialize(project, from:) def initialize(project, current_user, from:)
@project = project @project = project
@current_user = current_user
@from = from @from = from
@fetcher = Gitlab::CycleAnalytics::MetricsFetcher.new(project: project, from: from, branch: nil) @fetcher = Gitlab::CycleAnalytics::MetricsFetcher.new(project: project, from: from, branch: nil)
end end
def summary def summary
@summary ||= Summary.new(@project, from: @from) @summary ||= Summary.new(@project, @current_user, from: @from)
end end
def permissions(user:) def permissions(user:)
......
class CycleAnalytics class CycleAnalytics
class Summary class Summary
def initialize(project, from:) def initialize(project, current_user, from:)
@project = project @project = project
@current_user = current_user
@from = from @from = from
end end
def new_issues def new_issues
@project.issues.created_after(@from).count IssuesFinder.new(@current_user, project_id: @project.id).execute.created_after(@from).count
end end
def commits def commits
......
...@@ -25,7 +25,12 @@ class Discussion ...@@ -25,7 +25,12 @@ class Discussion
to: :last_resolved_note, to: :last_resolved_note,
allow_nil: true allow_nil: true
delegate :blob, :highlighted_diff_lines, to: :diff_file, allow_nil: true delegate :blob,
:highlighted_diff_lines,
:diff_lines,
to: :diff_file,
allow_nil: true
def self.for_notes(notes) def self.for_notes(notes)
notes.group_by(&:discussion_id).values.map { |notes| new(notes) } notes.group_by(&:discussion_id).values.map { |notes| new(notes) }
...@@ -159,10 +164,11 @@ class Discussion ...@@ -159,10 +164,11 @@ class Discussion
end end
# Returns an array of at most 16 highlighted lines above a diff note # Returns an array of at most 16 highlighted lines above a diff note
def truncated_diff_lines def truncated_diff_lines(highlight: true)
lines = highlight ? highlighted_diff_lines : diff_lines
prev_lines = [] prev_lines = []
highlighted_diff_lines.each do |line| lines.each do |line|
if line.meta? if line.meta?
prev_lines.clear prev_lines.clear
else else
......
...@@ -48,12 +48,6 @@ class Event < ActiveRecord::Base ...@@ -48,12 +48,6 @@ class Event < ActiveRecord::Base
scope :merged, -> { where(action: MERGED) } scope :merged, -> { where(action: MERGED) }
class << self class << self
def reset_event_cache_for(target)
Event.where(target_id: target.id, target_type: target.class.to_s).
order('id DESC').limit(100).
update_all(updated_at: Time.now)
end
# Update Gitlab::ContributionsCalendar#activity_dates if this changes # Update Gitlab::ContributionsCalendar#activity_dates if this changes
def contributions def contributions
where("action = ? OR (target_type in (?) AND action in (?))", where("action = ? OR (target_type in (?) AND action in (?))",
...@@ -358,6 +352,10 @@ class Event < ActiveRecord::Base ...@@ -358,6 +352,10 @@ class Event < ActiveRecord::Base
update_all(last_activity_at: created_at) update_all(last_activity_at: created_at)
end end
def authored_by?(user)
user ? author_id == user.id : false
end
private private
def recent_update? def recent_update?
......
...@@ -192,18 +192,6 @@ class Issue < ActiveRecord::Base ...@@ -192,18 +192,6 @@ class Issue < ActiveRecord::Base
branches_with_iid - branches_with_merge_request branches_with_iid - branches_with_merge_request
end end
# Reset issue events cache
#
# Since we do cache @event we need to reset cache in special cases:
# * when an issue is updated
# Events cache stored like events/23-20130109142513.
# The cache key includes updated_at timestamp.
# Thus it will automatically generate a new fragment
# when the event is updated because the key changes.
def reset_events_cache
Event.reset_event_cache_for(self)
end
# To allow polymorphism with MergeRequest. # To allow polymorphism with MergeRequest.
def source_project def source_project
project project
......
...@@ -27,7 +27,8 @@ class MergeRequest < ActiveRecord::Base ...@@ -27,7 +27,8 @@ class MergeRequest < ActiveRecord::Base
after_create :ensure_merge_request_diff, unless: :importing? after_create :ensure_merge_request_diff, unless: :importing?
after_update :reload_diff_if_branch_changed after_update :reload_diff_if_branch_changed
delegate :commits, :real_size, to: :merge_request_diff, prefix: nil delegate :commits, :real_size, :commits_sha, :commits_count,
to: :merge_request_diff, prefix: nil
# When this attribute is true some MR validation is ignored # When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests # It allows us to close or modify broken merge requests
...@@ -628,18 +629,6 @@ class MergeRequest < ActiveRecord::Base ...@@ -628,18 +629,6 @@ class MergeRequest < ActiveRecord::Base
self.target_project.repository.branch_names.include?(self.target_branch) self.target_project.repository.branch_names.include?(self.target_branch)
end end
# Reset merge request events cache
#
# Since we do cache @event we need to reset cache in special cases:
# * when a merge request is updated
# Events cache stored like events/23-20130109142513.
# The cache key includes updated_at timestamp.
# Thus it will automatically generate a new fragment
# when the event is updated because the key changes.
def reset_events_cache
Event.reset_event_cache_for(self)
end
def merge_commit_message def merge_commit_message
message = "Merge branch '#{source_branch}' into '#{target_branch}'\n\n" message = "Merge branch '#{source_branch}' into '#{target_branch}'\n\n"
message << "#{title}\n\n" message << "#{title}\n\n"
...@@ -697,7 +686,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -697,7 +686,7 @@ class MergeRequest < ActiveRecord::Base
end end
def broken? def broken?
self.commits.blank? || branch_missing? || cannot_be_merged? has_no_commits? || branch_missing? || cannot_be_merged?
end end
def can_be_merged_by?(user) def can_be_merged_by?(user)
...@@ -713,7 +702,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -713,7 +702,7 @@ class MergeRequest < ActiveRecord::Base
def mergeable_ci_state? def mergeable_ci_state?
return true unless project.only_allow_merge_if_build_succeeds? return true unless project.only_allow_merge_if_build_succeeds?
!pipeline || pipeline.success? || pipeline.skipped? !head_pipeline || head_pipeline.success? || head_pipeline.skipped?
end end
def environments def environments
...@@ -833,18 +822,14 @@ class MergeRequest < ActiveRecord::Base ...@@ -833,18 +822,14 @@ class MergeRequest < ActiveRecord::Base
diverged_commits_count > 0 diverged_commits_count > 0
end end
def commits_sha def head_pipeline
commits.map(&:sha)
end
def pipeline
return unless diff_head_sha && source_project return unless diff_head_sha && source_project
@pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha) @head_pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha)
end end
def all_pipelines def all_pipelines
return unless source_project return Ci::Pipeline.none unless source_project
@all_pipelines ||= source_project.pipelines @all_pipelines ||= source_project.pipelines
.where(sha: all_commits_sha, ref: source_branch) .where(sha: all_commits_sha, ref: source_branch)
...@@ -934,4 +919,12 @@ class MergeRequest < ActiveRecord::Base ...@@ -934,4 +919,12 @@ class MergeRequest < ActiveRecord::Base
@conflicts_can_be_resolved_in_ui = false @conflicts_can_be_resolved_in_ui = false
end end
end end
def has_commits?
commits_count > 0
end
def has_no_commits?
!has_commits?
end
end end
...@@ -127,12 +127,8 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -127,12 +127,8 @@ class MergeRequestDiff < ActiveRecord::Base
end end
def commits_sha def commits_sha
if @commits
commits.map(&:sha)
else
st_commits.map { |commit| commit[:id] } st_commits.map { |commit| commit[:id] }
end end
end
def diff_refs def diff_refs
return unless start_commit_sha || base_commit_sha return unless start_commit_sha || base_commit_sha
...@@ -176,6 +172,10 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -176,6 +172,10 @@ class MergeRequestDiff < ActiveRecord::Base
CompareService.new.execute(project, head_commit_sha, project, sha, straight: straight) CompareService.new.execute(project, head_commit_sha, project, sha, straight: straight)
end end
def commits_count
st_commits.count
end
private private
# Old GitLab implementations may have generated diffs as ["--broken-diff"]. # Old GitLab implementations may have generated diffs as ["--broken-diff"].
......
...@@ -207,19 +207,6 @@ class Note < ActiveRecord::Base ...@@ -207,19 +207,6 @@ class Note < ActiveRecord::Base
super(noteable_type.to_s.classify.constantize.base_class.to_s) super(noteable_type.to_s.classify.constantize.base_class.to_s)
end end
# Reset notes events cache
#
# Since we do cache @event we need to reset cache in special cases:
# * when a note is updated
# * when a note is removed
# Events cache stored like events/23-20130109142513.
# The cache key includes updated_at timestamp.
# Thus it will automatically generate a new fragment
# when the event is updated because the key changes.
def reset_events_cache
Event.reset_event_cache_for(self)
end
def editable? def editable?
!system? !system?
end end
......
...@@ -783,9 +783,9 @@ class Project < ActiveRecord::Base ...@@ -783,9 +783,9 @@ class Project < ActiveRecord::Base
self.id self.id
end end
def get_issue(issue_id) def get_issue(issue_id, current_user)
if default_issues_tracker? if default_issues_tracker?
issues.find_by(iid: issue_id) IssuesFinder.new(current_user, project_id: id).find_by(iid: issue_id)
else else
ExternalIssue.new(issue_id, self) ExternalIssue.new(issue_id, self)
end end
...@@ -1086,7 +1086,6 @@ class Project < ActiveRecord::Base ...@@ -1086,7 +1086,6 @@ class Project < ActiveRecord::Base
begin begin
gitlab_shell.mv_repository(repository_storage_path, "#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki") gitlab_shell.mv_repository(repository_storage_path, "#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
send_move_instructions(old_path_with_namespace) send_move_instructions(old_path_with_namespace)
reset_events_cache
@old_path_with_namespace = old_path_with_namespace @old_path_with_namespace = old_path_with_namespace
...@@ -1154,22 +1153,6 @@ class Project < ActiveRecord::Base ...@@ -1154,22 +1153,6 @@ class Project < ActiveRecord::Base
attrs attrs
end end
# Reset events cache related to this project
#
# Since we do cache @event we need to reset cache in special cases:
# * when project was moved
# * when project was renamed
# * when the project avatar changes
# Events cache stored like events/23-20130109142513.
# The cache key includes updated_at timestamp.
# Thus it will automatically generate a new fragment
# when the event is updated because the key changes.
def reset_events_cache
Event.where(project_id: self.id).
order('id DESC').limit(100).
update_all(updated_at: Time.now)
end
def project_member(user) def project_member(user)
project_members.find_by(user_id: user) project_members.find_by(user_id: user)
end end
......
...@@ -9,6 +9,9 @@ class JiraService < IssueTrackerService ...@@ -9,6 +9,9 @@ class JiraService < IssueTrackerService
before_update :reset_password before_update :reset_password
# This is confusing, but JiraService does not really support these events.
# The values here are required to display correct options in the service
# configuration screen.
def supported_events def supported_events
%w(commit merge_request) %w(commit merge_request)
end end
...@@ -105,18 +108,29 @@ class JiraService < IssueTrackerService ...@@ -105,18 +108,29 @@ class JiraService < IssueTrackerService
"#{url}/secure/CreateIssue.jspa" "#{url}/secure/CreateIssue.jspa"
end end
def execute(push, issue = nil) def execute(push)
if issue.nil? # This method is a no-op, because currently JiraService does not
# No specific issue, that means # support any events.
# we just want to test settings end
test_settings
else
jira_issue = jira_request { client.Issue.find(issue.iid) }
return false unless jira_issue.present? def close_issue(entity, external_issue)
issue = jira_request { client.Issue.find(external_issue.iid) }
close_issue(push, jira_issue) return if issue.nil? || issue.resolution.present? || !jira_issue_transition_id.present?
commit_id = if entity.is_a?(Commit)
entity.id
elsif entity.is_a?(MergeRequest)
entity.diff_head_sha
end end
commit_url = build_entity_url(:commit, commit_id)
# Depending on the JIRA project's workflow, a comment during transition
# may or may not be allowed. Refresh the issue after transition and check
# if it is closed, so we don't have one comment for every commit.
issue = jira_request { client.Issue.find(issue.key) } if transition_issue(issue)
add_issue_solved_comment(issue, commit_id, commit_url) if issue.resolution
end end
def create_cross_reference_note(mentioned, noteable, author) def create_cross_reference_note(mentioned, noteable, author)
...@@ -156,6 +170,11 @@ class JiraService < IssueTrackerService ...@@ -156,6 +170,11 @@ class JiraService < IssueTrackerService
"Please fill in Password and Username." "Please fill in Password and Username."
end end
def test(_)
result = test_settings
{ success: result.present?, result: result }
end
def can_test? def can_test?
username.present? && password.present? username.present? && password.present?
end end
...@@ -182,24 +201,6 @@ class JiraService < IssueTrackerService ...@@ -182,24 +201,6 @@ class JiraService < IssueTrackerService
end end
end end
def close_issue(entity, issue)
return if issue.nil? || issue.resolution.present? || !jira_issue_transition_id.present?
commit_id = if entity.is_a?(Commit)
entity.id
elsif entity.is_a?(MergeRequest)
entity.diff_head_sha
end
commit_url = build_entity_url(:commit, commit_id)
# Depending on the JIRA project's workflow, a comment during transition
# may or may not be allowed. Refresh the issue after transition and check
# if it is closed, so we don't have one comment for every commit.
issue = jira_request { client.Issue.find(issue.key) } if transition_issue(issue)
add_issue_solved_comment(issue, commit_id, commit_url) if issue.resolution
end
def transition_issue(issue) def transition_issue(issue)
issue.transitions.build.save(transition: { id: jira_issue_transition_id }) issue.transitions.build.save(transition: { id: jira_issue_transition_id })
end end
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment