Commit ad19de19 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'master' into auto-pipelines-vue

* master: (114 commits)
  Remove dashboard.scss
  Update custom_hooks.md for global custom hooks and chained hook info
  Move admin hooks spinach to rspec
  Move admin logs spinach test to rspec
  Fix 404 error when visit group label edit page
  A simpler implementation of finding a merge request
  Encourage bug reporters to mention if they use GitLab.com
  Bump gitlab-shell version to 4.0.3
  Remove confirmation.scss
  Explain "js: true" in "deleted_source_branch_spec.rb"
  Move award emojis to framwork
  Move image styles to framework
  Remove tags.scss
  Remove caching of Repository#has_visible_content?
  Add text example for pipeline status without action
  Use detailed status as CSS class in pipelines list
  Use status text when redering pipelines list
  Improve support for icons in new detailed statuses
  Add status label information to pipeline header
  Untangle status label and text in ci status helper
  ...
parents f55fcef3 e09c6df0
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3-git-2.7-phantomjs-2.1" image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-git-2.7-phantomjs-2.1-node-7.1"
cache: cache:
key: "ruby-231" key: "ruby-233"
paths: paths:
- vendor/ruby - vendor/ruby
...@@ -235,7 +235,7 @@ rake ee_compat_check: ...@@ -235,7 +235,7 @@ rake ee_compat_check:
- /^[\d-]+-stable(-ee)?$/ - /^[\d-]+-stable(-ee)?$/
allow_failure: yes allow_failure: yes
cache: cache:
key: "ruby231-ee_compat_check_repo" key: "ruby233-ee_compat_check_repo"
paths: paths:
- ee_compat_check/repo/ - ee_compat_check/repo/
- vendor/ruby - vendor/ruby
...@@ -277,8 +277,6 @@ teaspoon: ...@@ -277,8 +277,6 @@ teaspoon:
stage: test stage: test
<<: *use-db <<: *use-db
script: script:
- curl --silent --location https://deb.nodesource.com/setup_6.x | bash -
- apt-get install --assume-yes nodejs
- npm install - npm install
- npm link istanbul - npm link istanbul
- rake teaspoon - rake teaspoon
......
...@@ -21,6 +21,8 @@ logs, and code as it's very hard to read otherwise.) ...@@ -21,6 +21,8 @@ logs, and code as it's very hard to read otherwise.)
### Output of checks ### Output of checks
(If you are reporting a bug on GitLab.com, write: This bug happens on GitLab.com)
#### Results of GitLab application Check #### Results of GitLab application Check
(For installations with omnibus-gitlab package run and paste the output of: (For installations with omnibus-gitlab package run and paste the output of:
......
...@@ -2,6 +2,15 @@ ...@@ -2,6 +2,15 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 8.14.3 (2016-12-02)
- Pass commit data to ProcessCommitWorker to reduce Git overhead. !7744
- Speed up issuable dashboards.
- Don't change relative URLs to absolute URLs in the Help page.
- Fixes "ActionView::Template::Error: undefined method `text?` for nil:NilClass" on MR pages.
- Fix branch validation for GitHub PR where repo/fork was renamed/deleted.
- Validate state param when filtering issuables.
## 8.14.2 (2016-12-01) ## 8.14.2 (2016-12-01)
- Remove caching of events data. !6578 - Remove caching of events data. !6578
...@@ -242,6 +251,11 @@ entry. ...@@ -242,6 +251,11 @@ entry.
- Fix "Without projects" filter. !6611 (Ben Bodenmiller) - Fix "Without projects" filter. !6611 (Ben Bodenmiller)
- Fix 404 when visit /projects page - Fix 404 when visit /projects page
## 8.13.8 (2016-12-02)
- Pass tag SHA to post-receive hook when tag is created via UI. !7700
- Validate state param when filtering issuables.
## 8.13.7 (2016-11-28) ## 8.13.7 (2016-11-28)
- fixes 500 error on project show when user is not logged in and project is still empty. !7376 - fixes 500 error on project show when user is not logged in and project is still empty. !7376
......
...@@ -264,7 +264,7 @@ group :development do ...@@ -264,7 +264,7 @@ group :development do
end end
group :development, :test do group :development, :test do
gem 'byebug', '~> 8.2.1', platform: :mri gem 'pry-byebug', '~> 3.4.1', platform: :mri
gem 'pry-rails', '~> 0.3.4' gem 'pry-rails', '~> 0.3.4'
gem 'awesome_print', '~> 1.2.0', require: false gem 'awesome_print', '~> 1.2.0', require: false
...@@ -338,7 +338,7 @@ gem 'ruby-prof', '~> 0.16.2' ...@@ -338,7 +338,7 @@ gem 'ruby-prof', '~> 0.16.2'
gem 'oauth2', '~> 1.2.0' gem 'oauth2', '~> 1.2.0'
# Soft deletion # Soft deletion
gem 'paranoia', '~> 2.0' gem 'paranoia', '~> 2.2'
# Health check # Health check
gem 'health_check', '~> 2.2.0' gem 'health_check', '~> 2.2.0'
......
...@@ -91,7 +91,7 @@ GEM ...@@ -91,7 +91,7 @@ GEM
bundler-audit (0.5.0) bundler-audit (0.5.0)
bundler (~> 1.2) bundler (~> 1.2)
thor (~> 0.18) thor (~> 0.18)
byebug (8.2.1) byebug (9.0.6)
capybara (2.6.2) capybara (2.6.2)
addressable addressable
mime-types (>= 1.16) mime-types (>= 1.16)
...@@ -460,8 +460,8 @@ GEM ...@@ -460,8 +460,8 @@ GEM
org-ruby (0.9.12) org-ruby (0.9.12)
rubypants (~> 0.2) rubypants (~> 0.2)
orm_adapter (0.5.0) orm_adapter (0.5.0)
paranoia (2.1.4) paranoia (2.2.0)
activerecord (~> 4.0) activerecord (>= 4.0, < 5.1)
parser (2.3.1.4) parser (2.3.1.4)
ast (~> 2.2) ast (~> 2.2)
pg (0.18.4) pg (0.18.4)
...@@ -483,6 +483,9 @@ GEM ...@@ -483,6 +483,9 @@ GEM
coderay (~> 1.1.0) coderay (~> 1.1.0)
method_source (~> 0.8.1) method_source (~> 0.8.1)
slop (~> 3.4) slop (~> 3.4)
pry-byebug (3.4.1)
byebug (~> 9.0)
pry (~> 0.10)
pry-rails (0.3.4) pry-rails (0.3.4)
pry (>= 0.9.10) pry (>= 0.9.10)
pyu-ruby-sasl (0.0.3.3) pyu-ruby-sasl (0.0.3.3)
...@@ -796,7 +799,6 @@ DEPENDENCIES ...@@ -796,7 +799,6 @@ DEPENDENCIES
browser (~> 2.2) browser (~> 2.2)
bullet (~> 5.2.0) bullet (~> 5.2.0)
bundler-audit (~> 0.5.0) bundler-audit (~> 0.5.0)
byebug (~> 8.2.1)
capybara (~> 2.6.2) capybara (~> 2.6.2)
capybara-screenshot (~> 1.0.0) capybara-screenshot (~> 1.0.0)
carrierwave (~> 0.10.0) carrierwave (~> 0.10.0)
...@@ -887,10 +889,11 @@ DEPENDENCIES ...@@ -887,10 +889,11 @@ DEPENDENCIES
omniauth-twitter (~> 1.2.0) omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0) omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12) org-ruby (~> 0.9.12)
paranoia (~> 2.0) paranoia (~> 2.2)
pg (~> 0.18.2) pg (~> 0.18.2)
poltergeist (~> 1.9.0) poltergeist (~> 1.9.0)
premailer-rails (~> 1.9.0) premailer-rails (~> 1.9.0)
pry-byebug (~> 3.4.1)
pry-rails (~> 0.3.4) pry-rails (~> 0.3.4)
rack-attack (~> 4.4.1) rack-attack (~> 4.4.1)
rack-cors (~> 0.4.0) rack-cors (~> 0.4.0)
......
...@@ -76,7 +76,7 @@ GitLab is a Ruby on Rails application that runs on the following software: ...@@ -76,7 +76,7 @@ GitLab is a Ruby on Rails application that runs on the following software:
- Ubuntu/Debian/CentOS/RHEL - Ubuntu/Debian/CentOS/RHEL
- Ruby (MRI) 2.3 - Ruby (MRI) 2.3
- Git 2.7.4+ - Git 2.8.4+
- Redis 2.8+ - Redis 2.8+
- MySQL or PostgreSQL - MySQL or PostgreSQL
......
Copyright 2010, 2012, 2014 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, max-len, one-var, camelcase, one-var-declaration-per-line, no-unused-vars, no-unused-expressions, no-sequences, object-shorthand, comma-dangle, prefer-arrow-callback, semi, radix, padded-blocks, max-len */
(function() {
this.Diff = (function() {
var UNFOLD_COUNT;
UNFOLD_COUNT = 20;
function Diff() {
$('.files .diff-file').singleFileDiff();
this.filesCommentButton = $('.files .diff-file').filesCommentButton();
if (this.diffViewType() === 'parallel') {
$('.content-wrapper .container-fluid').removeClass('container-limited');
}
$(document).off('click', '.js-unfold');
$(document).on('click', '.js-unfold', (function(_this) {
return function(event) {
var line_number, link, file, offset, old_line, params, prev_new_line, prev_old_line, ref, ref1, since, target, to, unfold, unfoldBottom;
target = $(event.target);
unfoldBottom = target.hasClass('js-unfold-bottom');
unfold = true;
ref = _this.lineNumbers(target.parent()), old_line = ref[0], line_number = ref[1];
offset = line_number - old_line;
if (unfoldBottom) {
line_number += 1;
since = line_number;
to = line_number + UNFOLD_COUNT;
} else {
ref1 = _this.lineNumbers(target.parent().prev()), prev_old_line = ref1[0], prev_new_line = ref1[1];
line_number -= 1;
to = line_number;
if (line_number - UNFOLD_COUNT > prev_new_line + 1) {
since = line_number - UNFOLD_COUNT;
} else {
since = prev_new_line + 1;
unfold = false;
}
}
file = target.parents('.diff-file');
link = file.data('blob-diff-path');
params = {
since: since,
to: to,
bottom: unfoldBottom,
offset: offset,
unfold: unfold,
view: file.data('view')
};
return $.get(link, params, function(response) {
return target.parent().replaceWith(response);
});
};
})(this));
}
Diff.prototype.diffViewType = function() {
return $('.inline-parallel-buttons a.active').data('view-type');
}
Diff.prototype.lineNumbers = function(line) {
if (!line.children().length) {
return [0, 0];
}
return line.find('.diff-line-num').map(function() {
return parseInt($(this).data('linenumber'));
});
};
return Diff;
})();
}).call(this);
/* eslint-disable class-methods-use-this */
(() => {
const UNFOLD_COUNT = 20;
class Diff {
constructor() {
$('.files .diff-file').singleFileDiff();
$('.files .diff-file').filesCommentButton();
if (this.diffViewType() === 'parallel') {
$('.content-wrapper .container-fluid').removeClass('container-limited');
}
$(document)
.off('click', '.js-unfold, .diff-line-num a')
.on('click', '.js-unfold', this.handleClickUnfold.bind(this))
.on('click', '.diff-line-num a', this.handleClickLineNum.bind(this));
this.highlighSelectedLine();
}
handleClickUnfold(e) {
const $target = $(e.target);
// current babel config relies on iterators implementation, so we cannot simply do:
// const [oldLineNumber, newLineNumber] = this.lineNumbers($target.parent());
const ref = this.lineNumbers($target.parent());
const oldLineNumber = ref[0];
const newLineNumber = ref[1];
const offset = newLineNumber - oldLineNumber;
const bottom = $target.hasClass('js-unfold-bottom');
let since;
let to;
let unfold = true;
if (bottom) {
const lineNumber = newLineNumber + 1;
since = lineNumber;
to = lineNumber + UNFOLD_COUNT;
} else {
const lineNumber = newLineNumber - 1;
since = lineNumber - UNFOLD_COUNT;
to = lineNumber;
// make sure we aren't loading more than we need
const prevNewLine = this.lineNumbers($target.parent().prev())[1];
if (since <= prevNewLine + 1) {
since = prevNewLine + 1;
unfold = false;
}
}
const file = $target.parents('.diff-file');
const link = file.data('blob-diff-path');
const view = file.data('view');
const params = { since, to, bottom, offset, unfold, view };
$.get(link, params, response => $target.parent().replaceWith(response));
}
openAnchoredDiff(anchoredDiff, cb) {
const diffTitle = $(`#file-path-${anchoredDiff}`);
const diffFile = diffTitle.closest('.diff-file');
const nothingHereBlock = $('.nothing-here-block:visible', diffFile);
if (nothingHereBlock.length) {
diffFile.singleFileDiff(true, cb);
} else {
cb();
}
}
handleClickLineNum(e) {
const hash = $(e.currentTarget).attr('href');
e.preventDefault();
if (window.history.pushState) {
window.history.pushState(null, null, hash);
} else {
window.location.hash = hash;
}
this.highlighSelectedLine();
}
diffViewType() {
return $('.inline-parallel-buttons a.active').data('view-type');
}
lineNumbers(line) {
if (!line.children().length) {
return [0, 0];
}
return line.find('.diff-line-num').map((i, elm) => parseInt($(elm).data('linenumber'), 10));
}
highlighSelectedLine() {
const $diffFiles = $('.diff-file');
$diffFiles.find('.hll').removeClass('hll');
if (window.location.hash !== '') {
const hash = window.location.hash.replace('#', '');
$diffFiles
.find(`tr#${hash}:not(.match) td, td#${hash}, td[data-line-code="${hash}"]`)
.addClass('hll');
}
}
}
window.gl = window.gl || {};
window.gl.Diff = Diff;
})();
...@@ -61,7 +61,7 @@ ...@@ -61,7 +61,7 @@
new ZenMode(); new ZenMode();
break; break;
case 'projects:compare:show': case 'projects:compare:show':
new Diff(); new gl.Diff();
break; break;
case 'projects:issues:new': case 'projects:issues:new':
case 'projects:issues:edit': case 'projects:issues:edit':
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
break; break;
case 'projects:merge_requests:new': case 'projects:merge_requests:new':
case 'projects:merge_requests:edit': case 'projects:merge_requests:edit':
new Diff(); new gl.Diff();
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
new GLForm($('.merge-request-form')); new GLForm($('.merge-request-form'));
new IssuableForm($('.merge-request-form')); new IssuableForm($('.merge-request-form'));
...@@ -91,7 +91,7 @@ ...@@ -91,7 +91,7 @@
new GLForm($('.release-form')); new GLForm($('.release-form'));
break; break;
case 'projects:merge_requests:show': case 'projects:merge_requests:show':
new Diff(); new gl.Diff();
shortcut_handler = new ShortcutsIssuable(true); shortcut_handler = new ShortcutsIssuable(true);
new ZenMode(); new ZenMode();
new MergedButtons(); new MergedButtons();
...@@ -101,7 +101,7 @@ ...@@ -101,7 +101,7 @@
new MergedButtons(); new MergedButtons();
break; break;
case "projects:merge_requests:diffs": case "projects:merge_requests:diffs":
new Diff(); new gl.Diff();
new ZenMode(); new ZenMode();
new MergedButtons(); new MergedButtons();
break; break;
...@@ -117,7 +117,7 @@ ...@@ -117,7 +117,7 @@
break; break;
case 'projects:commit:show': case 'projects:commit:show':
new Commit(); new Commit();
new Diff(); new gl.Diff();
new ZenMode(); new ZenMode();
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
break; break;
......
...@@ -97,6 +97,19 @@ ...@@ -97,6 +97,19 @@
return $('body').data('page').split(':')[0]; return $('body').data('page').split(':')[0];
}; };
gl.utils.parseUrl = function (url) {
var parser = document.createElement('a');
parser.href = url;
return parser;
};
gl.utils.parseUrlPathname = function (url) {
var parsedUrl = gl.utils.parseUrl(url);
// parsedUrl.pathname will return an absolute path for Firefox and a relative path for IE11
// We have to make sure we always have an absolute path.
return parsedUrl.pathname.charAt(0) === '/' ? parsedUrl.pathname : '/' + parsedUrl.pathname;
};
gl.utils.isMetaKey = function(e) { gl.utils.isMetaKey = function(e) {
return e.metaKey || e.ctrlKey || e.altKey || e.shiftKey; return e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
}; };
......
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
if (window.mrTabs) { if (window.mrTabs) {
window.mrTabs.unbindEvents(); window.mrTabs.unbindEvents();
} }
window.mrTabs = new MergeRequestTabs(this.opts); window.mrTabs = new gl.MergeRequestTabs(this.opts);
}; };
MergeRequest.prototype.showAllCommits = function() { MergeRequest.prototype.showAllCommits = function() {
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>'; COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>';
function SingleFileDiff(file, forceLoad, cb) { function SingleFileDiff(file, forceLoad, cb) {
var clickTarget;
this.file = file; this.file = file;
this.toggleDiff = bind(this.toggleDiff, this); this.toggleDiff = bind(this.toggleDiff, this);
this.content = $('.diff-content', this.file); this.content = $('.diff-content', this.file);
...@@ -31,9 +32,9 @@ ...@@ -31,9 +32,9 @@
this.content.after(this.collapsedContent); this.content.after(this.collapsedContent);
this.$toggleIcon.addClass('fa-caret-down'); this.$toggleIcon.addClass('fa-caret-down');
} }
$('.file-title, .click-to-expand', this.file).on('click', this.toggleDiff); clickTarget = $('.file-title, .click-to-expand', this.file).on('click', this.toggleDiff);
if (forceLoad) { if (forceLoad) {
this.toggleDiff(null, cb); this.toggleDiff({ target: clickTarget }, cb);
} }
} }
......
@import "framework/fonts";
@import "framework/variables"; @import "framework/variables";
@import "framework/mixins"; @import "framework/mixins";
@import 'framework/tw_bootstrap_variables'; @import 'framework/tw_bootstrap_variables';
...@@ -41,3 +40,6 @@ ...@@ -41,3 +40,6 @@
@import "framework/blank"; @import "framework/blank";
@import "framework/wells.scss"; @import "framework/wells.scss";
@import "framework/page-header.scss"; @import "framework/page-header.scss";
@import "framework/awards.scss";
@import "framework/images.scss";
@import "framework/broadcast-messages";
...@@ -127,7 +127,7 @@ ...@@ -127,7 +127,7 @@
.award-control-icon { .award-control-icon {
float: left; float: left;
margin-right: 5px; margin-right: 5px;
font-size: 19px; font-size: 18px;
} }
.award-control-icon-loading { .award-control-icon-loading {
......
...@@ -32,14 +32,14 @@ ...@@ -32,14 +32,14 @@
.blank-state-title { .blank-state-title {
margin-top: 0; margin-top: 0;
margin-bottom: 5px; margin-bottom: 5px;
font-size: 19px; font-size: 18px;
font-weight: normal; font-weight: normal;
} }
.blank-state-text { .blank-state-text {
margin-top: 0; margin-top: 0;
margin-bottom: $gl-padding; margin-bottom: $gl-padding;
font-size: 15px; font-size: 14px;
> strong { > strong {
font-weight: 600; font-weight: 600;
......
.light-well {
background-color: $background-color;
padding: 15px;
}
.centered-light-block { .centered-light-block {
text-align: center; text-align: center;
color: $gl-gray; color: $gl-gray;
...@@ -274,6 +269,10 @@ ...@@ -274,6 +269,10 @@
} }
} }
.emoji-icon {
display: inline-block;
}
@media(max-width: $screen-xs-max) { @media(max-width: $screen-xs-max) {
margin-top: 50px; margin-top: 50px;
text-align: center; text-align: center;
......
.broadcast-message {
@extend .alert-warning;
padding: 10px;
text-align: center;
div,
p {
display: inline;
margin: 0;
a {
color: inherit;
text-decoration: underline;
}
}
}
.broadcast-message-preview {
@extend .broadcast-message;
margin-bottom: 20px;
}
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
.slead { .slead {
color: $common-gray; color: $common-gray;
font-size: 15px; font-size: 14px;
margin-bottom: 12px; margin-bottom: 12px;
font-weight: normal; font-weight: normal;
line-height: 24px; line-height: 24px;
...@@ -379,7 +379,9 @@ table { ...@@ -379,7 +379,9 @@ table {
border-top: 1px solid $border-color; border-top: 1px solid $border-color;
} }
.hide-bottom-border { border-bottom: none !important; } .hide-bottom-border {
border-bottom: none !important;
}
.gl-accessibility { .gl-accessibility {
&:focus { &:focus {
...@@ -396,3 +398,13 @@ table { ...@@ -396,3 +398,13 @@ table {
z-index: 1; z-index: 1;
} }
} }
.str-truncated {
&-60 {
@include str-truncated(60%);
}
&-100 {
@include str-truncated(100%);
}
}
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
padding: 6px 8px 6px 10px; padding: 6px 8px 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: 14px;
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;
...@@ -123,7 +123,7 @@ ...@@ -123,7 +123,7 @@
width: 240px; width: 240px;
margin-top: 2px; margin-top: 2px;
margin-bottom: 0; margin-bottom: 0;
font-size: 15px; font-size: 14px;
font-weight: normal; font-weight: normal;
padding: 8px 0; padding: 8px 0;
background-color: $dropdown-bg; background-color: $dropdown-bg;
...@@ -589,7 +589,7 @@ ...@@ -589,7 +589,7 @@
.ui-datepicker-title { .ui-datepicker-title {
color: $gl-gray; color: $gl-gray;
font-size: 15px; font-size: 14px;
line-height: 1; line-height: 1;
font-weight: normal; font-weight: normal;
} }
......
// Disabling "SpaceAfterPropertyColon" linter because the linter doesn't like
// the way the `src` property is formatted in this file.
// scss-lint:disable SpaceAfterPropertyColon
/* latin-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
src:
local('Source Sans Pro Light'),
local('SourceSansPro-Light'),
font-url('SourceSansPro-Light.ttf.woff2') format('woff2'),
font-url('SourceSansPro-Light.ttf.woff') format('woff');
}
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src:
local('Source Sans Pro'),
local('SourceSansPro-Regular'),
font-url('SourceSansPro-Regular.ttf.woff2') format('woff2'),
font-url('SourceSansPro-Regular.ttf.woff') format('woff');
}
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
src:
local('Source Sans Pro Semibold'),
local('SourceSansPro-Semibold'),
font-url('SourceSansPro-Semibold.ttf.woff2') format('woff2'),
font-url('SourceSansPro-Semibold.ttf.woff') format('woff');
}
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
src:
local('Source Sans Pro Bold'),
local('SourceSansPro-Bold'),
font-url('SourceSansPro-Bold.ttf.woff2') format('woff2'),
font-url('SourceSansPro-Bold.ttf.woff') format('woff');
}
...@@ -71,7 +71,7 @@ header { ...@@ -71,7 +71,7 @@ header {
} }
.fa-caret-down { .fa-caret-down {
font-size: 15px; font-size: 14px;
} }
} }
...@@ -156,7 +156,7 @@ header { ...@@ -156,7 +156,7 @@ header {
position: relative; position: relative;
padding-right: 20px; padding-right: 20px;
margin: 0; margin: 0;
font-size: 19px; font-size: 18px;
max-width: 385px; max-width: 385px;
display: inline-block; display: inline-block;
line-height: $header-height; line-height: $header-height;
......
...@@ -106,13 +106,13 @@ ul.task-list { ...@@ -106,13 +106,13 @@ ul.task-list {
} }
} }
// Generic content list
ul.content-list { ul.content-list {
@include basic-list; @include basic-list;
margin: 0; margin: 0;
padding: 0; padding: 0;
> li { li {
border-color: $table-border-color; border-color: $table-border-color;
font-size: $list-font-size; font-size: $list-font-size;
color: $list-text-color; color: $list-text-color;
...@@ -193,6 +193,41 @@ ul.content-list { ...@@ -193,6 +193,41 @@ ul.content-list {
} }
} }
// Content list using flexbox
.flex-list {
.flex-row {
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
white-space: nowrap;
}
.row-main-content {
flex: 1 1 auto;
overflow: hidden;
padding-right: 8px;
}
.row-title {
font-weight: 600;
}
.row-second-line {
display: block;
}
.dropdown {
.btn-block {
margin-bottom: 0;
line-height: inherit;
}
}
.label-default {
color: $btn-transparent-color;
}
}
.panel > .content-list > li { .panel > .content-list > li {
padding: $gl-padding-top $gl-padding; padding: $gl-padding-top $gl-padding;
......
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
padding: $gl-btn-padding; padding: $gl-btn-padding;
padding-bottom: 11px; padding-bottom: 11px;
margin-bottom: -1px; margin-bottom: -1px;
font-size: 15px; font-size: 14px;
line-height: 28px; line-height: 28px;
color: $note-toolbar-color; color: $note-toolbar-color;
border-bottom: 2px solid transparent; border-bottom: 2px solid transparent;
...@@ -268,6 +268,16 @@ ...@@ -268,6 +268,16 @@
width: auto; width: auto;
} }
} }
&.multi-line {
.nav-text {
line-height: 20px;
}
.nav-controls {
padding: 17px 0;
}
}
} }
.layout-nav { .layout-nav {
......
...@@ -34,6 +34,10 @@ table { ...@@ -34,6 +34,10 @@ table {
background-color: $background-color; background-color: $background-color;
font-weight: normal; font-weight: normal;
border-bottom: none; border-bottom: none;
&.wide {
width: 55%;
}
} }
td { td {
...@@ -42,3 +46,16 @@ table { ...@@ -42,3 +46,16 @@ table {
} }
} }
} }
.responsive-table {
@media (max-width: $screen-sm-max) {
th {
width: 100%;
}
td {
width: 100%;
float: left;
}
}
}
...@@ -102,7 +102,7 @@ $well-light-text-color: #5b6169; ...@@ -102,7 +102,7 @@ $well-light-text-color: #5b6169;
/* /*
* Text * Text
*/ */
$gl-font-size: 15px; $gl-font-size: 14px;
$gl-title-color: #333; $gl-title-color: #333;
$gl-text-color: #5c5c5c; $gl-text-color: #5c5c5c;
$gl-text-color-dark: #5c5d5e; $gl-text-color-dark: #5c5d5e;
...@@ -266,7 +266,7 @@ $diff-view-modes-border: #c1c1c1; ...@@ -266,7 +266,7 @@ $diff-view-modes-border: #c1c1c1;
* Fonts * Fonts
*/ */
$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace; $monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
$regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif; $regular_font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
/* /*
* Dropdowns * Dropdowns
...@@ -380,7 +380,7 @@ $ci-skipped-color: #888; ...@@ -380,7 +380,7 @@ $ci-skipped-color: #888;
/* /*
* Boards * Boards
*/ */
$issue-boards-font-size: 15px; $issue-boards-font-size: 14px;
$issue-boards-card-shadow: rgba(186, 186, 186, 0.5); $issue-boards-card-shadow: rgba(186, 186, 186, 0.5);
/* /*
...@@ -427,12 +427,6 @@ $common-gray-dark: #444; ...@@ -427,12 +427,6 @@ $common-gray-dark: #444;
$common-red: $gl-text-red; $common-red: $gl-text-red;
$common-green: $gl-text-green; $common-green: $gl-text-green;
/*
* Dashboard
*/
$dashboard-project-access-icon-color: #888;
/* /*
* Editor * Editor
*/ */
......
...@@ -43,3 +43,16 @@ ...@@ -43,3 +43,16 @@
background-color: $well-expand-item; background-color: $well-expand-item;
} }
} }
.light-well {
background-color: $background-color;
padding: 15px;
}
.well-centered {
h1 {
font-weight: normal;
text-align: center;
font-size: 48px;
}
}
/**
* Admin area
*
*/
.admin-dashboard {
.data {
a {
h1 {
line-height: 48px;
font-size: 48px;
padding: 20px;
text-align: center;
font-weight: normal;
}
}
}
.str-truncated {
max-width: 60%;
}
}
.admin-filter form {
.select2-container {
width: 100%;
}
.controls {
margin-left: 130px;
}
.form-actions {
padding-left: 130px;
background: $white-light;
}
.visibility-levels {
.controls {
margin-bottom: 9px;
}
i {
color: inherit;
}
}
}
.broadcast-messages {
.message {
line-height: 2;
}
}
.broadcast-message {
@extend .alert-warning;
padding: 10px;
text-align: center;
> div,
p {
display: inline;
margin: 0;
a {
color: inherit;
text-decoration: underline;
}
}
}
.broadcast-message-preview {
@extend .broadcast-message;
margin-bottom: 20px;
}
// Users List
.users-list {
.user-row {
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
white-space: nowrap;
}
.user-details {
flex: 1 1 auto;
overflow: hidden;
padding-right: 8px;
}
.user-name {
display: inline-block;
font-weight: 600;
}
.user-name,
.user-email {
overflow: hidden;
text-overflow: ellipsis;
}
.dropdown {
.btn-block {
margin-bottom: 0;
line-height: inherit;
}
}
.label-default {
color: $btn-transparent-color;
}
}
.abuse-reports {
.table {
table-layout: fixed;
}
.subheading {
padding-bottom: $gl-padding;
}
.message {
word-wrap: break-word;
}
.btn {
white-space: normal;
padding: $gl-btn-padding;
}
th {
width: 15%;
&.wide {
width: 55%;
}
}
@media (max-width: $screen-sm-max) {
th {
width: 100%;
}
td {
width: 100%;
float: left;
}
}
.no-reports {
.emoji-icon {
margin-left: $btn-side-margin;
margin-top: 3px;
}
span {
font-size: 19px;
}
}
}
.admin-builds-table {
.ci-table td:last-child {
min-width: 120px;
}
}
...@@ -64,6 +64,7 @@ ...@@ -64,6 +64,7 @@
@media (max-width: $screen-sm-max) { @media (max-width: $screen-sm-max) {
padding-right: 40px; padding-right: 40px;
margin-top: 6px;
.btn-inverted { .btn-inverted {
display: none; display: none;
......
.well-confirmation {
margin-bottom: 20px;
border-bottom: 1px solid $gray-darker;
> h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 400;
}
.lead {
margin-bottom: 20px;
}
ul,
ol {
padding-left: 0;
}
li {
list-style-type: none;
}
}
.confirmation-content {
a {
color: $md-link-color;
}
}
...@@ -109,7 +109,7 @@ ...@@ -109,7 +109,7 @@
&.title { &.title {
line-height: 19px; line-height: 19px;
font-size: 15px; font-size: 14px;
font-weight: 600; font-weight: 600;
color: $gl-title-color; color: $gl-title-color;
} }
......
.dashboard {
.side {
.panel {
.panel-heading {
background: $background-color;
border-top-left-radius: 0;
}
border-top-left-radius: 0;
}
}
}
.dashboard-search-filter {
padding: 5px;
.search-text-input {
float: left;
@extend .col-md-2;
}
.btn {
margin-left: 5px;
float: left;
}
}
.project-access-icon {
margin-left: 10px;
float: left;
margin-right: 15px;
margin-bottom: 15px;
i {
color: $dashboard-project-access-icon-color;
}
}
.dash-project-access-icon {
float: left;
margin-right: 5px;
width: 16px;
}
.error-page {
max-width: 400px;
margin: 0 auto;
h1,
h2,
h3 {
text-align: center;
}
h1 {
font-size: 56px;
line-height: 100px;
font-weight: 300;
}
}
.ci-body { .ci-body {
.incorrect-syntax { .incorrect-syntax {
font-size: 19px; font-size: 18px;
color: $lint-incorrect-color; color: $lint-incorrect-color;
} }
.correct-syntax { .correct-syntax {
font-size: 19px; font-size: 18px;
color: $lint-correct-color; color: $lint-correct-color;
} }
} }
...@@ -116,7 +116,7 @@ ...@@ -116,7 +116,7 @@
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
h4 { h4 {
font-size: 15px; font-size: 14px;
} }
p { p {
...@@ -129,6 +129,11 @@ ...@@ -129,6 +129,11 @@
margin-bottom: 4px; margin-bottom: 4px;
} }
.btn-grouped {
float: none;
margin-right: 0;
}
.accept-action { .accept-action {
width: 100%; width: 100%;
text-align: center; text-align: center;
......
...@@ -129,7 +129,7 @@ ...@@ -129,7 +129,7 @@
.note-edit-form { .note-edit-form {
display: none; display: none;
font-size: 15px; font-size: 14px;
.md-area { .md-area {
background-color: $white-light; background-color: $white-light;
......
...@@ -283,6 +283,12 @@ ...@@ -283,6 +283,12 @@
} }
} }
.admin-builds-table {
.ci-table td:last-child {
min-width: 120px;
}
}
// Pipeline visualization // Pipeline visualization
.toggle-pipeline-btn { .toggle-pipeline-btn {
......
...@@ -521,7 +521,7 @@ a.deploy-project-label { ...@@ -521,7 +521,7 @@ a.deploy-project-label {
.nav > li > a { .nav > li > a {
padding: 0; padding: 0;
background-color: transparent; background-color: transparent;
font-size: 15px; font-size: 14px;
line-height: 29px; line-height: 29px;
color: $notes-light-color; color: $notes-light-color;
......
.tag-buttons {
line-height: 40px;
.btn:not(.dropdown-toggle) {
margin-left: 10px;
}
}
.votes-inline {
display: inline-block;
margin: 0 8px;
}
...@@ -6,7 +6,12 @@ module MergeRequestsAction ...@@ -6,7 +6,12 @@ module MergeRequestsAction
@label = merge_requests_finder.labels.first @label = merge_requests_finder.labels.first
@merge_requests = merge_requests_collection @merge_requests = merge_requests_collection
.non_archived
.page(params[:page]) .page(params[:page])
end end
private
def filter_params
super.merge(non_archived: true)
end
end end
...@@ -5,9 +5,7 @@ class Projects::DiscussionsController < Projects::ApplicationController ...@@ -5,9 +5,7 @@ class Projects::DiscussionsController < Projects::ApplicationController
before_action :authorize_resolve_discussion! before_action :authorize_resolve_discussion!
def resolve def resolve
discussion.resolve!(current_user) Discussions::ResolveService.new(project, current_user, merge_request: merge_request).execute(discussion)
MergeRequests::ResolvedDiscussionNotificationService.new(project, current_user).execute(merge_request)
render json: { render json: {
resolved_by: discussion.resolved_by.try(:name), resolved_by: discussion.resolved_by.try(:name),
......
...@@ -46,8 +46,9 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -46,8 +46,9 @@ class Projects::IssuesController < Projects::ApplicationController
params[:issue] ||= ActionController::Parameters.new( params[:issue] ||= ActionController::Parameters.new(
assignee_id: "" assignee_id: ""
) )
build_params = issue_params.merge(merge_request_for_resolving_discussions: merge_request_for_resolving_discussions)
@issue = @noteable = Issues::BuildService.new(project, current_user, build_params).execute
@issue = @noteable = @project.issues.new(issue_params)
respond_with(@issue) respond_with(@issue)
end end
...@@ -75,7 +76,9 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -75,7 +76,9 @@ class Projects::IssuesController < Projects::ApplicationController
end end
def create def create
@issue = Issues::CreateService.new(project, current_user, issue_params.merge(request: request)).execute extra_params = { request: request,
merge_request_for_resolving_discussions: merge_request_for_resolving_discussions }
@issue = Issues::CreateService.new(project, current_user, issue_params.merge(extra_params)).execute
respond_to do |format| respond_to do |format|
format.html do format.html do
...@@ -169,6 +172,14 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -169,6 +172,14 @@ class Projects::IssuesController < Projects::ApplicationController
alias_method :awardable, :issue alias_method :awardable, :issue
alias_method :spammable, :issue alias_method :spammable, :issue
def merge_request_for_resolving_discussions
return unless merge_request_iid = params[:merge_request_for_resolving_discussions]
@merge_request_for_resolving_discussions ||= MergeRequestsFinder.new(current_user, project_id: project.id).
execute.
find_by(iid: merge_request_iid)
end
def authorize_read_issue! def authorize_read_issue!
return render_404 unless can?(current_user, :read_issue, @issue) return render_404 unless can?(current_user, :read_issue, @issue)
end end
......
...@@ -302,9 +302,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -302,9 +302,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def cancel_merge_when_build_succeeds def cancel_merge_when_build_succeeds
return access_denied! unless @merge_request.can_cancel_merge_when_build_succeeds?(current_user) unless @merge_request.can_cancel_merge_when_build_succeeds?(current_user)
return access_denied!
end
MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user).cancel(@merge_request) MergeRequests::MergeWhenPipelineSucceedsService
.new(@project, current_user)
.cancel(@merge_request)
end end
def merge def merge
...@@ -331,8 +335,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -331,8 +335,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
if @merge_request.head_pipeline.active? if @merge_request.head_pipeline.active?
MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params) MergeRequests::MergeWhenPipelineSucceedsService
.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.head_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
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
# current_user - which user use # current_user - which user use
# params: # params:
# scope: 'created-by-me' or 'assigned-to-me' or 'all' # scope: 'created-by-me' or 'assigned-to-me' or 'all'
# state: 'open' or 'closed' or 'all' # state: 'opened' or 'closed' or 'all'
# group_id: integer # group_id: integer
# project_id: integer # project_id: integer
# milestone_title: string # milestone_title: string
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
# search: string # search: string
# label_name: string # label_name: string
# sort: string # sort: string
# non_archived: boolean
# #
class IssuableFinder class IssuableFinder
NONE = '0' NONE = '0'
...@@ -38,6 +39,7 @@ class IssuableFinder ...@@ -38,6 +39,7 @@ class IssuableFinder
items = by_author(items) items = by_author(items)
items = by_label(items) items = by_label(items)
items = by_due_date(items) items = by_due_date(items)
items = by_non_archived(items)
sort(items) sort(items)
end end
...@@ -207,10 +209,13 @@ class IssuableFinder ...@@ -207,10 +209,13 @@ class IssuableFinder
end end
def by_state(items) def by_state(items)
params[:state] ||= 'all' case params[:state].to_s
when 'closed'
if items.respond_to?(params[:state]) items.closed
items.public_send(params[:state]) when 'merged'
items.respond_to?(:merged) ? items.merged : items.closed
when 'opened'
items.opened
else else
items items
end end
...@@ -353,6 +358,10 @@ class IssuableFinder ...@@ -353,6 +358,10 @@ class IssuableFinder
end end
end end
def by_non_archived(items)
params[:non_archived].present? ? items.non_archived : items
end
def current_user_related? def current_user_related?
params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me' params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
end end
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
# search: string # search: string
# label_name: string # label_name: string
# sort: string # sort: string
# non_archived: boolean
# #
class MergeRequestsFinder < IssuableFinder class MergeRequestsFinder < IssuableFinder
def klass def klass
......
...@@ -5,8 +5,9 @@ module CiStatusHelper ...@@ -5,8 +5,9 @@ module CiStatusHelper
end end
def ci_status_with_icon(status, target = nil) def ci_status_with_icon(status, target = nil)
content = ci_icon_for_status(status) + ci_label_for_status(status) content = ci_icon_for_status(status) + ci_text_for_status(status)
klass = "ci-status ci-#{status}" klass = "ci-status ci-#{status}"
if target if target
link_to content, target, class: klass link_to content, target, class: klass
else else
...@@ -14,7 +15,19 @@ module CiStatusHelper ...@@ -14,7 +15,19 @@ module CiStatusHelper
end end
end end
def ci_text_for_status(status)
if detailed_status?(status)
status.text
else
status
end
end
def ci_label_for_status(status) def ci_label_for_status(status)
if detailed_status?(status)
return status.label
end
case status case status
when 'success' when 'success'
'passed' 'passed'
...@@ -31,6 +44,10 @@ module CiStatusHelper ...@@ -31,6 +44,10 @@ module CiStatusHelper
end end
def ci_icon_for_status(status) def ci_icon_for_status(status)
if detailed_status?(status)
return custom_icon(status.icon)
end
icon_name = icon_name =
case status case status
when 'success' when 'success'
...@@ -94,4 +111,10 @@ module CiStatusHelper ...@@ -94,4 +111,10 @@ module CiStatusHelper
class: klass, title: title, data: data class: klass, title: title, data: data
end end
end end
def detailed_status?(status)
status.respond_to?(:text) &&
status.respond_to?(:label) &&
status.respond_to?(:icon)
end
end end
...@@ -174,7 +174,7 @@ module GitlabMarkdownHelper ...@@ -174,7 +174,7 @@ module GitlabMarkdownHelper
# Returns a String # Returns a String
def cross_project_reference(project, entity) def cross_project_reference(project, entity)
if entity.respond_to?(:to_reference) if entity.respond_to?(:to_reference)
"#{project.to_reference}#{entity.to_reference}" entity.to_reference(project)
else else
'' ''
end end
......
...@@ -82,12 +82,6 @@ module LabelsHelper ...@@ -82,12 +82,6 @@ module LabelsHelper
span.html_safe span.html_safe
end end
def render_colored_cross_project_label(label, source_project = nil, tooltip: true)
label_suffix = source_project ? source_project.name_with_namespace : label.project.name_with_namespace
label_suffix = " <i>in #{escape_once(label_suffix)}</i>"
render_colored_label(label, label_suffix, tooltip: tooltip)
end
def suggested_colors def suggested_colors
[ [
'#0033CC', '#0033CC',
...@@ -166,6 +160,5 @@ module LabelsHelper ...@@ -166,6 +160,5 @@ module LabelsHelper
end end
# Required for Banzai::Filter::LabelReferenceFilter # Required for Banzai::Filter::LabelReferenceFilter
module_function :render_colored_label, :render_colored_cross_project_label, module_function :render_colored_label, :text_color_for_bg, :escape_once
:text_color_for_bg, :escape_once
end end
...@@ -391,20 +391,6 @@ module ProjectsHelper ...@@ -391,20 +391,6 @@ module ProjectsHelper
end end
end end
def new_readme_path
ref = @repository.root_ref if @repository
ref ||= 'master'
namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'README.md')
end
def new_license_path
ref = @repository.root_ref if @repository
ref ||= 'master'
namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'LICENSE')
end
def readme_cache_key def readme_cache_key
sha = @project.commit.try(:sha) || 'nil' sha = @project.commit.try(:sha) || 'nil'
[@project.path_with_namespace, sha, "readme"].join('-') [@project.path_with_namespace, sha, "readme"].join('-')
......
...@@ -341,6 +341,10 @@ module Ci ...@@ -341,6 +341,10 @@ module Ci
.select { |merge_request| merge_request.head_pipeline.try(:id) == self.id } .select { |merge_request| merge_request.head_pipeline.try(:id) == self.id }
end end
def detailed_status
Gitlab::Ci::Status::Pipeline::Factory.new(self).fabricate!
end
private private
def pipeline_data def pipeline_data
......
...@@ -4,10 +4,10 @@ module Ci ...@@ -4,10 +4,10 @@ module Ci
belongs_to :project, foreign_key: :gl_project_id belongs_to :project, foreign_key: :gl_project_id
validates_uniqueness_of :key, scope: :gl_project_id
validates :key, validates :key,
presence: true, presence: true,
length: { within: 0..255 }, uniqueness: { scope: :gl_project_id },
length: { maximum: 255 },
format: { with: /\A[a-zA-Z0-9_]+\z/, format: { with: /\A[a-zA-Z0-9_]+\z/,
message: "can contain only letters, digits and '_'." } message: "can contain only letters, digits and '_'." }
......
...@@ -92,19 +92,11 @@ class Commit ...@@ -92,19 +92,11 @@ class Commit
end end
def to_reference(from_project = nil) def to_reference(from_project = nil)
if cross_project_reference?(from_project) commit_reference(from_project, id)
project.to_reference + self.class.reference_prefix + self.id
else
self.id
end
end end
def reference_link_text(from_project = nil) def reference_link_text(from_project = nil)
if cross_project_reference?(from_project) commit_reference(from_project, short_id)
project.to_reference + self.class.reference_prefix + self.short_id
else
self.short_id
end
end end
def diff_line_count def diff_line_count
...@@ -329,6 +321,16 @@ class Commit ...@@ -329,6 +321,16 @@ class Commit
private private
def commit_reference(from_project, referable_commit_id)
reference = project.to_reference(from_project)
if reference.present?
"#{reference}#{self.class.reference_prefix}#{referable_commit_id}"
else
referable_commit_id
end
end
def find_author_by_any_email def find_author_by_any_email
User.find_by_any_email(author_email.downcase) User.find_by_any_email(author_email.downcase)
end end
......
...@@ -90,22 +90,25 @@ class CommitRange ...@@ -90,22 +90,25 @@ class CommitRange
alias_method :id, :to_s alias_method :id, :to_s
def to_reference(from_project = nil) def to_reference(from_project = nil)
if cross_project_reference?(from_project) project_reference = project.to_reference(from_project)
project.to_reference + self.class.reference_prefix + self.id
if project_reference.present?
project_reference + self.class.reference_prefix + self.id
else else
self.id self.id
end end
end end
def reference_link_text(from_project = nil) def reference_link_text(from_project = nil)
project_reference = project.to_reference(from_project)
reference = ref_from + notation + ref_to reference = ref_from + notation + ref_to
if cross_project_reference?(from_project) if project_reference.present?
reference = project.to_reference + self.class.reference_prefix + reference project_reference + self.class.reference_prefix + reference
end else
reference reference
end end
end
# Return a Hash of parameters for passing to a URL helper # Return a Hash of parameters for passing to a URL helper
# #
......
...@@ -41,7 +41,7 @@ module Issuable ...@@ -41,7 +41,7 @@ module Issuable
has_one :metrics has_one :metrics
validates :author, presence: true validates :author, presence: true
validates :title, presence: true, length: { within: 0..255 } validates :title, presence: true, length: { maximum: 255 }
scope :authored, ->(user) { where(author_id: user) } scope :authored, ->(user) { where(author_id: user) }
scope :assigned_to, ->(u) { where(assignee_id: u.id)} scope :assigned_to, ->(u) { where(assignee_id: u.id)}
......
...@@ -72,17 +72,4 @@ module Referable ...@@ -72,17 +72,4 @@ module Referable
}x }x
end end
end end
private
# Check if a reference is being done cross-project
#
# from_project - Refering Project object
def cross_project_reference?(from_project)
if self.is_a?(Project)
self != from_project
else
from_project && self.project && self.project != from_project
end
end
end end
...@@ -88,6 +88,10 @@ class Discussion ...@@ -88,6 +88,10 @@ class Discussion
@first_note ||= @notes.first @first_note ||= @notes.first
end end
def first_note_to_resolve
@first_note_to_resolve ||= notes.detect(&:to_be_resolved?)
end
def last_note def last_note
@last_note ||= @notes.last @last_note ||= @notes.last
end end
......
...@@ -9,7 +9,7 @@ class Environment < ActiveRecord::Base ...@@ -9,7 +9,7 @@ class Environment < ActiveRecord::Base
validates :name, validates :name,
presence: true, presence: true,
uniqueness: { scope: :project_id }, uniqueness: { scope: :project_id },
length: { within: 0..255 }, length: { maximum: 255 },
format: { with: Gitlab::Regex.environment_name_regex, format: { with: Gitlab::Regex.environment_name_regex,
message: Gitlab::Regex.environment_name_regex_message } message: Gitlab::Regex.environment_name_regex_message }
......
...@@ -153,11 +153,7 @@ class Issue < ActiveRecord::Base ...@@ -153,11 +153,7 @@ class Issue < ActiveRecord::Base
def to_reference(from_project = nil) def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}" reference = "#{self.class.reference_prefix}#{iid}"
if cross_project_reference?(from_project) "#{project.to_reference(from_project)}#{reference}"
reference = project.to_reference + reference
end
reference
end end
def referenced_merge_requests(current_user = nil) def referenced_merge_requests(current_user = nil)
......
...@@ -8,10 +8,18 @@ class Key < ActiveRecord::Base ...@@ -8,10 +8,18 @@ class Key < ActiveRecord::Base
before_validation :generate_fingerprint before_validation :generate_fingerprint
validates :title, presence: true, length: { within: 0..255 } validates :title,
validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ } presence: true,
validates :key, format: { without: /\n|\r/, message: 'should be a single line' } length: { maximum: 255 }
validates :fingerprint, uniqueness: true, presence: { message: 'cannot be generated' } validates :key,
presence: true,
length: { maximum: 5000 },
format: { with: /\A(ssh|ecdsa)-.*\Z/ }
validates :key,
format: { without: /\n|\r/, message: 'should be a single line' }
validates :fingerprint,
uniqueness: true,
presence: { message: 'cannot be generated' }
delegate :name, :email, to: :user, prefix: true delegate :name, :email, to: :user, prefix: true
......
...@@ -146,7 +146,8 @@ class Label < ActiveRecord::Base ...@@ -146,7 +146,8 @@ class Label < ActiveRecord::Base
# #
# Label.first.to_reference # => "~1" # Label.first.to_reference # => "~1"
# Label.first.to_reference(format: :name) # => "~\"bug\"" # Label.first.to_reference(format: :name) # => "~\"bug\""
# Label.first.to_reference(project1, project2) # => "gitlab-org/gitlab-ce~1" # Label.first.to_reference(project, same_namespace_project) # => "gitlab-ce~1"
# Label.first.to_reference(project, another_namespace_project) # => "gitlab-org/gitlab-ce~1"
# #
# Returns a String # Returns a String
# #
...@@ -154,8 +155,8 @@ class Label < ActiveRecord::Base ...@@ -154,8 +155,8 @@ class Label < ActiveRecord::Base
format_reference = label_format_reference(format) format_reference = label_format_reference(format)
reference = "#{self.class.reference_prefix}#{format_reference}" reference = "#{self.class.reference_prefix}#{format_reference}"
if cross_project_reference?(source_project, target_project) if source_project
source_project.to_reference + reference "#{source_project.to_reference(target_project)}#{reference}"
else else
reference reference
end end
...@@ -169,10 +170,6 @@ class Label < ActiveRecord::Base ...@@ -169,10 +170,6 @@ class Label < ActiveRecord::Base
private private
def cross_project_reference?(source_project, target_project)
source_project && target_project && source_project != target_project
end
def issues_count(user, params = {}) def issues_count(user, params = {})
params.merge!(subject_foreign_key => subject.id, label_name: title, scope: 'all') params.merge!(subject_foreign_key => subject.id, label_name: title, scope: 'all')
IssuesFinder.new(user, params.with_indifferent_access).execute.count IssuesFinder.new(user, params.with_indifferent_access).execute.count
......
...@@ -63,6 +63,7 @@ class Member < ActiveRecord::Base ...@@ -63,6 +63,7 @@ class Member < ActiveRecord::Base
after_create :send_request, if: :request?, unless: :importing? after_create :send_request, if: :request?, unless: :importing?
after_create :create_notification_setting, unless: [:pending?, :importing?] after_create :create_notification_setting, unless: [:pending?, :importing?]
after_create :post_create_hook, unless: [:pending?, :importing?] after_create :post_create_hook, unless: [:pending?, :importing?]
after_create :refresh_member_authorized_projects, if: :importing?
after_update :post_update_hook, unless: [:pending?, :importing?] after_update :post_update_hook, unless: [:pending?, :importing?]
after_destroy :post_destroy_hook, unless: :pending? after_destroy :post_destroy_hook, unless: :pending?
......
...@@ -176,11 +176,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -176,11 +176,7 @@ class MergeRequest < ActiveRecord::Base
def to_reference(from_project = nil) def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}" reference = "#{self.class.reference_prefix}#{iid}"
if cross_project_reference?(from_project) "#{project.to_reference(from_project)}#{reference}"
reference = project.to_reference + reference
end
reference
end end
def first_commit def first_commit
...@@ -480,6 +476,14 @@ class MergeRequest < ActiveRecord::Base ...@@ -480,6 +476,14 @@ class MergeRequest < ActiveRecord::Base
@diff_discussions ||= self.notes.diff_notes.discussions @diff_discussions ||= self.notes.diff_notes.discussions
end end
def resolvable_discussions
@resolvable_discussions ||= diff_discussions.select(&:to_be_resolved?)
end
def discussions_can_be_resolved_by?(user)
resolvable_discussions.all? { |discussion| discussion.can_resolve?(user) }
end
def find_diff_discussion(discussion_id) def find_diff_discussion(discussion_id)
notes = self.notes.diff_notes.where(discussion_id: discussion_id).fresh.to_a notes = self.notes.diff_notes.where(discussion_id: discussion_id).fresh.to_a
return if notes.empty? return if notes.empty?
......
...@@ -115,17 +115,14 @@ class Milestone < ActiveRecord::Base ...@@ -115,17 +115,14 @@ class Milestone < ActiveRecord::Base
# #
# Milestone.first.to_reference # => "%1" # Milestone.first.to_reference # => "%1"
# Milestone.first.to_reference(format: :name) # => "%\"goal\"" # Milestone.first.to_reference(format: :name) # => "%\"goal\""
# Milestone.first.to_reference(project) # => "gitlab-org/gitlab-ce%1" # Milestone.first.to_reference(cross_namespace_project) # => "gitlab-org/gitlab-ce%1"
# Milestone.first.to_reference(same_namespace_project) # => "gitlab-ce%1"
# #
def to_reference(from_project = nil, format: :iid) def to_reference(from_project = nil, format: :iid)
format_reference = milestone_format_reference(format) format_reference = milestone_format_reference(format)
reference = "#{self.class.reference_prefix}#{format_reference}" reference = "#{self.class.reference_prefix}#{format_reference}"
if cross_project_reference?(from_project) "#{project.to_reference(from_project)}#{reference}"
project.to_reference + reference
else
reference
end
end end
def reference_link_text(from_project = nil) def reference_link_text(from_project = nil)
......
...@@ -12,17 +12,17 @@ class Namespace < ActiveRecord::Base ...@@ -12,17 +12,17 @@ class Namespace < ActiveRecord::Base
validates :owner, presence: true, unless: ->(n) { n.type == "Group" } validates :owner, presence: true, unless: ->(n) { n.type == "Group" }
validates :name, validates :name,
length: { within: 0..255 },
namespace_name: true,
presence: true, presence: true,
uniqueness: true uniqueness: true,
length: { maximum: 255 },
namespace_name: true
validates :description, length: { within: 0..255 } validates :description, length: { maximum: 255 }
validates :path, validates :path,
length: { within: 1..255 },
namespace: true,
presence: true, presence: true,
uniqueness: { case_sensitive: false } uniqueness: { case_sensitive: false },
length: { maximum: 255 },
namespace: true
delegate :name, to: :owner, allow_nil: true, prefix: true delegate :name, to: :owner, allow_nil: true, prefix: true
......
...@@ -99,7 +99,7 @@ class Note < ActiveRecord::Base ...@@ -99,7 +99,7 @@ class Note < ActiveRecord::Base
end end
def discussions def discussions
Discussion.for_notes(all) Discussion.for_notes(fresh)
end end
def grouped_diff_discussions def grouped_diff_discussions
......
...@@ -172,13 +172,13 @@ class Project < ActiveRecord::Base ...@@ -172,13 +172,13 @@ class Project < ActiveRecord::Base
validates :description, length: { maximum: 2000 }, allow_blank: true validates :description, length: { maximum: 2000 }, allow_blank: true
validates :name, validates :name,
presence: true, presence: true,
length: { within: 0..255 }, length: { maximum: 255 },
format: { with: Gitlab::Regex.project_name_regex, format: { with: Gitlab::Regex.project_name_regex,
message: Gitlab::Regex.project_name_regex_message } message: Gitlab::Regex.project_name_regex_message }
validates :path, validates :path,
presence: true, presence: true,
project_path: true, project_path: true,
length: { within: 0..255 }, length: { maximum: 255 },
format: { with: Gitlab::Regex.project_path_regex, format: { with: Gitlab::Regex.project_path_regex,
message: Gitlab::Regex.project_path_regex_message } message: Gitlab::Regex.project_path_regex_message }
validates :namespace, presence: true validates :namespace, presence: true
...@@ -419,7 +419,11 @@ class Project < ActiveRecord::Base ...@@ -419,7 +419,11 @@ class Project < ActiveRecord::Base
def reference_pattern def reference_pattern
name_pattern = Gitlab::Regex::NAMESPACE_REGEX_STR name_pattern = Gitlab::Regex::NAMESPACE_REGEX_STR
%r{(?<project>#{name_pattern}/#{name_pattern})}
%r{
((?<namespace>#{name_pattern})\/)?
(?<project>#{name_pattern})
}x
end end
def trending def trending
...@@ -650,8 +654,20 @@ class Project < ActiveRecord::Base ...@@ -650,8 +654,20 @@ class Project < ActiveRecord::Base
end end
end end
def to_reference(_from_project = nil) def to_reference(from_project = nil)
if cross_namespace_reference?(from_project)
path_with_namespace path_with_namespace
elsif cross_project_reference?(from_project)
path
end
end
def to_human_reference(from_project = nil)
if cross_namespace_reference?(from_project)
name_with_namespace
elsif cross_project_reference?(from_project)
name
end
end end
def web_url def web_url
...@@ -1327,10 +1343,21 @@ class Project < ActiveRecord::Base ...@@ -1327,10 +1343,21 @@ class Project < ActiveRecord::Base
private private
# Check if a reference is being done cross-project
#
# from_project - Refering Project object
def cross_project_reference?(from_project)
from_project && self != from_project
end
def pushes_since_gc_redis_key def pushes_since_gc_redis_key
"projects/#{id}/pushes_since_gc" "projects/#{id}/pushes_since_gc"
end end
def cross_namespace_reference?(from_project)
from_project && namespace != from_project.namespace
end
def default_branch_protected? def default_branch_protected?
current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL || current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL ||
current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE
......
...@@ -85,12 +85,8 @@ class Repository ...@@ -85,12 +85,8 @@ class Repository
# This method return true if repository contains some content visible in project page. # This method return true if repository contains some content visible in project page.
# #
def has_visible_content? def has_visible_content?
return @has_visible_content unless @has_visible_content.nil?
@has_visible_content = cache.fetch(:has_visible_content?) do
branch_count > 0 branch_count > 0
end end
end
def commit(ref = 'HEAD') def commit(ref = 'HEAD')
return nil unless exists? return nil unless exists?
...@@ -374,12 +370,6 @@ class Repository ...@@ -374,12 +370,6 @@ class Repository
return unless empty? return unless empty?
expire_method_caches(%i(empty?)) expire_method_caches(%i(empty?))
expire_has_visible_content_cache
end
def expire_has_visible_content_cache
cache.expire(:has_visible_content?)
@has_visible_content = nil
end end
def lookup_cache def lookup_cache
...@@ -467,7 +457,6 @@ class Repository ...@@ -467,7 +457,6 @@ class Repository
# Runs code after a new branch has been created. # Runs code after a new branch has been created.
def after_create_branch def after_create_branch
expire_branches_cache expire_branches_cache
expire_has_visible_content_cache
repository_event(:push_branch) repository_event(:push_branch)
end end
...@@ -481,7 +470,6 @@ class Repository ...@@ -481,7 +470,6 @@ class Repository
# Runs code after an existing branch has been removed. # Runs code after an existing branch has been removed.
def after_remove_branch def after_remove_branch
expire_has_visible_content_cache
expire_branches_cache expire_branches_cache
end end
......
...@@ -27,9 +27,9 @@ class Snippet < ActiveRecord::Base ...@@ -27,9 +27,9 @@ class Snippet < ActiveRecord::Base
delegate :name, :email, to: :author, prefix: true, allow_nil: true delegate :name, :email, to: :author, prefix: true, allow_nil: true
validates :author, presence: true validates :author, presence: true
validates :title, presence: true, length: { within: 0..255 } validates :title, presence: true, length: { maximum: 255 }
validates :file_name, validates :file_name,
length: { within: 0..255 }, length: { maximum: 255 },
format: { with: Gitlab::Regex.file_name_regex, format: { with: Gitlab::Regex.file_name_regex,
message: Gitlab::Regex.file_name_regex_message } message: Gitlab::Regex.file_name_regex_message }
...@@ -67,12 +67,12 @@ class Snippet < ActiveRecord::Base ...@@ -67,12 +67,12 @@ class Snippet < ActiveRecord::Base
def to_reference(from_project = nil) def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{id}" reference = "#{self.class.reference_prefix}#{id}"
if cross_project_reference?(from_project) if project.present?
reference = project.to_reference + reference "#{project.to_reference(from_project)}#{reference}"
end else
reference reference
end end
end
def self.content_types def self.content_types
[ [
...@@ -94,6 +94,10 @@ class Snippet < ActiveRecord::Base ...@@ -94,6 +94,10 @@ class Snippet < ActiveRecord::Base
0 0
end end
def file_name
super.to_s
end
# alias for compatibility with blobs and highlighting # alias for compatibility with blobs and highlighting
def path def path
file_name file_name
......
...@@ -512,7 +512,7 @@ class User < ActiveRecord::Base ...@@ -512,7 +512,7 @@ class User < ActiveRecord::Base
end end
def require_ssh_key? def require_ssh_key?
keys.count == 0 keys.count == 0 && Gitlab::ProtocolAccess.allowed?('ssh')
end end
def require_password? def require_password?
......
module Ci module Ci
class BuildPolicy < CommitStatusPolicy class BuildPolicy < CommitStatusPolicy
def rules def rules
can! :read_build if @subject.project.public_builds?
super super
# If we can't read build we should also not have that # If we can't read build we should also not have that
......
...@@ -12,9 +12,6 @@ class ProjectPolicy < BasePolicy ...@@ -12,9 +12,6 @@ class ProjectPolicy < BasePolicy
guest_access! guest_access!
public_access! public_access!
# Allow to read builds for internal projects
can! :read_build if project.public_builds?
if project.request_access_enabled && if project.request_access_enabled &&
!(owner || user.admin? || project.team.member?(user) || project_group_member?(user)) !(owner || user.admin? || project.team.member?(user) || project_group_member?(user))
can! :request_access can! :request_access
...@@ -46,6 +43,11 @@ class ProjectPolicy < BasePolicy ...@@ -46,6 +43,11 @@ class ProjectPolicy < BasePolicy
can! :create_note can! :create_note
can! :upload_file can! :upload_file
can! :read_cycle_analytics can! :read_cycle_analytics
if project.public_builds?
can! :read_pipeline
can! :read_build
end
end end
def reporter_access! def reporter_access!
......
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