Commit f071e3d3 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into fix-git-hooks-when-creating-file

* upstream/master: (116 commits)
  adds impersonator variable and makes sudo usage overall more clear
  Reenables /user API request to return private-token if user is admin and requested with sudo
  Fix Backup::Manager#remove_old
  Allow public access to some Tag API endpoints
  Update outdated visible content spec descriptions
  Grapify the issues API
  new DevOps report, 404s, typos
  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 [ci skip]
  Bump gitlab-shell version to 4.0.3
  Remove confirmation.scss
  Explain "js: true" in "deleted_source_branch_spec.rb" [ci skip]
  Move award emojis to framwork
  Move image styles to framework
  Remove tags.scss
  ...
parents 9c6563f6 50a78448
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:
key: "ruby-231"
key: "ruby-233"
paths:
- vendor/ruby
......@@ -30,7 +30,12 @@ stages:
- post-test
- pages
# Prepare and merge knapsack tests
# Predefined scopes
.dedicated-runner: &dedicated-runner
tags:
- gitlab-org
- 2gb
.knapsack-state: &knapsack-state
services: []
variables:
......@@ -45,47 +50,14 @@ stages:
paths:
- knapsack/
knapsack:
<<: *knapsack-state
stage: prepare
script:
- mkdir -p knapsack/
- '[[ -f knapsack/rspec_report.json ]] || echo "{}" > knapsack/rspec_report.json'
- '[[ -f knapsack/spinach_report.json ]] || echo "{}" > knapsack/spinach_report.json'
update-knapsack:
<<: *knapsack-state
stage: post-test
script:
- scripts/merge-reports knapsack/rspec_report.json knapsack/rspec_node_*.json
- scripts/merge-reports knapsack/spinach_report.json knapsack/spinach_node_*.json
- rm -f knapsack/*_node_*.json
only:
- master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
- master@gitlab/gitlabhq
- master@gitlab/gitlab-ee
.use-db: &use-db
services:
- mysql:latest
- redis:alpine
setup-test-env:
<<: *use-db
stage: prepare
script:
- bundle exec rake assets:precompile 2>/dev/null
- bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
artifacts:
expire_in: 7d
paths:
- public/assets
- tmp/tests
.rspec-knapsack: &rspec-knapsack
stage: test
<<: *dedicated-runner
<<: *use-db
script:
- JOB_NAME=( $CI_BUILD_NAME )
......@@ -103,6 +75,7 @@ setup-test-env:
.spinach-knapsack: &spinach-knapsack
stage: test
<<: *dedicated-runner
<<: *use-db
script:
- JOB_NAME=( $CI_BUILD_NAME )
......@@ -118,6 +91,44 @@ setup-test-env:
- knapsack/
- coverage/
# Prepare and merge knapsack tests
knapsack:
<<: *knapsack-state
<<: *dedicated-runner
stage: prepare
script:
- mkdir -p knapsack/
- '[[ -f knapsack/rspec_report.json ]] || echo "{}" > knapsack/rspec_report.json'
- '[[ -f knapsack/spinach_report.json ]] || echo "{}" > knapsack/spinach_report.json'
setup-test-env:
<<: *use-db
<<: *dedicated-runner
stage: prepare
script:
- bundle exec rake assets:precompile 2>/dev/null
- bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
artifacts:
expire_in: 7d
paths:
- public/assets
- tmp/tests
update-knapsack:
<<: *knapsack-state
<<: *dedicated-runner
stage: post-test
script:
- scripts/merge-reports knapsack/rspec_report.json knapsack/rspec_node_*.json
- scripts/merge-reports knapsack/spinach_report.json knapsack/spinach_node_*.json
- rm -f knapsack/*_node_*.json
only:
- master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
- master@gitlab/gitlabhq
- master@gitlab/gitlab-ee
rspec 0 20: *rspec-knapsack
rspec 1 20: *rspec-knapsack
rspec 2 20: *rspec-knapsack
......@@ -166,10 +177,12 @@ spinach 9 10: *spinach-knapsack
.rspec-knapsack-ruby21: &rspec-knapsack-ruby21
<<: *rspec-knapsack
<<: *dedicated-runner
<<: *ruby-21
.spinach-knapsack-ruby21: &spinach-knapsack-ruby21
<<: *spinach-knapsack
<<: *dedicated-runner
<<: *ruby-21
rspec 0 20 ruby21: *rspec-knapsack-ruby21
......@@ -214,6 +227,7 @@ spinach 9 10 ruby21: *spinach-knapsack-ruby21
.exec: &exec
<<: *ruby-static-analysis
<<: *dedicated-runner
stage: test
script:
- bundle exec $CI_BUILD_NAME
......@@ -235,7 +249,7 @@ rake ee_compat_check:
- /^[\d-]+-stable(-ee)?$/
allow_failure: yes
cache:
key: "ruby231-ee_compat_check_repo"
key: "ruby233-ee_compat_check_repo"
paths:
- ee_compat_check/repo/
- vendor/ruby
......@@ -249,12 +263,14 @@ rake ee_compat_check:
rake db:migrate:reset:
stage: test
<<: *use-db
<<: *dedicated-runner
script:
- rake db:migrate:reset
rake db:seed_fu:
stage: test
<<: *use-db
<<: *dedicated-runner
variables:
SIZE: "1"
SETUP_DB: "false"
......@@ -276,9 +292,8 @@ teaspoon:
- node_modules/
stage: test
<<: *use-db
<<: *dedicated-runner
script:
- curl --silent --location https://deb.nodesource.com/setup_6.x | bash -
- apt-get install --assume-yes nodejs
- npm install
- npm link istanbul
- rake teaspoon
......@@ -290,20 +305,23 @@ teaspoon:
lint-doc:
stage: test
<<: *dedicated-runner
image: "phusion/baseimage:latest"
before_script: []
script:
- scripts/lint-doc.sh
bundler:check:
stage: test
<<: *ruby-static-analysis
script:
stage: test
<<: *dedicated-runner
<<: *ruby-static-analysis
script:
- bundle check
bundler:audit:
stage: test
<<: *ruby-static-analysis
<<: *dedicated-runner
only:
- master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
......@@ -315,6 +333,7 @@ bundler:audit:
migration paths:
stage: test
<<: *use-db
<<: *dedicated-runner
variables:
SETUP_DB: "false"
only:
......@@ -336,6 +355,7 @@ migration paths:
coverage:
stage: post-test
services: []
<<: *dedicated-runner
variables:
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "true"
......@@ -349,6 +369,7 @@ coverage:
- coverage/assets/
lint:javascript:
<<: *dedicated-runner
cache:
paths:
- node_modules/
......@@ -360,6 +381,7 @@ lint:javascript:
- npm --silent run eslint
lint:javascript:report:
<<: *dedicated-runner
cache:
paths:
- node_modules/
......@@ -381,6 +403,7 @@ lint:javascript:report:
trigger_docs:
stage: post-test
image: "alpine"
<<: *dedicated-runner
before_script:
- apk update && apk add curl
variables:
......@@ -396,6 +419,7 @@ trigger_docs:
notify:slack:
stage: post-test
<<: *dedicated-runner
variables:
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false"
......@@ -411,6 +435,7 @@ notify:slack:
pages:
before_script: []
stage: pages
<<: *dedicated-runner
dependencies:
- coverage
- teaspoon
......@@ -425,11 +450,12 @@ pages:
paths:
- public
only:
- master
- master@gitlab-org/gitlab-ce
# Insurance in case a gem needed by one of our releases gets yanked from
# rubygems.org in the future.
cache gems:
<<: *dedicated-runner
only:
- tags
variables:
......@@ -439,3 +465,5 @@ cache gems:
artifacts:
paths:
- vendor/cache
only:
- master@gitlab-org/gitlab-ce
......@@ -21,6 +21,8 @@ logs, and code as it's very hard to read otherwise.)
### Output of checks
(If you are reporting a bug on GitLab.com, write: This bug happens on GitLab.com)
#### Results of GitLab application Check
(For installations with omnibus-gitlab package run and paste the output of:
......
......@@ -2,6 +2,15 @@
documentation](doc/development/changelog.md) for instructions on adding your own
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)
- Remove caching of events data. !6578
......@@ -242,6 +251,11 @@ entry.
- Fix "Without projects" filter. !6611 (Ben Bodenmiller)
- 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)
- 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
end
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 'awesome_print', '~> 1.2.0', require: false
......@@ -338,7 +338,7 @@ gem 'ruby-prof', '~> 0.16.2'
gem 'oauth2', '~> 1.2.0'
# Soft deletion
gem 'paranoia', '~> 2.0'
gem 'paranoia', '~> 2.2'
# Health check
gem 'health_check', '~> 2.2.0'
......
......@@ -91,7 +91,7 @@ GEM
bundler-audit (0.5.0)
bundler (~> 1.2)
thor (~> 0.18)
byebug (8.2.1)
byebug (9.0.6)
capybara (2.6.2)
addressable
mime-types (>= 1.16)
......@@ -460,8 +460,8 @@ GEM
org-ruby (0.9.12)
rubypants (~> 0.2)
orm_adapter (0.5.0)
paranoia (2.1.4)
activerecord (~> 4.0)
paranoia (2.2.0)
activerecord (>= 4.0, < 5.1)
parser (2.3.1.4)
ast (~> 2.2)
pg (0.18.4)
......@@ -483,6 +483,9 @@ GEM
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
pry-byebug (3.4.1)
byebug (~> 9.0)
pry (~> 0.10)
pry-rails (0.3.4)
pry (>= 0.9.10)
pyu-ruby-sasl (0.0.3.3)
......@@ -796,7 +799,6 @@ DEPENDENCIES
browser (~> 2.2)
bullet (~> 5.2.0)
bundler-audit (~> 0.5.0)
byebug (~> 8.2.1)
capybara (~> 2.6.2)
capybara-screenshot (~> 1.0.0)
carrierwave (~> 0.10.0)
......@@ -887,10 +889,11 @@ DEPENDENCIES
omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12)
paranoia (~> 2.0)
paranoia (~> 2.2)
pg (~> 0.18.2)
poltergeist (~> 1.9.0)
premailer-rails (~> 1.9.0)
pry-byebug (~> 3.4.1)
pry-rails (~> 0.3.4)
rack-attack (~> 4.4.1)
rack-cors (~> 0.4.0)
......
......@@ -76,7 +76,7 @@ GitLab is a Ruby on Rails application that runs on the following software:
- Ubuntu/Debian/CentOS/RHEL
- Ruby (MRI) 2.3
- Git 2.7.4+
- Git 2.8.4+
- Redis 2.8+
- MySQL or PostgreSQL
......
/* 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 @@
new ZenMode();
break;
case 'projects:compare:show':
new Diff();
new gl.Diff();
break;
case 'projects:issues:new':
case 'projects:issues:edit':
......@@ -74,7 +74,7 @@
break;
case 'projects:merge_requests:new':
case 'projects:merge_requests:edit':
new Diff();
new gl.Diff();
shortcut_handler = new ShortcutsNavigation();
new GLForm($('.merge-request-form'));
new IssuableForm($('.merge-request-form'));
......@@ -91,7 +91,7 @@
new GLForm($('.release-form'));
break;
case 'projects:merge_requests:show':
new Diff();
new gl.Diff();
shortcut_handler = new ShortcutsIssuable(true);
new ZenMode();
new MergedButtons();
......@@ -101,7 +101,7 @@
new MergedButtons();
break;
case "projects:merge_requests:diffs":
new Diff();
new gl.Diff();
new ZenMode();
new MergedButtons();
break;
......@@ -117,7 +117,7 @@
break;
case 'projects:commit:show':
new Commit();
new Diff();
new gl.Diff();
new ZenMode();
shortcut_handler = new ShortcutsNavigation();
break;
......
......@@ -97,6 +97,19 @@
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) {
return e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
};
......
......@@ -40,7 +40,7 @@
if (window.mrTabs) {
window.mrTabs.unbindEvents();
}
window.mrTabs = new MergeRequestTabs(this.opts);
window.mrTabs = new gl.MergeRequestTabs(this.opts);
};
MergeRequest.prototype.showAllCommits = function() {
......
......@@ -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>';
function SingleFileDiff(file, forceLoad, cb) {
var clickTarget;
this.file = file;
this.toggleDiff = bind(this.toggleDiff, this);
this.content = $('.diff-content', this.file);
......@@ -31,9 +32,9 @@
this.content.after(this.collapsedContent);
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) {
this.toggleDiff(null, cb);
this.toggleDiff({ target: clickTarget }, cb);
}
}
......
......@@ -40,3 +40,6 @@
@import "framework/blank";
@import "framework/wells.scss";
@import "framework/page-header.scss";
@import "framework/awards.scss";
@import "framework/images.scss";
@import "framework/broadcast-messages";
......@@ -127,7 +127,7 @@
.award-control-icon {
float: left;
margin-right: 5px;
font-size: 19px;
font-size: 18px;
}
.award-control-icon-loading {
......
......@@ -32,14 +32,14 @@
.blank-state-title {
margin-top: 0;
margin-bottom: 5px;
font-size: 19px;
font-size: 18px;
font-weight: normal;
}
.blank-state-text {
margin-top: 0;
margin-bottom: $gl-padding;
font-size: 15px;
font-size: 14px;
> strong {
font-weight: 600;
......
.light-well {
background-color: $background-color;
padding: 15px;
}
.centered-light-block {
text-align: center;
color: $gl-gray;
......@@ -274,6 +269,10 @@
}
}
.emoji-icon {
display: inline-block;
}
@media(max-width: $screen-xs-max) {
margin-top: 50px;
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 @@
.slead {
color: $common-gray;
font-size: 15px;
font-size: 14px;
margin-bottom: 12px;
font-weight: normal;
line-height: 24px;
......@@ -379,7 +379,9 @@ table {
border-top: 1px solid $border-color;
}
.hide-bottom-border { border-bottom: none !important; }
.hide-bottom-border {
border-bottom: none !important;
}
.gl-accessibility {
&:focus {
......@@ -396,3 +398,13 @@ table {
z-index: 1;
}
}
.str-truncated {
&-60 {
@include str-truncated(60%);
}
&-100 {
@include str-truncated(100%);
}
}
......@@ -36,7 +36,7 @@
padding: 6px 8px 6px 10px;
background-color: $dropdown-toggle-bg;
color: $dropdown-toggle-color;
font-size: 15px;
font-size: 14px;
text-align: left;
border: 1px solid $border-color;
border-radius: $border-radius-base;
......@@ -123,7 +123,7 @@
width: 240px;
margin-top: 2px;
margin-bottom: 0;
font-size: 15px;
font-size: 14px;
font-weight: normal;
padding: 8px 0;
background-color: $dropdown-bg;
......@@ -589,7 +589,7 @@
.ui-datepicker-title {
color: $gl-gray;
font-size: 15px;
font-size: 14px;
line-height: 1;
font-weight: normal;
}
......
......@@ -71,7 +71,7 @@ header {
}
.fa-caret-down {
font-size: 15px;
font-size: 14px;
}
}
......@@ -156,7 +156,7 @@ header {
position: relative;
padding-right: 20px;
margin: 0;
font-size: 19px;
font-size: 18px;
max-width: 385px;
display: inline-block;
line-height: $header-height;
......
......@@ -106,13 +106,13 @@ ul.task-list {
}
}
// Generic content list
ul.content-list {
@include basic-list;
margin: 0;
padding: 0;
> li {
li {
border-color: $table-border-color;
font-size: $list-font-size;
color: $list-text-color;
......@@ -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 {
padding: $gl-padding-top $gl-padding;
......
......@@ -49,7 +49,7 @@
padding: $gl-btn-padding;
padding-bottom: 11px;
margin-bottom: -1px;
font-size: 15px;
font-size: 14px;
line-height: 28px;
color: $note-toolbar-color;
border-bottom: 2px solid transparent;
......@@ -268,6 +268,16 @@
width: auto;
}
}
&.multi-line {
.nav-text {
line-height: 20px;
}
.nav-controls {
padding: 17px 0;
}
}
}
.layout-nav {
......
......@@ -34,6 +34,10 @@ table {
background-color: $background-color;
font-weight: normal;
border-bottom: none;
&.wide {
width: 55%;
}
}
td {
......@@ -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;
/*
* Text
*/
$gl-font-size: 15px;
$gl-font-size: 14px;
$gl-title-color: #333;
$gl-text-color: #5c5c5c;
$gl-text-color-dark: #5c5d5e;
......@@ -380,7 +380,7 @@ $ci-skipped-color: #888;
/*
* Boards
*/
$issue-boards-font-size: 15px;
$issue-boards-font-size: 14px;
$issue-boards-card-shadow: rgba(186, 186, 186, 0.5);
/*
......@@ -427,12 +427,6 @@ $common-gray-dark: #444;
$common-red: $gl-text-red;
$common-green: $gl-text-green;
/*
* Dashboard
*/
$dashboard-project-access-icon-color: #888;
/*
* Editor
*/
......
......@@ -43,3 +43,16 @@
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 @@
@media (max-width: $screen-sm-max) {
padding-right: 40px;
margin-top: 6px;
.btn-inverted {
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 @@
&.title {
line-height: 19px;
font-size: 15px;
font-size: 14px;
font-weight: 600;
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 {
.incorrect-syntax {
font-size: 19px;
font-size: 18px;
color: $lint-incorrect-color;
}
.correct-syntax {
font-size: 19px;
font-size: 18px;
color: $lint-correct-color;
}
}
......@@ -116,7 +116,7 @@
@media (max-width: $screen-xs-max) {
h4 {
font-size: 15px;
font-size: 14px;
}
p {
......@@ -129,6 +129,11 @@
margin-bottom: 4px;
}
.btn-grouped {
float: none;
margin-right: 0;
}
.accept-action {
width: 100%;
text-align: center;
......
......@@ -129,7 +129,7 @@
.note-edit-form {
display: none;
font-size: 15px;
font-size: 14px;
.md-area {
background-color: $white-light;
......
......@@ -280,6 +280,12 @@
}
}
.admin-builds-table {
.ci-table td:last-child {
min-width: 120px;
}
}
// Pipeline visualization
.toggle-pipeline-btn {
......
......@@ -521,7 +521,7 @@ a.deploy-project-label {
.nav > li > a {
padding: 0;
background-color: transparent;
font-size: 15px;
font-size: 14px;
line-height: 29px;
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
@label = merge_requests_finder.labels.first
@merge_requests = merge_requests_collection
.non_archived
.page(params[:page])
end
private
def filter_params
super.merge(non_archived: true)
end
end
......@@ -5,9 +5,7 @@ class Projects::DiscussionsController < Projects::ApplicationController
before_action :authorize_resolve_discussion!
def resolve
discussion.resolve!(current_user)
MergeRequests::ResolvedDiscussionNotificationService.new(project, current_user).execute(merge_request)
Discussions::ResolveService.new(project, current_user, merge_request: merge_request).execute(discussion)
render json: {
resolved_by: discussion.resolved_by.try(:name),
......
......@@ -46,8 +46,9 @@ class Projects::IssuesController < Projects::ApplicationController
params[:issue] ||= ActionController::Parameters.new(
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)
end
......@@ -75,7 +76,9 @@ class Projects::IssuesController < Projects::ApplicationController
end
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|
format.html do
......@@ -169,6 +172,14 @@ class Projects::IssuesController < Projects::ApplicationController
alias_method :awardable, :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!
return render_404 unless can?(current_user, :read_issue, @issue)
end
......
......@@ -302,9 +302,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
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
def merge
......@@ -331,8 +335,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
if @merge_request.head_pipeline.active?
MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params)
.execute(@merge_request)
MergeRequests::MergeWhenPipelineSucceedsService
.new(@project, current_user, merge_params)
.execute(@merge_request)
@status = :merge_when_build_succeeds
elsif @merge_request.head_pipeline.success?
# This can be triggered when a user clicks the auto merge button while
......
......@@ -7,7 +7,7 @@
# current_user - which user use
# params:
# 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
# project_id: integer
# milestone_title: string
......@@ -15,6 +15,7 @@
# search: string
# label_name: string
# sort: string
# non_archived: boolean
#
class IssuableFinder
NONE = '0'
......@@ -38,6 +39,7 @@ class IssuableFinder
items = by_author(items)
items = by_label(items)
items = by_due_date(items)
items = by_non_archived(items)
sort(items)
end
......@@ -207,10 +209,13 @@ class IssuableFinder
end
def by_state(items)
params[:state] ||= 'all'
if items.respond_to?(params[:state])
items.public_send(params[:state])
case params[:state].to_s
when 'closed'
items.closed
when 'merged'
items.respond_to?(:merged) ? items.merged : items.closed
when 'opened'
items.opened
else
items
end
......@@ -353,6 +358,10 @@ class IssuableFinder
end
end
def by_non_archived(items)
params[:non_archived].present? ? items.non_archived : items
end
def current_user_related?
params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
end
......
......@@ -14,6 +14,7 @@
# search: string
# label_name: string
# sort: string
# non_archived: boolean
#
class MergeRequestsFinder < IssuableFinder
def klass
......
......@@ -5,8 +5,9 @@ module CiStatusHelper
end
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}"
if target
link_to content, target, class: klass
else
......@@ -14,7 +15,19 @@ module CiStatusHelper
end
end
def ci_text_for_status(status)
if detailed_status?(status)
status.text
else
status
end
end
def ci_label_for_status(status)
if detailed_status?(status)
return status.label
end
case status
when 'success'
'passed'
......@@ -31,6 +44,10 @@ module CiStatusHelper
end
def ci_icon_for_status(status)
if detailed_status?(status)
return custom_icon(status.icon)
end
icon_name =
case status
when 'success'
......@@ -94,4 +111,10 @@ module CiStatusHelper
class: klass, title: title, data: data
end
end
def detailed_status?(status)
status.respond_to?(:text) &&
status.respond_to?(:label) &&
status.respond_to?(:icon)
end
end
......@@ -174,7 +174,7 @@ module GitlabMarkdownHelper
# Returns a String
def cross_project_reference(project, entity)
if entity.respond_to?(:to_reference)
"#{project.to_reference}#{entity.to_reference}"
entity.to_reference(project)
else
''
end
......
......@@ -82,12 +82,6 @@ module LabelsHelper
span.html_safe
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
[
'#0033CC',
......@@ -166,6 +160,5 @@ module LabelsHelper
end
# Required for Banzai::Filter::LabelReferenceFilter
module_function :render_colored_label, :render_colored_cross_project_label,
:text_color_for_bg, :escape_once
module_function :render_colored_label, :text_color_for_bg, :escape_once
end
......@@ -320,6 +320,10 @@ module Ci
.select { |merge_request| merge_request.head_pipeline.try(:id) == self.id }
end
def detailed_status
Gitlab::Ci::Status::Pipeline::Factory.new(self).fabricate!
end
private
def pipeline_data
......
......@@ -4,10 +4,10 @@ module Ci
belongs_to :project, foreign_key: :gl_project_id
validates_uniqueness_of :key, scope: :gl_project_id
validates :key,
presence: true,
length: { within: 0..255 },
uniqueness: { scope: :gl_project_id },
length: { maximum: 255 },
format: { with: /\A[a-zA-Z0-9_]+\z/,
message: "can contain only letters, digits and '_'." }
......
......@@ -92,19 +92,11 @@ class Commit
end
def to_reference(from_project = nil)
if cross_project_reference?(from_project)
project.to_reference + self.class.reference_prefix + self.id
else
self.id
end
commit_reference(from_project, id)
end
def reference_link_text(from_project = nil)
if cross_project_reference?(from_project)
project.to_reference + self.class.reference_prefix + self.short_id
else
self.short_id
end
commit_reference(from_project, short_id)
end
def diff_line_count
......@@ -329,6 +321,16 @@ class Commit
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
User.find_by_any_email(author_email.downcase)
end
......
......@@ -90,21 +90,24 @@ class CommitRange
alias_method :id, :to_s
def to_reference(from_project = nil)
if cross_project_reference?(from_project)
project.to_reference + self.class.reference_prefix + self.id
project_reference = project.to_reference(from_project)
if project_reference.present?
project_reference + self.class.reference_prefix + self.id
else
self.id
end
end
def reference_link_text(from_project = nil)
reference = ref_from + notation + ref_to
project_reference = project.to_reference(from_project)
reference = ref_from + notation + ref_to
if cross_project_reference?(from_project)
reference = project.to_reference + self.class.reference_prefix + reference
if project_reference.present?
project_reference + self.class.reference_prefix + reference
else
reference
end
reference
end
# Return a Hash of parameters for passing to a URL helper
......
......@@ -41,7 +41,7 @@ module Issuable
has_one :metrics
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 :assigned_to, ->(u) { where(assignee_id: u.id)}
......
......@@ -72,17 +72,4 @@ module Referable
}x
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
......@@ -88,6 +88,10 @@ class Discussion
@first_note ||= @notes.first
end
def first_note_to_resolve
@first_note_to_resolve ||= notes.detect(&:to_be_resolved?)
end
def last_note
@last_note ||= @notes.last
end
......
......@@ -9,7 +9,7 @@ class Environment < ActiveRecord::Base
validates :name,
presence: true,
uniqueness: { scope: :project_id },
length: { within: 0..255 },
length: { maximum: 255 },
format: { with: Gitlab::Regex.environment_name_regex,
message: Gitlab::Regex.environment_name_regex_message }
......
......@@ -153,11 +153,7 @@ class Issue < ActiveRecord::Base
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}"
if cross_project_reference?(from_project)
reference = project.to_reference + reference
end
reference
"#{project.to_reference(from_project)}#{reference}"
end
def referenced_merge_requests(current_user = nil)
......
......@@ -8,10 +8,18 @@ class Key < ActiveRecord::Base
before_validation :generate_fingerprint
validates :title, presence: true, length: { within: 0..255 }
validates :key, presence: true, length: { within: 0..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' }
validates :title,
presence: true,
length: { maximum: 255 }
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
......
......@@ -144,9 +144,10 @@ class Label < ActiveRecord::Base
#
# Examples:
#
# Label.first.to_reference # => "~1"
# Label.first.to_reference(format: :name) # => "~\"bug\""
# Label.first.to_reference(project1, project2) # => "gitlab-org/gitlab-ce~1"
# Label.first.to_reference # => "~1"
# Label.first.to_reference(format: :name) # => "~\"bug\""
# 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
#
......@@ -154,8 +155,8 @@ class Label < ActiveRecord::Base
format_reference = label_format_reference(format)
reference = "#{self.class.reference_prefix}#{format_reference}"
if cross_project_reference?(source_project, target_project)
source_project.to_reference + reference
if source_project
"#{source_project.to_reference(target_project)}#{reference}"
else
reference
end
......@@ -169,10 +170,6 @@ class Label < ActiveRecord::Base
private
def cross_project_reference?(source_project, target_project)
source_project && target_project && source_project != target_project
end
def issues_count(user, params = {})
params.merge!(subject_foreign_key => subject.id, label_name: title, scope: 'all')
IssuesFinder.new(user, params.with_indifferent_access).execute.count
......
......@@ -63,6 +63,7 @@ class Member < ActiveRecord::Base
after_create :send_request, if: :request?, unless: :importing?
after_create :create_notification_setting, 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_destroy :post_destroy_hook, unless: :pending?
......
......@@ -176,11 +176,7 @@ class MergeRequest < ActiveRecord::Base
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}"
if cross_project_reference?(from_project)
reference = project.to_reference + reference
end
reference
"#{project.to_reference(from_project)}#{reference}"
end
def first_commit
......@@ -480,6 +476,14 @@ class MergeRequest < ActiveRecord::Base
@diff_discussions ||= self.notes.diff_notes.discussions
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)
notes = self.notes.diff_notes.where(discussion_id: discussion_id).fresh.to_a
return if notes.empty?
......
......@@ -113,19 +113,16 @@ class Milestone < ActiveRecord::Base
#
# Examples:
#
# Milestone.first.to_reference # => "%1"
# Milestone.first.to_reference(format: :name) # => "%\"goal\""
# Milestone.first.to_reference(project) # => "gitlab-org/gitlab-ce%1"
# Milestone.first.to_reference # => "%1"
# Milestone.first.to_reference(format: :name) # => "%\"goal\""
# 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)
format_reference = milestone_format_reference(format)
reference = "#{self.class.reference_prefix}#{format_reference}"
if cross_project_reference?(from_project)
project.to_reference + reference
else
reference
end
"#{project.to_reference(from_project)}#{reference}"
end
def reference_link_text(from_project = nil)
......
......@@ -12,17 +12,17 @@ class Namespace < ActiveRecord::Base
validates :owner, presence: true, unless: ->(n) { n.type == "Group" }
validates :name,
length: { within: 0..255 },
namespace_name: 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,
length: { within: 1..255 },
namespace: 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
......
......@@ -99,7 +99,7 @@ class Note < ActiveRecord::Base
end
def discussions
Discussion.for_notes(all)
Discussion.for_notes(fresh)
end
def grouped_diff_discussions
......
......@@ -172,13 +172,13 @@ class Project < ActiveRecord::Base
validates :description, length: { maximum: 2000 }, allow_blank: true
validates :name,
presence: true,
length: { within: 0..255 },
length: { maximum: 255 },
format: { with: Gitlab::Regex.project_name_regex,
message: Gitlab::Regex.project_name_regex_message }
validates :path,
presence: true,
project_path: true,
length: { within: 0..255 },
length: { maximum: 255 },
format: { with: Gitlab::Regex.project_path_regex,
message: Gitlab::Regex.project_path_regex_message }
validates :namespace, presence: true
......@@ -419,7 +419,11 @@ class Project < ActiveRecord::Base
def reference_pattern
name_pattern = Gitlab::Regex::NAMESPACE_REGEX_STR
%r{(?<project>#{name_pattern}/#{name_pattern})}
%r{
((?<namespace>#{name_pattern})\/)?
(?<project>#{name_pattern})
}x
end
def trending
......@@ -650,8 +654,20 @@ class Project < ActiveRecord::Base
end
end
def to_reference(_from_project = nil)
path_with_namespace
def to_reference(from_project = nil)
if cross_namespace_reference?(from_project)
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
def web_url
......@@ -1327,10 +1343,21 @@ class Project < ActiveRecord::Base
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
"projects/#{id}/pushes_since_gc"
end
def cross_namespace_reference?(from_project)
from_project && namespace != from_project.namespace
end
def default_branch_protected?
current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL ||
current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE
......
......@@ -81,11 +81,7 @@ class Repository
# This method return true if repository contains some content visible in project page.
#
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
end
branch_count > 0
end
def commit(ref = 'HEAD')
......@@ -340,12 +336,6 @@ class Repository
return unless 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
def lookup_cache
......@@ -433,7 +423,6 @@ class Repository
# Runs code after a new branch has been created.
def after_create_branch
expire_branches_cache
expire_has_visible_content_cache
repository_event(:push_branch)
end
......@@ -447,7 +436,6 @@ class Repository
# Runs code after an existing branch has been removed.
def after_remove_branch
expire_has_visible_content_cache
expire_branches_cache
end
......
......@@ -27,9 +27,9 @@ class Snippet < ActiveRecord::Base
delegate :name, :email, to: :author, prefix: true, allow_nil: true
validates :author, presence: true
validates :title, presence: true, length: { within: 0..255 }
validates :title, presence: true, length: { maximum: 255 }
validates :file_name,
length: { within: 0..255 },
length: { maximum: 255 },
format: { with: Gitlab::Regex.file_name_regex,
message: Gitlab::Regex.file_name_regex_message }
......@@ -67,11 +67,11 @@ class Snippet < ActiveRecord::Base
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{id}"
if cross_project_reference?(from_project)
reference = project.to_reference + reference
if project.present?
"#{project.to_reference(from_project)}#{reference}"
else
reference
end
reference
end
def self.content_types
......@@ -94,6 +94,10 @@ class Snippet < ActiveRecord::Base
0
end
def file_name
super.to_s
end
# alias for compatibility with blobs and highlighting
def path
file_name
......
module Ci
class BuildPolicy < CommitStatusPolicy
def rules
can! :read_build if @subject.project.public_builds?
super
# If we can't read build we should also not have that
......
......@@ -12,9 +12,6 @@ class ProjectPolicy < BasePolicy
guest_access!
public_access!
# Allow to read builds for internal projects
can! :read_build if project.public_builds?
if project.request_access_enabled &&
!(owner || user.admin? || project.team.member?(user) || project_group_member?(user))
can! :request_access
......@@ -46,6 +43,11 @@ class ProjectPolicy < BasePolicy
can! :create_note
can! :upload_file
can! :read_cycle_analytics
if project.public_builds?
can! :read_pipeline
can! :read_build
end
end
def reporter_access!
......
module Discussions
class BaseService < ::BaseService
end
end
module Discussions
class ResolveService < Discussions::BaseService
def execute(one_or_more_discussions)
Array(one_or_more_discussions).each { |discussion| resolve_discussion(discussion) }
end
def resolve_discussion(discussion)
return unless discussion.can_resolve?(current_user)
discussion.resolve!(current_user)
MergeRequests::ResolvedDiscussionNotificationService.new(project, current_user).execute(merge_request)
SystemNoteService.discussion_continued_in_issue(discussion, project, current_user, follow_up_issue) if follow_up_issue
end
def merge_request
params[:merge_request]
end
def follow_up_issue
params[:follow_up_issue]
end
end
end
......@@ -120,9 +120,10 @@ class IssuableBaseService < BaseService
def merge_slash_commands_into_params!(issuable)
description, command_params =
SlashCommands::InterpretService.new(project, current_user).
execute(params[:description], issuable)
execute(params[:description], issuable)
params[:description] = description
# Avoid a description already set on an issuable to be overwritten by a nil
params[:description] = description if params.has_key?(:description)
params.merge!(command_params)
end
......
module Issues
class BaseService < ::IssuableBaseService
attr_reader :merge_request_for_resolving_discussions
def initialize(*args)
super
@merge_request_for_resolving_discussions ||= params.delete(:merge_request_for_resolving_discussions)
end
def hook_data(issue, action)
issue_data = issue.to_hook_data(current_user)
issue_url = Gitlab::UrlBuilder.build(issue)
......
module Issues
class BuildService < Issues::BaseService
def execute
@issue = project.issues.new(issue_params)
end
def issue_params_with_info_from_merge_request
return {} unless merge_request_for_resolving_discussions
{ title: title_from_merge_request, description: description_from_merge_request }
end
def title_from_merge_request
"Follow-up from \"#{merge_request_for_resolving_discussions.title}\""
end
def description_from_merge_request
if merge_request_for_resolving_discussions.resolvable_discussions.empty?
return "There are no unresolved discussions. "\
"Review the conversation in #{merge_request_for_resolving_discussions.to_reference}"
end
description = "The following discussions from #{merge_request_for_resolving_discussions.to_reference} should be addressed:"
[description, *items_for_discussions].join("\n\n")
end
def items_for_discussions
merge_request_for_resolving_discussions.resolvable_discussions.map { |discussion| item_for_discussion(discussion) }
end
def item_for_discussion(discussion)
first_note = discussion.first_note_to_resolve
other_note_count = discussion.notes.size - 1
creation_time = first_note.created_at.to_s(:medium)
note_url = Gitlab::UrlBuilder.build(first_note)
discussion_info = "- [ ] #{first_note.author.to_reference} commented in a discussion on [#{creation_time}](#{note_url}): "
discussion_info << " (+#{other_note_count} #{'comment'.pluralize(other_note_count)})" if other_note_count > 0
note_without_block_quotes = Banzai::Filter::BlockquoteFenceFilter.new(first_note.note).call
quote = ">>>\n#{note_without_block_quotes}\n>>>"
[discussion_info, quote].join("\n\n")
end
def issue_params
@issue_params ||= issue_params_with_info_from_merge_request.merge(params.slice(:title, :description))
end
end
end
......@@ -4,7 +4,8 @@ module Issues
@request = params.delete(:request)
@api = params.delete(:api)
@issue = project.issues.new
issue_attributes = params.merge(merge_request_for_resolving_discussions: merge_request_for_resolving_discussions)
@issue = BuildService.new(project, current_user, issue_attributes).execute
create(@issue)
end
......@@ -18,6 +19,17 @@ module Issues
notification_service.new_issue(issuable, current_user)
todo_service.new_issue(issuable, current_user)
user_agent_detail_service.create
if merge_request_for_resolving_discussions.try(:discussions_can_be_resolved_by?, current_user)
resolve_discussions_in_merge_request(issuable)
end
end
def resolve_discussions_in_merge_request(issue)
Discussions::ResolveService.new(project, current_user,
merge_request: merge_request_for_resolving_discussions,
follow_up_issue: issue).
execute(merge_request_for_resolving_discussions.resolvable_discussions)
end
private
......
module MergeRequests
class MergeWhenBuildSucceedsService < MergeRequests::BaseService
class MergeWhenPipelineSucceedsService < MergeRequests::BaseService
# Marks the passed `merge_request` to be merged when the build succeeds or
# updates the params for the automatic merge
def execute(merge_request)
......
......@@ -131,14 +131,14 @@ module SystemNoteService
create_note(noteable: noteable, project: project, author: author, note: body)
end
# Called when 'merge when build succeeds' is executed
# Called when 'merge when pipeline succeeds' is executed
def merge_when_build_succeeds(noteable, project, author, last_commit)
body = "enabled an automatic merge when the build for #{last_commit.to_reference(project)} succeeds"
body = "enabled an automatic merge when the pipeline for #{last_commit.to_reference(project)} succeeds"
create_note(noteable: noteable, project: project, author: author, note: body)
end
# Called when 'merge when build succeeds' is canceled
# Called when 'merge when pipeline succeeds' is canceled
def cancel_merge_when_build_succeeds(noteable, project, author)
body = 'canceled the automatic merge'
......@@ -163,6 +163,14 @@ module SystemNoteService
create_note(noteable: merge_request, project: project, author: author, note: body)
end
def discussion_continued_in_issue(discussion, project, author, issue)
body = "Added #{issue.to_reference} to continue this discussion"
note_attributes = discussion.reply_attributes.merge(project: project, author: author, note: body)
note_attributes[:type] = note_attributes.delete(:note_type)
create_note(note_attributes)
end
# Called when the title of a Noteable is changed
#
# noteable - Noteable object that responds to `title`
......
......@@ -4,7 +4,7 @@
.abuse-reports
- if @abuse_reports.present?
.table-holder
%table.table
%table.table.responsive-table
%thead.hidden-sm.hidden-xs
%tr
%th User
......@@ -13,8 +13,6 @@
%th Action
= render @abuse_reports
- else
.no-reports
%span.pull-left
There are no abuse reports!
.pull-left
= emoji_icon 'tada'
.empty-state
.text-center
%h4 There are no abuse reports! #{emoji_icon 'tada'}
......@@ -113,7 +113,7 @@
%hr
.row
.col-sm-4
.light-well
.light-well.well-centered
%h4 Projects
.data
= link_to admin_namespaces_projects_path do
......@@ -121,7 +121,7 @@
%hr
= link_to('New Project', new_project_path, class: "btn btn-new")
.col-sm-4
.light-well
.light-well.well-centered
%h4 Users
.data
= link_to admin_users_path do
......@@ -129,7 +129,7 @@
%hr
= link_to 'New User', new_admin_user_path, class: "btn btn-new"
.col-sm-4
.light-well
.light-well.well-centered
%h4 Groups
.data
= link_to admin_groups_path do
......@@ -143,7 +143,7 @@
%hr
- @projects.each do |project|
%p
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated'
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated-60'
%span.light.pull-right
#{time_ago_with_tooltip(project.created_at)}
......@@ -152,7 +152,7 @@
%hr
- @users.each do |user|
%p
= link_to [:admin, user], class: 'str-truncated' do
= link_to [:admin, user], class: 'str-truncated-60' do
= user.name
%span.light.pull-right
#{time_ago_with_tooltip(user.created_at)}
......@@ -162,7 +162,7 @@
%hr
- @groups.each do |group|
%p
= link_to [:admin, group], class: 'str-truncated' do
= link_to [:admin, group], class: 'str-truncated-60' do
= group.name
%span.light.pull-right
#{time_ago_with_tooltip(group.created_at)}
%li.user-row
%li.flex-row
.user-avatar
= image_tag avatar_icon(user), class: "avatar", alt: ''
.user-details
.user-name
.row-main-content
.user-name.row-title.str-truncated-100
= link_to user.name, [:admin, user]
- if user.blocked?
%span.label.label-danger blocked
......@@ -12,7 +12,7 @@
%span.label.label-default External
- if user == current_user
%span It's you!
.user-email
.row-second-line.str-truncated-100
= mail_to user.email, user.email
.controls
= link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: 'btn'
......
......@@ -68,7 +68,7 @@
%small.badge= number_with_delimiter(User.without_projects.count)
.fade-right
%ul.users-list.content-list
%ul.flex-list.content-list
- if @users.empty?
%li
.nothing-here-block No users found.
......
......@@ -7,7 +7,7 @@
%ul.well-list
- @user.groups.each do |group|
%li
%strong= group.name
%strong= link_to group.name, admin_group_path(group)
&ndash; access to
#{pluralize(group.projects.count, 'project')}
......
.well-confirmation.text-center
.well-confirmation.text-center.append-bottom-20
%h1.prepend-top-0
Almost there...
%p.lead
%p.lead.append-bottom-20
Please check your email to confirm your account
%hr
- if current_application_settings.after_sign_up_text.present?
.well-confirmation.text-center
= markdown_field(current_application_settings, :after_sign_up_text)
%p.confirmation-content.text-center
%p.text-center
No confirmation email received? Please check your spam folder or
.append-bottom-20.prepend-top-20.text-center
%a.btn.btn-lg.btn-success{ href: new_user_confirmation_path }
......
......@@ -31,7 +31,7 @@
= link_to merge_requests_group_path(@group), title: 'Merge Requests' do
%span
Merge Requests
- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened').execute
- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute
%span.badge.count= number_with_delimiter(merge_requests.count)
= nav_link(controller: [:group_members]) do
= link_to group_group_members_path(@group), title: 'Members' do
......
......@@ -31,7 +31,7 @@
&nbsp;
.light
= commit_author_link(commit, avatar: false)
authored
committed
#{time_ago_with_tooltip(commit.committed_date)}
%td.line-numbers
- line_count = blame_group[:lines].count
......
- if !project.empty_repo? && can?(current_user, :download_code, project)
%span{class: 'download-button'}
.dropdown.inline
%button.btn{ 'data-toggle' => 'dropdown' }
= icon('download')
= icon("caret-down")
%span.sr-only
Select Archive Format
%ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
%li.dropdown-header Source code
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do
%i.fa.fa-download
%span Download zip
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar.gz
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar.bz2
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar
.dropdown.inline.download-button
%button.btn{ 'data-toggle' => 'dropdown' }
= icon('download')
= icon("caret-down")
%span.sr-only
Select Archive Format
%ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
%li.dropdown-header Source code
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do
%i.fa.fa-download
%span Download zip
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar.gz
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar.bz2
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar
- pipeline = project.pipelines.latest_successful_for(ref)
- if pipeline
- artifacts = pipeline.builds.latest.with_artifacts
- if artifacts.any?
%li.dropdown-header Artifacts
- unless pipeline.latest?
- latest_pipeline = project.pipeline_for(ref)
%li
.unclickable= ci_status_for_statuseable(latest_pipeline)
%li.dropdown-header Previous Artifacts
- artifacts.each do |job|
%li
= link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{ref}/download", job: job.name), rel: 'nofollow' do
%i.fa.fa-download
%span Download '#{job.name}'
- pipeline = project.pipelines.latest_successful_for(ref)
- if pipeline
- artifacts = pipeline.builds.latest.with_artifacts
- if artifacts.any?
%li.dropdown-header Artifacts
- unless pipeline.latest?
- latest_pipeline = project.pipeline_for(ref)
%li
.unclickable= ci_status_for_statuseable(latest_pipeline)
%li.dropdown-header Previous Artifacts
- artifacts.each do |job|
%li
= link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{ref}/download", job: job.name), rel: 'nofollow' do
%i.fa.fa-download
%span Download '#{job.name}'
- status = pipeline.status
- detailed_status = pipeline.detailed_status
- show_commit = local_assigns.fetch(:show_commit, true)
- show_branch = local_assigns.fetch(:show_branch, true)
%tr.commit
%td.commit-link
= link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "ci-status ci-#{status}" do
= ci_icon_for_status(status)
= ci_label_for_status(status)
= link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "ci-status ci-#{detailed_status}" do
= ci_icon_for_status(detailed_status)
= ci_text_for_status(detailed_status)
%td
= link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do
......
......@@ -37,5 +37,5 @@
= preserve(markdown(commit.description, pipeline: :single_line, author: commit.author))
= commit_author_link(commit, avatar: false, size: 24)
authored
committed
#{time_ago_with_tooltip(commit.committed_date)}
......@@ -8,14 +8,13 @@
= link_to "##{@pipeline.id}", namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'pipeline'
= ci_label_for_status(status)
for
- commit = @merge_request.diff_head_commit
= succeed "." do
= link_to @pipeline.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @pipeline.sha), class: "monospace"
%span.ci-coverage
- elsif @merge_request.has_ci?
- # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX
- # Remove in later versions when services like Jenkins will set CI status via Commit status API
- # TODO, remove in later versions when services like Jenkins will set CI status via Commit status API
.mr-widget-heading
- %w[success skipped canceled failed running pending].each do |status|
.ci_widget{class: "ci-#{status}", style: "display:none"}
......
......@@ -9,7 +9,7 @@
- if @pipeline && @pipeline.active?
%span.btn-group
= button_tag class: "btn btn-create js-merge-button merge_when_build_succeeds" do
Merge When Build Succeeds
Merge When Pipeline Succeeds
- unless @project.only_allow_merge_if_build_succeeds?
= button_tag class: "btn btn-success dropdown-toggle", 'data-toggle' => 'dropdown' do
= icon('caret-down')
......@@ -19,7 +19,7 @@
%li
= link_to "#", class: "merge_when_build_succeeds" do
= icon('check fw')
Merge When Build Succeeds
Merge When Pipeline Succeeds
%li
= link_to "#", class: "accept_merge_request" do
= icon('warning fw')
......
%h4
Set by #{link_to_member(@project, @merge_request.merge_user, avatar: true)}
to be merged automatically when the build succeeds.
to be merged automatically when the pipeline succeeds.
%div
%p
= succeed '.' do
......
......@@ -3,4 +3,8 @@
This merge request has unresolved discussions
%p
Please resolve these discussions to allow this merge request to be merged.
\ No newline at end of file
Please resolve these discussions
- if @project.issues_enabled? && can?(current_user, :create_issue, @project)
or
= link_to "open an issue to resolve them later", new_namespace_project_issue_path(@project.namespace, @project, merge_request_for_resolving_discussions: @merge_request.iid)
to allow this merge request to be merged.
.page-content-header
.header-main-content
= ci_status_with_icon(@pipeline.status)
= ci_status_with_icon(@pipeline.detailed_status)
%strong Pipeline ##{@commit.pipelines.last.id}
triggered #{time_ago_with_tooltip(@commit.authored_date)} by
= author_avatar(@commit, size: 24)
......
......@@ -86,6 +86,9 @@
%li
tap --coverage-report=text-summary (NodeJS) -
%code ^Statements\s*:\s*([^%]+)
%li
excoveralls (Elixir) -
%code \[TOTAL\]\s+(\d+\.\d+)%
= f.submit 'Save changes', class: "btn btn-save"
......
......@@ -3,8 +3,16 @@
= render "projects/commits/head"
%div{ class: container_class }
.sub-header-block
.pull-right.tag-buttons
.top-area.multi-line
.nav-text
.title
%span.item-title= @tag.name
- if @commit
= render 'projects/branches/commit', commit: @commit, project: @project
- else
Cant find HEAD commit for this tag
.nav-controls
- if can?(current_user, :push_code, @project)
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Edit release notes' do
= icon("pencil")
......@@ -15,15 +23,8 @@
= render 'projects/buttons/download', project: @project, ref: @tag.name
- if can?(current_user, :admin_project, @project)
.pull-right
= link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
= link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
%i.fa.fa-trash-o
.tag-info.append-bottom-10
.title
%span.item-title= @tag.name
- if @commit
= render 'projects/branches/commit', commit: @commit, project: @project
- else
Cant find HEAD commit for this tag
- if @tag.message.present?
%pre.body
= strip_gpg_signature(@tag.message)
......
......@@ -42,6 +42,21 @@
= render 'shared/issuable/form/branch_chooser', issuable: issuable, form: form
- if @merge_request_for_resolving_discussions
.form-group
.col-sm-10.col-sm-offset-2
- if @merge_request_for_resolving_discussions.discussions_can_be_resolved_by?(current_user)
= icon('exclamation-triangle')
Creating this issue will mark all discussions in
= link_to @merge_request_for_resolving_discussions.to_reference, merge_request_path(@merge_request_for_resolving_discussions)
as resolved.
= hidden_field_tag 'merge_request_for_resolving_discussions', @merge_request_for_resolving_discussions.iid
- else
= icon('exclamation-triangle')
You can't automatically mark all discussions in
= link_to @merge_request_for_resolving_discussions.to_reference, merge_request_path(@merge_request_for_resolving_discussions)
as resolved. Ask someone with sufficient rights to resolve the them.
- is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?)
.row-content-block{class: (is_footer ? "footer-block" : "middle-block")}
- if issuable.new_record?
......
......@@ -4,22 +4,22 @@
%ul.nav-links.issues-state-filters
%li{class: ("active" if params[:state] == 'opened')}
= link_to page_filter_path(state: 'opened', label: true), title: "Filter by #{page_context_word} that are currently opened." do
= link_to page_filter_path(state: 'opened', label: true), id: 'state-opened', title: "Filter by #{page_context_word} that are currently opened." do
#{issuables_state_counter_text(type, :opened)}
- if type == :merge_requests
%li{class: ("active" if params[:state] == 'merged')}
= link_to page_filter_path(state: 'merged', label: true), title: 'Filter by merge requests that are currently merged.' do
= link_to page_filter_path(state: 'merged', label: true), id: 'state-merged', title: 'Filter by merge requests that are currently merged.' do
#{issuables_state_counter_text(type, :merged)}
%li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed', label: true), title: 'Filter by merge requests that are currently closed and unmerged.' do
= link_to page_filter_path(state: 'closed', label: true), id: 'state-closed', title: 'Filter by merge requests that are currently closed and unmerged.' do
#{issuables_state_counter_text(type, :closed)}
- else
%li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed', label: true), title: 'Filter by issues that are currently closed.' do
= link_to page_filter_path(state: 'closed', label: true), id: 'state-all', title: 'Filter by issues that are currently closed.' do
#{issuables_state_counter_text(type, :closed)}
%li{class: ("active" if params[:state] == 'all')}
= link_to page_filter_path(state: 'all', label: true), title: "Show all #{page_context_word}." do
= link_to page_filter_path(state: 'all', label: true), id: 'state-all', title: "Show all #{page_context_word}." do
#{issuables_state_counter_text(type, :all)}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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