Commit 54d9a7d9 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'ce-to-ee-2018-06-05' into 'master'

CE upstream - 2018-06-05 21:46 UTC

Closes gitaly#1221

See merge request gitlab-org/gitlab-ee!5985
parents e50c0c27 58fe0bc1
...@@ -183,7 +183,7 @@ Assigning a team label makes sure issues get the attention of the appropriate ...@@ -183,7 +183,7 @@ Assigning a team label makes sure issues get the attention of the appropriate
people. people.
The current team labels are ~Distribution, ~"CI/CD", ~Discussion, ~Documentation, ~Quality, The current team labels are ~Distribution, ~"CI/CD", ~Discussion, ~Documentation, ~Quality,
~Geo, ~Gitaly, ~Monitoring, ~Platform, ~Release, ~"Security Products" and ~"UX". ~Geo, ~Gitaly, ~Monitoring, ~Platform, ~Release, ~"Security Products", ~"Configuration", and ~"UX".
The descriptions on the [labels page][labels-page] explain what falls under the The descriptions on the [labels page][labels-page] explain what falls under the
responsibility of each team. responsibility of each team.
...@@ -350,7 +350,7 @@ on those issues. Please select someone with relevant experience from the ...@@ -350,7 +350,7 @@ on those issues. Please select someone with relevant experience from the
[GitLab team][team]. If there is nobody mentioned with that expertise look in [GitLab team][team]. If there is nobody mentioned with that expertise look in
the commit history for the affected files to find someone. the commit history for the affected files to find someone.
[described in our handbook]: https://about.gitlab.com/handbook/engineering/issues/issue-triage-policies/ [described in our handbook]: https://about.gitlab.com/handbook/engineering/issue-triage/
[issue bash events]: https://gitlab.com/gitlab-org/gitlab-ce/issues/17815 [issue bash events]: https://gitlab.com/gitlab-org/gitlab-ce/issues/17815
### Feature proposals ### Feature proposals
......
...@@ -390,7 +390,7 @@ end ...@@ -390,7 +390,7 @@ end
group :test do group :test do
gem 'shoulda-matchers', '~> 3.1.2', require: false gem 'shoulda-matchers', '~> 3.1.2', require: false
gem 'email_spec', '~> 1.6.0' gem 'email_spec', '~> 2.2.0'
gem 'json-schema', '~> 2.8.0' gem 'json-schema', '~> 2.8.0'
gem 'webmock', '~> 2.3.2' gem 'webmock', '~> 2.3.2'
gem 'rails-controller-testing' if rails5? # Rails5 only gem. gem 'rails-controller-testing' if rails5? # Rails5 only gem.
......
...@@ -180,7 +180,7 @@ GEM ...@@ -180,7 +180,7 @@ GEM
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
doorkeeper (4.3.2) doorkeeper (4.3.2)
railties (>= 4.2) railties (>= 4.2)
doorkeeper-openid_connect (1.3.0) doorkeeper-openid_connect (1.4.0)
doorkeeper (~> 4.3) doorkeeper (~> 4.3)
json-jwt (~> 1.6) json-jwt (~> 1.6)
dropzonejs-rails (0.7.2) dropzonejs-rails (0.7.2)
...@@ -199,9 +199,10 @@ GEM ...@@ -199,9 +199,10 @@ GEM
faraday faraday
multi_json multi_json
email_reply_trimmer (0.1.6) email_reply_trimmer (0.1.6)
email_spec (1.6.0) email_spec (2.2.0)
htmlentities (~> 4.3.3)
launchy (~> 2.1) launchy (~> 2.1)
mail (~> 2.2) mail (~> 2.7)
encryptor (3.0.0) encryptor (3.0.0)
equalizer (0.0.11) equalizer (0.0.11)
erubis (2.7.0) erubis (2.7.0)
...@@ -1049,7 +1050,7 @@ DEPENDENCIES ...@@ -1049,7 +1050,7 @@ DEPENDENCIES
elasticsearch-model (~> 0.1.9) elasticsearch-model (~> 0.1.9)
elasticsearch-rails (~> 0.1.9) elasticsearch-rails (~> 0.1.9)
email_reply_trimmer (~> 0.1) email_reply_trimmer (~> 0.1)
email_spec (~> 1.6.0) email_spec (~> 2.2.0)
factory_bot_rails (~> 4.8.2) factory_bot_rails (~> 4.8.2)
faraday (~> 0.12) faraday (~> 0.12)
faraday_middleware-aws-signers-v4 faraday_middleware-aws-signers-v4
......
...@@ -201,9 +201,10 @@ GEM ...@@ -201,9 +201,10 @@ GEM
faraday faraday
multi_json multi_json
email_reply_trimmer (0.1.10) email_reply_trimmer (0.1.10)
email_spec (1.6.0) email_spec (2.2.0)
htmlentities (~> 4.3.3)
launchy (~> 2.1) launchy (~> 2.1)
mail (~> 2.2) mail (~> 2.7)
encryptor (3.0.0) encryptor (3.0.0)
equalizer (0.0.11) equalizer (0.0.11)
erubis (2.7.0) erubis (2.7.0)
...@@ -1050,7 +1051,7 @@ DEPENDENCIES ...@@ -1050,7 +1051,7 @@ DEPENDENCIES
elasticsearch-model (~> 0.1.9) elasticsearch-model (~> 0.1.9)
elasticsearch-rails (~> 0.1.9) elasticsearch-rails (~> 0.1.9)
email_reply_trimmer (~> 0.1) email_reply_trimmer (~> 0.1)
email_spec (~> 1.6.0) email_spec (~> 2.2.0)
factory_bot_rails (~> 4.8.2) factory_bot_rails (~> 4.8.2)
faraday (~> 0.12) faraday (~> 0.12)
faraday_middleware-aws-signers-v4 faraday_middleware-aws-signers-v4
......
...@@ -176,6 +176,7 @@ the stable branch are: ...@@ -176,6 +176,7 @@ the stable branch are:
* Fixes for [regressions](#regressions) * Fixes for [regressions](#regressions)
* Fixes for security issues * Fixes for security issues
* Fixes or improvements to automated QA scenarios
* New or updated translations (as long as they do not touch application code) * New or updated translations (as long as they do not touch application code)
During the feature freeze all merge requests that are meant to go into the During the feature freeze all merge requests that are meant to go into the
......
...@@ -156,5 +156,5 @@ Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on ...@@ -156,5 +156,5 @@ Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on
## Is it awesome? ## Is it awesome?
Thanks for [asking this question](https://twitter.com/supersloth/status/489462789384056832) Joshua.
[These people](https://twitter.com/gitlab/likes) seem to like it. [These people](https://twitter.com/gitlab/likes) seem to like it.
...@@ -187,7 +187,7 @@ ...@@ -187,7 +187,7 @@
role="row" role="row"
> >
<div <div
class="alert alert-danger alert-block append-bottom-0" class="alert alert-danger alert-block append-bottom-0 clusters-error-alert"
role="gridcell" role="gridcell"
> >
<div> <div>
......
<script> <script>
import $ from 'jquery';
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
...@@ -20,6 +21,13 @@ export default { ...@@ -20,6 +21,13 @@ export default {
}, },
methods: { methods: {
...mapActions(['updateActivityBarView']), ...mapActions(['updateActivityBarView']),
changedActivityView(e, view) {
e.currentTarget.blur();
this.updateActivityBarView(view);
$(e.currentTarget).tooltip('hide');
},
}, },
activityBarViews, activityBarViews,
}; };
...@@ -54,7 +62,7 @@ export default { ...@@ -54,7 +62,7 @@ export default {
:class="{ :class="{
active: currentActivityView === $options.activityBarViews.edit active: currentActivityView === $options.activityBarViews.edit
}" }"
@click.prevent="updateActivityBarView($options.activityBarViews.edit)" @click.prevent="changedActivityView($event, $options.activityBarViews.edit)"
:title="s__('IDE|Edit')" :title="s__('IDE|Edit')"
:aria-label="s__('IDE|Edit')" :aria-label="s__('IDE|Edit')"
> >
...@@ -73,7 +81,7 @@ export default { ...@@ -73,7 +81,7 @@ export default {
:class="{ :class="{
active: currentActivityView === $options.activityBarViews.review active: currentActivityView === $options.activityBarViews.review
}" }"
@click.prevent="updateActivityBarView($options.activityBarViews.review)" @click.prevent="changedActivityView($event, $options.activityBarViews.review)"
:title="s__('IDE|Review')" :title="s__('IDE|Review')"
:aria-label="s__('IDE|Review')" :aria-label="s__('IDE|Review')"
> >
...@@ -92,7 +100,7 @@ export default { ...@@ -92,7 +100,7 @@ export default {
:class="{ :class="{
active: currentActivityView === $options.activityBarViews.commit active: currentActivityView === $options.activityBarViews.commit
}" }"
@click.prevent="updateActivityBarView($options.activityBarViews.commit)" @click.prevent="changedActivityView($event, $options.activityBarViews.commit)"
:title="s__('IDE|Commit')" :title="s__('IDE|Commit')"
:aria-label="s__('IDE|Commit')" :aria-label="s__('IDE|Commit')"
> >
......
...@@ -426,7 +426,7 @@ export default class LabelsSelect { ...@@ -426,7 +426,7 @@ export default class LabelsSelect {
const tpl = _.template([ const tpl = _.template([
'<% _.each(labels, function(label){ %>', '<% _.each(labels, function(label){ %>',
'<a href="<%- issueUpdateURL.slice(0, issueUpdateURL.lastIndexOf("/")) %>?label_name[]=<%- encodeURIComponent(label.title) %>">', '<a href="<%- issueUpdateURL.slice(0, issueUpdateURL.lastIndexOf("/")) %>?label_name[]=<%- encodeURIComponent(label.title) %>">',
'<span class="label has-tooltip color-label" title="<%- label.description %>" style="background-color: <%- label.color %>; color: <%- label.text_color %>;">', '<span class="badge label has-tooltip color-label" title="<%- label.description %>" style="background-color: <%- label.color %>; color: <%- label.text_color %>;">',
'<%- label.title %>', '<%- label.title %>',
'</span>', '</span>',
'</a>', '</a>',
......
...@@ -14,6 +14,7 @@ export const EPIC_NOTEABLE_TYPE = 'epic'; ...@@ -14,6 +14,7 @@ export const EPIC_NOTEABLE_TYPE = 'epic';
export const MERGE_REQUEST_NOTEABLE_TYPE = 'merge_request'; export const MERGE_REQUEST_NOTEABLE_TYPE = 'merge_request';
export const UNRESOLVE_NOTE_METHOD_NAME = 'delete'; export const UNRESOLVE_NOTE_METHOD_NAME = 'delete';
export const RESOLVE_NOTE_METHOD_NAME = 'post'; export const RESOLVE_NOTE_METHOD_NAME = 'post';
export const DESCRIPTION_TYPE = 'changed the description';
export const NOTEABLE_TYPE_MAPPING = { export const NOTEABLE_TYPE_MAPPING = {
Issue: ISSUE_NOTEABLE_TYPE, Issue: ISSUE_NOTEABLE_TYPE,
......
import { n__, s__, sprintf } from '~/locale';
import { DESCRIPTION_TYPE } from '../constants';
/**
* Changes the description from a note, returns 'changed the description n number of times'
*/
export const changeDescriptionNote = (note, descriptionChangedTimes, timeDifferenceMinutes) => {
const descriptionNote = Object.assign({}, note);
descriptionNote.note_html = sprintf(
s__(`MergeRequest|
%{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}`),
{
paragraphStart: '<p dir="auto">',
paragraphEnd: '</p>',
descriptionChangedTimes,
timeDifferenceMinutes: n__('within %d minute ', 'within %d minutes ', timeDifferenceMinutes),
},
false,
);
descriptionNote.times_updated = descriptionChangedTimes;
return descriptionNote;
};
/**
* Checks the time difference between two notes from their 'created_at' dates
* returns an integer
*/
export const getTimeDifferenceMinutes = (noteBeggining, noteEnd) => {
const descriptionNoteBegin = new Date(noteBeggining.created_at);
const descriptionNoteEnd = new Date(noteEnd.created_at);
const timeDifferenceMinutes = (descriptionNoteEnd - descriptionNoteBegin) / 1000 / 60;
return Math.ceil(timeDifferenceMinutes);
};
/**
* Checks if a note is a system note and if the content is description
*
* @param {Object} note
* @returns {Boolean}
*/
export const isDescriptionSystemNote = note => note.system && note.note === DESCRIPTION_TYPE;
/**
* Collapses the system notes of a description type, e.g. Changed the description, n minutes ago
* the notes will collapse as long as they happen no more than 10 minutes away from each away
* in between the notes can be anything, another type of system note
* (such as 'changed the weight') or a comment.
*
* @param {Array} notes
* @returns {Array}
*/
export const collapseSystemNotes = notes => {
let lastDescriptionSystemNote = null;
let lastDescriptionSystemNoteIndex = -1;
let descriptionChangedTimes = 1;
return notes.slice(0).reduce((acc, currentNote) => {
const note = currentNote.notes[0];
if (isDescriptionSystemNote(note)) {
// is it the first one?
if (!lastDescriptionSystemNote) {
lastDescriptionSystemNote = note;
lastDescriptionSystemNoteIndex = acc.length;
} else if (lastDescriptionSystemNote) {
const timeDifferenceMinutes = getTimeDifferenceMinutes(
lastDescriptionSystemNote,
note,
);
// are they less than 10 minutes appart?
if (timeDifferenceMinutes > 10) {
// reset counter
descriptionChangedTimes = 1;
// update the previous system note
lastDescriptionSystemNote = note;
lastDescriptionSystemNoteIndex = acc.length;
} else {
// increase counter
descriptionChangedTimes += 1;
// delete the previous one
acc.splice(lastDescriptionSystemNoteIndex, 1);
// replace the text of the current system note with the collapsed note.
currentNote.notes.splice(
0,
1,
changeDescriptionNote(note, descriptionChangedTimes, timeDifferenceMinutes),
);
// update the previous system note index
lastDescriptionSystemNoteIndex = acc.length;
}
}
}
acc.push(currentNote);
return acc;
}, []);
};
// for babel-rewire
export default {};
import _ from 'underscore'; import _ from 'underscore';
import { collapseSystemNotes } from './collapse_utils';
export const notes = state => collapseSystemNotes(state.notes);
export const notes = state => state.notes;
export const targetNoteHash = state => state.targetNoteHash; export const targetNoteHash = state => state.targetNoteHash;
export const getNotesData = state => state.notesData; export const getNotesData = state => state.notesData;
......
...@@ -124,15 +124,18 @@ ...@@ -124,15 +124,18 @@
break; break;
} }
}, },
hideOnSmallScreen(item) {
return !item.first && !item.last && !item.next && !item.prev && !item.active;
},
}, },
}; };
</script> </script>
<template> <template>
<div <div
v-if="showPagination" v-if="showPagination"
class="gl-pagination" class="gl-pagination prepend-top-default"
> >
<ul class="pagination clearfix"> <ul class="pagination justify-content-center">
<li <li
v-for="(item, index) in getItems" v-for="(item, index) in getItems"
:key="index" :key="index"
...@@ -142,12 +145,17 @@ ...@@ -142,12 +145,17 @@
'js-next-button': item.next, 'js-next-button': item.next,
'js-last-button': item.last, 'js-last-button': item.last,
'js-first-button': item.first, 'js-first-button': item.first,
'd-none d-md-block': hideOnSmallScreen(item),
separator: item.separator, separator: item.separator,
active: item.active, active: item.active,
disabled: item.disabled disabled: item.disabled || item.separator
}" }"
class="page-item"
> >
<a @click.prevent="changePage(item.title, item.disabled)"> <a
@click.prevent="changePage(item.title, item.disabled)"
class="page-link"
>
{{ item.title }} {{ item.title }}
</a> </a>
</li> </li>
......
...@@ -24,16 +24,54 @@ html { ...@@ -24,16 +24,54 @@ html {
font-size: 14px; font-size: 14px;
} }
legend {
border-bottom: 1px solid $border-color;
margin-bottom: 20px;
}
button, button,
html [type="button"], html [type="button"],
[type="reset"], [type="reset"],
[type="submit"] { [type="submit"],
[role="button"] {
// Override bootstrap reboot // Override bootstrap reboot
-webkit-appearance: inherit; -webkit-appearance: inherit;
cursor: pointer;
} }
[role="button"] { h1,
cursor: pointer; h2,
h3,
h4,
h5,
h6 {
color: $gl-text-color;
font-weight: 600;
}
h1,
.h1,
h2,
.h2,
h3,
.h3 {
margin-top: 20px;
margin-bottom: 10px;
}
h4,
.h4,
h5,
.h5,
h6,
.h6 {
margin-top: 10px;
margin-bottom: 10px;
}
h5,
.h5 {
font-size: $gl-font-size;
} }
input[type="file"] { input[type="file"] {
...@@ -59,6 +97,10 @@ a { ...@@ -59,6 +97,10 @@ a {
} }
} }
kbd {
display: inline-block;
}
code { code {
padding: 2px 4px; padding: 2px 4px;
color: $red-600; color: $red-600;
...@@ -69,6 +111,11 @@ code { ...@@ -69,6 +111,11 @@ code {
background-color: inherit; background-color: inherit;
padding: unset; padding: unset;
} }
.build-trace & {
background-color: inherit;
padding: inherit;
}
} }
.code { .code {
...@@ -181,7 +228,9 @@ table { ...@@ -181,7 +228,9 @@ table {
border-bottom: 0; border-bottom: 0;
.nav-link { .nav-link {
border: 0; border-top: 0;
border-left: 0;
border-right: 0;
} }
.nav-item { .nav-item {
......
...@@ -35,6 +35,12 @@ ...@@ -35,6 +35,12 @@
@include media-breakpoint-down(xs) { @include media-breakpoint-down(xs) {
width: 100%; width: 100%;
} }
&.projects-dropdown-menu {
padding: 0;
overflow-y: initial;
max-height: initial;
}
} }
.dropdown-toggle, .dropdown-toggle,
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
} }
&.active > a, &.active > a,
&.dropdown.open > a { &.dropdown.show > a {
color: $color-900; color: $color-900;
background-color: $color-alternate; background-color: $color-alternate;
} }
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
} }
&.active > a, &.active > a,
&.dropdown.open > a { &.dropdown.show > a {
color: $color-900; color: $color-900;
background-color: $color-alternate; background-color: $color-alternate;
......
...@@ -297,12 +297,6 @@ ...@@ -297,12 +297,6 @@
display: flex; display: flex;
margin: 0 0 0 6px; margin: 0 0 0 6px;
.projects-dropdown-menu {
padding: 0;
overflow-y: initial;
max-height: initial;
}
.dropdown-chevron { .dropdown-chevron {
position: relative; position: relative;
top: -1px; top: -1px;
......
...@@ -115,9 +115,3 @@ body { ...@@ -115,9 +115,3 @@ body {
.with-performance-bar .layout-page { .with-performance-bar .layout-page {
margin-top: $header-height + $performance-bar-height; margin-top: $header-height + $performance-bar-height;
} }
.vertical-center {
min-height: 100vh;
display: flex;
align-items: center;
}
.gl-pagination { .gl-pagination {
text-align: center; a {
border-top: 1px solid $border-color; color: inherit;
margin: 0; text-decoration: none;
margin-top: 0;
.pagination {
padding: 0;
margin: 20px 0;
a {
cursor: pointer;
}
.separator,
.separator:hover {
a {
cursor: default;
background-color: $gray-light;
padding: $gl-vert-padding;
}
}
}
.gap,
.gap:hover {
background-color: $gray-light;
padding: $gl-vert-padding;
cursor: default;
}
}
.card > .gl-pagination {
margin: 0;
}
/**
* Extra-small screen pagination.
*/
@media (max-width: 320px) {
.gl-pagination {
.first,
.last {
display: none;
}
.page-item {
display: none;
&.active {
display: inline;
}
}
}
}
/**
* Small screen pagination
*/
@include media-breakpoint-down(xs) {
.gl-pagination {
.pagination li a {
padding: 6px 10px;
}
.page-item {
display: none;
&.active {
display: inline;
}
}
}
}
/**
* Medium screen pagination
*/
@media (min-width: map-get($grid-breakpoints, xs)) and (max-width: map-get($grid-breakpoints, sm)) {
.gl-pagination {
.page-item {
display: none;
&.active,
&.sibling {
display: inline;
}
}
} }
} }
...@@ -11,15 +11,15 @@ ...@@ -11,15 +11,15 @@
padding-top: $gl-padding; padding-top: $gl-padding;
} }
.panel { .card {
.panel-heading { .card-header {
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
line-height: $line-height-base; line-height: $line-height-base;
.title { .card-title {
display: flex; display: flex;
align-items: center; align-items: center;
...@@ -34,6 +34,8 @@ ...@@ -34,6 +34,8 @@
.navbar-collapse { .navbar-collapse {
padding-right: 0; padding-right: 0;
flex-grow: 0;
flex-basis: auto;
.navbar-nav { .navbar-nav {
margin: 0; margin: 0;
......
...@@ -114,26 +114,27 @@ ...@@ -114,26 +114,27 @@
font-size: 0.95em; font-size: 0.95em;
} }
blockquote,
.blockquote { .blockquote {
color: $gl-grayish-blue; color: $gl-grayish-blue;
font-size: inherit; font-size: inherit;
padding: 8px 24px; padding: 8px 24px;
margin: 16px 0; margin: 16px 0;
border-left: 3px solid $white-dark; border-left: 3px solid $white-dark;
}
.blockquote:dir(rtl) { &:dir(rtl) {
border-left: 0; border-left: 0;
border-right: 3px solid $white-dark; border-right: 3px solid $white-dark;
} }
.blockquote p { p {
color: $gl-grayish-blue !important; color: $gl-grayish-blue !important;
font-size: inherit; font-size: inherit;
line-height: 1.5; line-height: 1.5;
&:last-child { &:last-child {
margin: 0; margin: 0;
}
} }
} }
......
...@@ -138,6 +138,7 @@ pre { ...@@ -138,6 +138,7 @@ pre {
margin: 0; margin: 0;
} }
blockquote,
.blockquote { .blockquote {
color: $gl-grayish-blue; color: $gl-grayish-blue;
padding: 0 0 0 15px; padding: 0 0 0 15px;
......
...@@ -13,6 +13,10 @@ ...@@ -13,6 +13,10 @@
max-width: 100%; max-width: 100%;
} }
.clusters-error-alert {
width: 100%;
}
.clusters-container { .clusters-container {
.nav-bar-right { .nav-bar-right {
padding: $gl-padding-top $gl-padding; padding: $gl-padding-top $gl-padding;
......
...@@ -489,6 +489,15 @@ ...@@ -489,6 +489,15 @@
.sidebar-collapsed-user { .sidebar-collapsed-user {
padding-bottom: 0; padding-bottom: 0;
margin-bottom: 10px; margin-bottom: 10px;
.author_link {
padding-left: 0;
.avatar {
position: static;
margin: 0;
}
}
} }
.issuable-header-btn { .issuable-header-btn {
......
...@@ -196,6 +196,10 @@ ...@@ -196,6 +196,10 @@
.prioritized-labels { .prioritized-labels {
margin-bottom: 30px; margin-bottom: 30px;
h5 {
font-size: $gl-font-size;
}
.add-priority { .add-priority {
display: none; display: none;
color: $gray-light; color: $gray-light;
...@@ -210,6 +214,10 @@ ...@@ -210,6 +214,10 @@
} }
.other-labels { .other-labels {
h5 {
font-size: $gl-font-size;
}
.remove-priority { .remove-priority {
display: none; display: none;
} }
......
...@@ -183,7 +183,7 @@ ...@@ -183,7 +183,7 @@
svg { svg {
position: relative; position: relative;
top: -1px; top: -2px;
} }
.ide-file-changed-icon { .ide-file-changed-icon {
...@@ -458,6 +458,10 @@ ...@@ -458,6 +458,10 @@
width: auto; width: auto;
margin-right: 0; margin-right: 0;
a {
height: 60px;
}
a:hover, a:hover,
a:focus { a:focus {
text-decoration: none; text-decoration: none;
...@@ -718,9 +722,17 @@ ...@@ -718,9 +722,17 @@
} }
.ide-new-btn { .ide-new-btn {
.btn {
padding-top: 3px;
padding-bottom: 3px;
}
.dropdown {
display: flex;
}
.dropdown-toggle svg { .dropdown-toggle svg {
margin-top: -2px; top: 0;
margin-bottom: 2px;
} }
.dropdown-menu { .dropdown-menu {
...@@ -877,6 +889,7 @@ ...@@ -877,6 +889,7 @@
border-top: 1px solid transparent; border-top: 1px solid transparent;
border-bottom: 1px solid transparent; border-bottom: 1px solid transparent;
outline: 0; outline: 0;
cursor: pointer;
svg { svg {
margin: 0 auto; margin: 0 auto;
......
...@@ -174,7 +174,7 @@ ...@@ -174,7 +174,7 @@
.option-description, .option-description,
.option-disabled-reason { .option-disabled-reason {
margin-left: 45px; margin-left: 30px;
color: $project-option-descr-color; color: $project-option-descr-color;
} }
......
...@@ -22,9 +22,9 @@ ...@@ -22,9 +22,9 @@
header, header,
nav, nav,
nav.main-nav,
nav.navbar-collapse, nav.navbar-collapse,
nav.navbar-collapse.collapse, nav.navbar-collapse.collapse,
.nav-sidebar,
.profiler-results, .profiler-results,
.tree-ref-holder, .tree-ref-holder,
.tree-holder .breadcrumb, .tree-holder .breadcrumb,
...@@ -38,7 +38,8 @@ ul.notes-form, ...@@ -38,7 +38,8 @@ ul.notes-form,
.edit-link, .edit-link,
.note-action-button, .note-action-button,
.right-sidebar, .right-sidebar,
.flash-container { .flash-container,
#js-peek {
display: none !important; display: none !important;
} }
......
...@@ -24,7 +24,9 @@ module Groups ...@@ -24,7 +24,9 @@ module Groups
# Make the `search` param consistent for the frontend, # Make the `search` param consistent for the frontend,
# which will be using `filter`. # which will be using `filter`.
params[:search] ||= params[:filter] if params[:filter] params[:search] ||= params[:filter] if params[:filter]
params.permit(:sort, :search) # Don't show archived projects
params[:non_archived] = true
params.permit(:sort, :search, :non_archived)
end end
end end
end end
......
...@@ -18,7 +18,7 @@ class Projects::LfsStorageController < Projects::GitHttpClientController ...@@ -18,7 +18,7 @@ class Projects::LfsStorageController < Projects::GitHttpClientController
def upload_authorize def upload_authorize
set_workhorse_internal_api_content_type set_workhorse_internal_api_content_type
authorized = LfsObjectUploader.workhorse_authorize authorized = LfsObjectUploader.workhorse_authorize(has_length: true)
authorized.merge!(LfsOid: oid, LfsSize: size) authorized.merge!(LfsOid: oid, LfsSize: size)
render json: authorized render json: authorized
......
class Projects::MilestonesController < Projects::ApplicationController class Projects::MilestonesController < Projects::ApplicationController
include Gitlab::Utils::StrongMemoize
include MilestoneActions include MilestoneActions
before_action :check_issuables_available! before_action :check_issuables_available!
...@@ -103,7 +104,7 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -103,7 +104,7 @@ class Projects::MilestonesController < Projects::ApplicationController
protected protected
def milestones def milestones
@milestones ||= begin strong_memoize(:milestones) do
MilestonesFinder.new(search_params).execute MilestonesFinder.new(search_params).execute
end end
end end
...@@ -121,10 +122,10 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -121,10 +122,10 @@ class Projects::MilestonesController < Projects::ApplicationController
end end
def search_params def search_params
if @project.group && can?(current_user, :read_group, @project.group) if request.format.json? && @project.group && can?(current_user, :read_group, @project.group)
group = @project.group groups = @project.group.self_and_ancestors
end end
params.permit(:state).merge(project_ids: @project.id, group_ids: group&.id) params.permit(:state).merge(project_ids: @project.id, group_ids: groups&.select(:id))
end end
end end
...@@ -13,6 +13,10 @@ module Users ...@@ -13,6 +13,10 @@ module Users
def index def index
@redirect = redirect_path @redirect = redirect_path
if @term.accepted_by_user?(current_user)
flash.now[:notice] = "You have already accepted the Terms of Service as #{current_user.to_reference}"
end
end end
def accept def accept
......
...@@ -37,7 +37,7 @@ module ApplicationSettingsHelper ...@@ -37,7 +37,7 @@ module ApplicationSettingsHelper
# Return a group of checkboxes that use Bootstrap's button plugin for a # Return a group of checkboxes that use Bootstrap's button plugin for a
# toggle button effect. # toggle button effect.
def restricted_level_checkboxes(help_block_id, checkbox_name) def restricted_level_checkboxes(help_block_id, checkbox_name, options = {})
Gitlab::VisibilityLevel.values.map do |level| Gitlab::VisibilityLevel.values.map do |level|
checked = restricted_visibility_levels(true).include?(level) checked = restricted_visibility_levels(true).include?(level)
css_class = checked ? 'active' : '' css_class = checked ? 'active' : ''
...@@ -47,6 +47,7 @@ module ApplicationSettingsHelper ...@@ -47,6 +47,7 @@ module ApplicationSettingsHelper
check_box_tag(checkbox_name, level, checked, check_box_tag(checkbox_name, level, checked,
autocomplete: 'off', autocomplete: 'off',
'aria-describedby' => help_block_id, 'aria-describedby' => help_block_id,
'class' => options[:class],
id: tag_name) + visibility_level_icon(level) + visibility_level_label(level) id: tag_name) + visibility_level_icon(level) + visibility_level_label(level)
end end
end end
...@@ -54,7 +55,7 @@ module ApplicationSettingsHelper ...@@ -54,7 +55,7 @@ module ApplicationSettingsHelper
# Return a group of checkboxes that use Bootstrap's button plugin for a # Return a group of checkboxes that use Bootstrap's button plugin for a
# toggle button effect. # toggle button effect.
def import_sources_checkboxes(help_block_id) def import_sources_checkboxes(help_block_id, options = {})
Gitlab::ImportSources.options.map do |name, source| Gitlab::ImportSources.options.map do |name, source|
checked = Gitlab::CurrentSettings.import_sources.include?(source) checked = Gitlab::CurrentSettings.import_sources.include?(source)
css_class = checked ? 'active' : '' css_class = checked ? 'active' : ''
...@@ -64,6 +65,7 @@ module ApplicationSettingsHelper ...@@ -64,6 +65,7 @@ module ApplicationSettingsHelper
check_box_tag(checkbox_name, source, checked, check_box_tag(checkbox_name, source, checked,
autocomplete: 'off', autocomplete: 'off',
'aria-describedby' => help_block_id, 'aria-describedby' => help_block_id,
'class' => options[:class],
id: name.tr(' ', '_')) + name id: name.tr(' ', '_')) + name
end end
end end
......
...@@ -240,6 +240,14 @@ module ProjectsHelper ...@@ -240,6 +240,14 @@ module ProjectsHelper
"git push --set-upstream #{repository_url}/$(git rev-parse --show-toplevel | xargs basename).git $(git rev-parse --abbrev-ref HEAD)" "git push --set-upstream #{repository_url}/$(git rev-parse --show-toplevel | xargs basename).git $(git rev-parse --abbrev-ref HEAD)"
end end
def show_xcode_link?(project = @project)
browser.platform.mac? && project.repository.xcode_project?
end
def xcode_uri_to_repo(project = @project)
"xcode://clone?repo=#{CGI.escape(default_url_to_repo(project))}"
end
private private
def get_project_nav_tabs(project, current_user) def get_project_nav_tabs(project, current_user)
...@@ -455,7 +463,10 @@ module ProjectsHelper ...@@ -455,7 +463,10 @@ module ProjectsHelper
exports_path = File.join(Settings.shared['path'], 'tmp/project_exports') exports_path = File.join(Settings.shared['path'], 'tmp/project_exports')
filtered_message = message.strip.gsub(exports_path, "[REPO EXPORT PATH]") filtered_message = message.strip.gsub(exports_path, "[REPO EXPORT PATH]")
disk_path = Gitlab.config.repositories.storages[project.repository_storage].legacy_disk_path disk_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
Gitlab.config.repositories.storages[project.repository_storage].legacy_disk_path
end
filtered_message.gsub(disk_path.chomp('/'), "[REPOS PATH]") filtered_message.gsub(disk_path.chomp('/'), "[REPOS PATH]")
end end
......
class ApplicationSetting class ApplicationSetting
class Term < ActiveRecord::Base class Term < ActiveRecord::Base
include CacheMarkdownField include CacheMarkdownField
has_many :term_agreements
validates :terms, presence: true validates :terms, presence: true
...@@ -9,5 +10,10 @@ class ApplicationSetting ...@@ -9,5 +10,10 @@ class ApplicationSetting
def self.latest def self.latest
order(:id).last order(:id).last
end end
def accepted_by_user?(user)
user.accepted_term_id == id ||
term_agreements.accepted.where(user: user).exists?
end
end end
end end
...@@ -59,6 +59,11 @@ module Ci ...@@ -59,6 +59,11 @@ module Ci
where('(artifacts_file IS NOT NULL AND artifacts_file <> ?) OR EXISTS (?)', where('(artifacts_file IS NOT NULL AND artifacts_file <> ?) OR EXISTS (?)',
'', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive) '', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive)
end end
scope :without_archived_trace, ->() do
where('NOT EXISTS (?)', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').trace)
end
scope :with_artifacts_stored_locally, -> { with_artifacts_archive.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) } scope :with_artifacts_stored_locally, -> { with_artifacts_archive.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) }
scope :with_artifacts_not_expired, ->() { with_artifacts_archive.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) } scope :with_artifacts_not_expired, ->() { with_artifacts_archive.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
scope :with_expired_artifacts, ->() { with_artifacts_archive.where('artifacts_expire_at < ?', Time.now) } scope :with_expired_artifacts, ->() { with_artifacts_archive.where('artifacts_expire_at < ?', Time.now) }
...@@ -148,6 +153,7 @@ module Ci ...@@ -148,6 +153,7 @@ module Ci
after_transition any => [:success] do |build| after_transition any => [:success] do |build|
build.run_after_commit do build.run_after_commit do
BuildSuccessWorker.perform_async(id) BuildSuccessWorker.perform_async(id)
PagesWorker.perform_async(:deploy, id) if build.pages_generator?
end end
end end
...@@ -187,6 +193,11 @@ module Ci ...@@ -187,6 +193,11 @@ module Ci
pipeline.manual_actions.where.not(name: name) pipeline.manual_actions.where.not(name: name)
end end
def pages_generator?
Gitlab.config.pages.enabled &&
self.name == 'pages'
end
def playable? def playable?
action? && (manual? || retryable?) action? && (manual? || retryable?)
end end
...@@ -406,8 +417,6 @@ module Ci ...@@ -406,8 +417,6 @@ module Ci
build_data = Gitlab::DataBuilder::Build.build(self) build_data = Gitlab::DataBuilder::Build.build(self)
project.execute_hooks(build_data.dup, :job_hooks) project.execute_hooks(build_data.dup, :job_hooks)
project.execute_services(build_data.dup, :job_hooks) project.execute_services(build_data.dup, :job_hooks)
PagesService.new(build_data).execute
project.running_or_pending_build_count(force: true)
end end
def browsable_artifacts? def browsable_artifacts?
......
...@@ -272,13 +272,17 @@ module Ci ...@@ -272,13 +272,17 @@ module Ci
end end
## ##
# TODO consider switching to persisted stages only in pipelines table # TODO We do not completely switch to persisted stages because of
# (not necessairly in the show pipeline page because of #23257. # race conditions with setting statuses gitlab-ce#23257.
# Hide this behind two feature flags - enabled / disabled and only
# gitlab-ce / everywhere.
# #
def stages def ordered_stages
super return legacy_stages unless complete?
if Feature.enabled?('ci_pipeline_persisted_stages')
stages
else
legacy_stages
end
end end
def legacy_stages def legacy_stages
......
...@@ -220,10 +220,8 @@ module Ci ...@@ -220,10 +220,8 @@ module Ci
cache_attributes(values) cache_attributes(values)
if persist_cached_data? # We save data without validation, it will always change due to `contacted_at`
self.assign_attributes(values) self.update_columns(values) if persist_cached_data?
self.save if self.changed?
end
end end
def pick_build!(build) def pick_build!(build)
......
...@@ -1674,12 +1674,6 @@ class Project < ActiveRecord::Base ...@@ -1674,12 +1674,6 @@ class Project < ActiveRecord::Base
import_state.update_column(:jid, nil) import_state.update_column(:jid, nil)
end end
def running_or_pending_build_count(force: false)
Rails.cache.fetch(['projects', id, 'running_or_pending_build_count'], force: force) do
builds.running_or_pending.count(:all)
end
end
# Lazy loading of the `pipeline_status` attribute # Lazy loading of the `pipeline_status` attribute
def pipeline_status def pipeline_status
@pipeline_status ||= Gitlab::Cache::Ci::ProjectPipelineStatus.load_for_project(self) @pipeline_status ||= Gitlab::Cache::Ci::ProjectPipelineStatus.load_for_project(self)
......
...@@ -277,6 +277,16 @@ class Repository ...@@ -277,6 +277,16 @@ class Repository
end end
end end
def archive_metadata(ref, storage_path, format = "tar.gz", append_sha:)
raw_repository.archive_metadata(
ref,
storage_path,
project.path,
format,
append_sha: append_sha
)
end
def expire_tags_cache def expire_tags_cache
expire_method_caches(%i(tag_names tag_count)) expire_method_caches(%i(tag_names tag_count))
@tags = nil @tags = nil
......
...@@ -2,5 +2,7 @@ class TermAgreement < ActiveRecord::Base ...@@ -2,5 +2,7 @@ class TermAgreement < ActiveRecord::Base
belongs_to :term, class_name: 'ApplicationSetting::Term' belongs_to :term, class_name: 'ApplicationSetting::Term'
belongs_to :user belongs_to :user
scope :accepted, -> { where(accepted: true) }
validates :user, :term, presence: true validates :user, :term, presence: true
end end
class PipelineDetailsEntity < PipelineEntity class PipelineDetailsEntity < PipelineEntity
expose :details do expose :details do
expose :stages, using: StageEntity expose :ordered_stages, as: :stages, using: StageEntity
expose :artifacts, using: BuildArtifactEntity expose :artifacts, using: BuildArtifactEntity
expose :manual_actions, using: BuildActionEntity expose :manual_actions, using: BuildActionEntity
end end
......
class PagesService
attr_reader :data
def initialize(data)
@data = data
end
def execute
return unless Settings.pages.enabled
return unless data[:build_name] == 'pages'
return unless data[:build_status] == 'success'
PagesWorker.perform_async(:deploy, data[:build_id])
end
end
...@@ -11,7 +11,7 @@ module Projects ...@@ -11,7 +11,7 @@ module Projects
order: { due_date: :asc, title: :asc } order: { due_date: :asc, title: :asc }
} }
finder_params[:group_ids] = [@project.group.id] if @project.group finder_params[:group_ids] = @project.group.self_and_ancestors.select(:id) if @project.group
MilestonesFinder.new(finder_params).execute.select([:iid, :title]) MilestonesFinder.new(finder_params).execute.select([:iid, :title])
end end
......
...@@ -40,7 +40,7 @@ module Projects ...@@ -40,7 +40,7 @@ module Projects
end end
def run_auto_devops_pipeline? def run_auto_devops_pipeline?
return false if project.repository.gitlab_ci_yml || !project.auto_devops.previous_changes.include?('enabled') return false if project.repository.gitlab_ci_yml || !project.auto_devops&.previous_changes&.include?('enabled')
project.auto_devops.enabled? || (project.auto_devops.enabled.nil? && Gitlab::CurrentSettings.auto_devops_enabled?) project.auto_devops.enabled? || (project.auto_devops.enabled.nil? && Gitlab::CurrentSettings.auto_devops_enabled?)
end end
......
...@@ -10,8 +10,6 @@ module ObjectStorage ...@@ -10,8 +10,6 @@ module ObjectStorage
UnknownStoreError = Class.new(StandardError) UnknownStoreError = Class.new(StandardError)
ObjectStorageUnavailable = Class.new(StandardError) ObjectStorageUnavailable = Class.new(StandardError)
DIRECT_UPLOAD_TIMEOUT = 4.hours
DIRECT_UPLOAD_EXPIRE_OFFSET = 15.minutes
TMP_UPLOAD_PATH = 'tmp/uploads'.freeze TMP_UPLOAD_PATH = 'tmp/uploads'.freeze
module Store module Store
...@@ -157,9 +155,9 @@ module ObjectStorage ...@@ -157,9 +155,9 @@ module ObjectStorage
model_class.uploader_options.dig(mount_point, :mount_on) || mount_point model_class.uploader_options.dig(mount_point, :mount_on) || mount_point
end end
def workhorse_authorize def workhorse_authorize(has_length:, maximum_size: nil)
{ {
RemoteObject: workhorse_remote_upload_options, RemoteObject: workhorse_remote_upload_options(has_length: has_length, maximum_size: maximum_size),
TempPath: workhorse_local_upload_path TempPath: workhorse_local_upload_path
}.compact }.compact
end end
...@@ -168,23 +166,16 @@ module ObjectStorage ...@@ -168,23 +166,16 @@ module ObjectStorage
File.join(self.root, TMP_UPLOAD_PATH) File.join(self.root, TMP_UPLOAD_PATH)
end end
def workhorse_remote_upload_options def workhorse_remote_upload_options(has_length:, maximum_size: nil)
return unless self.object_store_enabled? return unless self.object_store_enabled?
return unless self.direct_upload_enabled? return unless self.direct_upload_enabled?
id = [CarrierWave.generate_cache_id, SecureRandom.hex].join('-') id = [CarrierWave.generate_cache_id, SecureRandom.hex].join('-')
upload_path = File.join(TMP_UPLOAD_PATH, id) upload_path = File.join(TMP_UPLOAD_PATH, id)
connection = ::Fog::Storage.new(self.object_store_credentials) direct_upload = ObjectStorage::DirectUpload.new(self.object_store_credentials, remote_store_path, upload_path,
expire_at = Time.now + DIRECT_UPLOAD_TIMEOUT + DIRECT_UPLOAD_EXPIRE_OFFSET has_length: has_length, maximum_size: maximum_size)
options = { 'Content-Type' => 'application/octet-stream' }
{ direct_upload.to_hash.merge(ID: id)
ID: id,
Timeout: DIRECT_UPLOAD_TIMEOUT,
GetURL: connection.get_object_url(remote_store_path, upload_path, expire_at),
DeleteURL: connection.delete_object_url(remote_store_path, upload_path, expire_at),
StoreURL: connection.put_object_url(remote_store_path, upload_path, expire_at, options)
}
end end
end end
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
.col-sm-10 .col-sm-10
- checkbox_name = 'application_setting[restricted_visibility_levels][]' - checkbox_name = 'application_setting[restricted_visibility_levels][]'
= hidden_field_tag(checkbox_name) = hidden_field_tag(checkbox_name)
- restricted_level_checkboxes('restricted-visibility-help', checkbox_name).each do |level| - restricted_level_checkboxes('restricted-visibility-help', checkbox_name, class: 'form-check-input').each do |level|
.form-check .form-check
= level = level
%span.form-text.text-muted#restricted-visibility-help %span.form-text.text-muted#restricted-visibility-help
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
= f.label :import_sources, class: 'col-form-label col-sm-2' = f.label :import_sources, class: 'col-form-label col-sm-2'
.col-sm-10 .col-sm-10
= hidden_field_tag 'application_setting[import_sources][]' = hidden_field_tag 'application_setting[import_sources][]'
- import_sources_checkboxes('import-sources-help').each do |source| - import_sources_checkboxes('import-sources-help', class: 'form-check-input').each do |source|
.form-check= source .form-check= source
%span.form-text.text-muted#import-sources-help %span.form-text.text-muted#import-sources-help
Enabled sources for code import during project creation. OmniAuth must be configured for GitHub Enabled sources for code import during project creation. OmniAuth must be configured for GitHub
......
...@@ -118,8 +118,8 @@ ...@@ -118,8 +118,8 @@
.card-body .card-body
= form_for @project, url: transfer_admin_project_path(@project), method: :put do |f| = form_for @project, url: transfer_admin_project_path(@project), method: :put do |f|
.form-group.row .form-group.row
= f.label :new_namespace_id, "Namespace", class: 'col-form-label col-sm-2' = f.label :new_namespace_id, "Namespace", class: 'col-form-label col-sm-3'
.col-sm-10 .col-sm-9
.dropdown .dropdown
= dropdown_toggle('Search for Namespace', { toggle: 'dropdown', field_name: 'new_namespace_id' }, { toggle_class: 'js-namespace-select large' }) = dropdown_toggle('Search for Namespace', { toggle: 'dropdown', field_name: 'new_namespace_id' }, { toggle_class: 'js-namespace-select large' })
.dropdown-menu.dropdown-select .dropdown-menu.dropdown-select
...@@ -129,7 +129,7 @@ ...@@ -129,7 +129,7 @@
= dropdown_loading = dropdown_loading
.form-group.row .form-group.row
.offset-sm-2.col-sm-10 .offset-sm-3.col-sm-9
= f.submit 'Transfer', class: 'btn btn-primary' = f.submit 'Transfer', class: 'btn btn-primary'
.card.repository-check .card.repository-check
......
.nav-block .nav-block.activities
.controls .controls
= link_to group_path(@group, rss_url_options), class: 'btn rss-btn has-tooltip' , title: 'Subscribe' do = link_to group_path(@group, rss_url_options), class: 'btn rss-btn has-tooltip' , title: 'Subscribe' do
%i.fa.fa-rss %i.fa.fa-rss
......
This diff is collapsed.
...@@ -12,11 +12,11 @@ ...@@ -12,11 +12,11 @@
= form_tag personal_access_token_import_gitea_path do = form_tag personal_access_token_import_gitea_path do
.form-group.row .form-group.row
= label_tag :gitea_host_url, 'Gitea Host URL', class: 'col-form-label col-sm-8' = label_tag :gitea_host_url, 'Gitea Host URL', class: 'col-form-label col-sm-2'
.col-sm-4 .col-sm-4
= text_field_tag :gitea_host_url, nil, placeholder: 'https://try.gitea.io', class: 'form-control' = text_field_tag :gitea_host_url, nil, placeholder: 'https://try.gitea.io', class: 'form-control'
.form-group.row .form-group.row
= label_tag :personal_access_token, 'Personal Access Token', class: 'col-form-label col-sm-8' = label_tag :personal_access_token, 'Personal Access Token', class: 'col-form-label col-sm-2'
.col-sm-4 .col-sm-4
= text_field_tag :personal_access_token, nil, class: 'form-control' = text_field_tag :personal_access_token, nil, class: 'form-control'
.form-actions .form-actions
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
= form_tag personal_access_token_import_github_path, method: :post, class: 'form-inline' do = form_tag personal_access_token_import_github_path, method: :post, class: 'form-inline' do
.form-group .form-group
= text_field_tag :personal_access_token, '', class: 'form-control', placeholder: _('Personal Access Token'), size: 40 = text_field_tag :personal_access_token, '', class: 'form-control append-right-8', placeholder: _('Personal Access Token'), size: 40
= submit_tag _('List your GitHub repositories'), class: 'btn btn-success' = submit_tag _('List your GitHub repositories'), class: 'btn btn-success'
-# EE-specific start -# EE-specific start
......
...@@ -5,5 +5,5 @@ ...@@ -5,5 +5,5 @@
-# total_pages: total number of pages -# total_pages: total number of pages
-# per_page: number of items to fetch per page -# per_page: number of items to fetch per page
-# remote: data-remote -# remote: data-remote
%li.first.page-item %li.page-item.js-first-button
= link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, remote: remote, class: 'page-link' = link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, remote: remote, class: 'page-link'
...@@ -4,5 +4,5 @@ ...@@ -4,5 +4,5 @@
-# total_pages: total number of pages -# total_pages: total number of pages
-# per_page: number of items to fetch per page -# per_page: number of items to fetch per page
-# remote: data-remote -# remote: data-remote
%li.page-item.disabled %li.page-item.disabled.d-none.d-md-block
= link_to raw(t 'views.pagination.truncate'), '#', class: 'page-link' = link_to raw(t 'views.pagination.truncate'), '#', class: 'page-link'
...@@ -5,5 +5,5 @@ ...@@ -5,5 +5,5 @@
-# total_pages: total number of pages -# total_pages: total number of pages
-# per_page: number of items to fetch per page -# per_page: number of items to fetch per page
-# remote: data-remote -# remote: data-remote
%li.last.page-item %li.page-item.js-last-button
= link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, {remote: remote, class: 'page-link'} = link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, {remote: remote, class: 'page-link'}
...@@ -8,5 +8,5 @@ ...@@ -8,5 +8,5 @@
- page_url = current_page.last? ? '#' : url - page_url = current_page.last? ? '#' : url
%li.page-item{ class: ('disabled' if current_page.last?) } %li.page-item.js-next-button{ class: ('disabled' if current_page.last?) }
= link_to raw(t 'views.pagination.next'), page_url, rel: 'next', remote: remote, class: 'page-link' = link_to raw(t 'views.pagination.next'), page_url, rel: 'next', remote: remote, class: 'page-link'
...@@ -6,5 +6,5 @@ ...@@ -6,5 +6,5 @@
-# total_pages: total number of pages -# total_pages: total number of pages
-# per_page: number of items to fetch per page -# per_page: number of items to fetch per page
-# remote: data-remote -# remote: data-remote
%li.page-item.js-pagination-page{ class: [active_when(page.current?), ('sibling' if page.next? || page.prev?)] } %li.page-item.js-pagination-page{ class: [active_when(page.current?), ('sibling' if page.next? || page.prev?), ('d-none d-md-block' if !page.current?) ] }
= link_to page, url, { remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil, class: 'page-link' } = link_to page, url, { remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil, class: 'page-link' }
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
-# remote: data-remote -# remote: data-remote
-# paginator: the paginator that renders the pagination tags inside -# paginator: the paginator that renders the pagination tags inside
= paginator.render do = paginator.render do
.gl-pagination .gl-pagination.prepend-top-default
%ul.pagination.justify-content-center %ul.pagination.justify-content-center
- unless current_page.first? - unless current_page.first?
= first_page_tag unless total_pages < 5 # As kaminari will always show the first 5 pages = first_page_tag unless total_pages < 5 # As kaminari will always show the first 5 pages
......
...@@ -8,5 +8,5 @@ ...@@ -8,5 +8,5 @@
- page_url = current_page.first? ? '#' : url - page_url = current_page.first? ? '#' : url
%li.page-item{ class: ('disabled' if current_page.first?) } %li.page-item.js-previous-button{ class: ('disabled' if current_page.first?) }
= link_to raw(t 'views.pagination.previous'), page_url, rel: 'prev', remote: remote, class: 'page-link' = link_to raw(t 'views.pagination.previous'), page_url, rel: 'prev', remote: remote, class: 'page-link'
.gl-pagination .gl-pagination.prepend-top-default
%ul.pagination.clearfix %ul.pagination.justify-content-center
- if previous_path - if previous_path
%li.page-item.prev %li.page-item.prev
= link_to(t('views.pagination.previous'), previous_path, rel: 'prev', class: 'page-link') = link_to(t('views.pagination.previous'), previous_path, rel: 'prev', class: 'page-link')
......
...@@ -14,9 +14,9 @@ ...@@ -14,9 +14,9 @@
%div{ class: "#{container_class} limit-container-width" } %div{ class: "#{container_class} limit-container-width" }
.content{ id: "content-body" } .content{ id: "content-body" }
.panel.panel-default .card
.panel-heading .card-header
.title .card-title
= brand_header_logo = brand_header_logo
- logo_text = brand_header_logo_type - logo_text = brand_header_logo_type
- if logo_text.present? - if logo_text.present?
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f| = form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f|
.col-lg-4.application-theme .col-lg-4.application-theme
%h4.prepend-top-0 %h4.prepend-top-0
s_('Preferences|Navigation theme') = s_('Preferences|Navigation theme')
%p Customize the appearance of the application header and navigation sidebar. %p Customize the appearance of the application header and navigation sidebar.
.col-lg-8.application-theme .col-lg-8.application-theme
- Gitlab::Themes.each do |theme| - Gitlab::Themes.each do |theme|
......
...@@ -49,6 +49,10 @@ ...@@ -49,6 +49,10 @@
.project-clone-holder .project-clone-holder
= render "shared/clone_panel" = render "shared/clone_panel"
- if show_xcode_link?(@project)
.project-action-button.project-xcode.inline
= render "projects/buttons/xcode_link"
- if current_user - if current_user
- if can?(current_user, :download_code, @project) - if can?(current_user, :download_code, @project)
= render 'projects/buttons/download', project: @project, ref: @ref = render 'projects/buttons/download', project: @project, ref: @ref
......
.file-content.blob_file.blob-no-preview .file-content.blob_file.blob-no-preview
.center.render-error.vertical-center .center.render-error
= link_to blob_raw_path do = link_to blob_raw_path do
%h1.light %h1.light
= sprite_icon('download') = sprite_icon('download')
......
%a.btn.btn-default{ href: xcode_uri_to_repo(@project) }
= _("Open in Xcode")
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
-# Only show it in the first page -# Only show it in the first page
- hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1') - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
.prioritized-labels{ class: ('hide' if hide) } .prioritized-labels{ class: ('hide' if hide) }
%h5 Prioritized Labels %h5.prepend-top-10 Prioritized Labels
%ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_project_labels_path(@project) } %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_project_labels_path(@project) }
#js-priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" } #js-priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" }
= render 'shared/empty_states/priority_labels' = render 'shared/empty_states/priority_labels'
......
...@@ -34,16 +34,16 @@ ...@@ -34,16 +34,16 @@
.col-lg-9.js-toggle-container .col-lg-9.js-toggle-container
%ul.nav.nav-tabs.nav-links.gitlab-tabs{ role: 'tablist' } %ul.nav.nav-tabs.nav-links.gitlab-tabs{ role: 'tablist' }
%li{ class: active_when(active_tab == 'blank'), role: 'presentation' } %li.nav-item{ role: 'presentation' }
%a{ href: '#blank-project-pane', id: 'blank-project-tab', data: { toggle: 'tab' }, role: 'tab' } %a.nav-link.active{ href: '#blank-project-pane', id: 'blank-project-tab', data: { toggle: 'tab' }, role: 'tab' }
%span.d-none.d-sm-block Blank project %span.d-none.d-sm-block Blank project
%span.d-block.d-sm-none Blank %span.d-block.d-sm-none Blank
%li{ class: active_when(active_tab == 'template'), role: 'presentation' } %li.nav-item{ role: 'presentation' }
%a{ href: '#create-from-template-pane', id: 'create-from-template-tab', data: { toggle: 'tab' }, role: 'tab' } %a.nav-link{ href: '#create-from-template-pane', id: 'create-from-template-tab', data: { toggle: 'tab' }, role: 'tab' }
%span.d-none.d-sm-block Create from template %span.d-none.d-sm-block Create from template
%span.d-block.d-sm-none Template %span.d-block.d-sm-none Template
%li{ class: active_when(active_tab == 'import'), role: 'presentation' } %li.nav-item{ role: 'presentation' }
%a{ href: '#import-project-pane', id: 'import-project-tab', data: { toggle: 'tab' }, role: 'tab' } %a.nav-link{ href: '#import-project-pane', id: 'import-project-tab', data: { toggle: 'tab' }, role: 'tab' }
%span.d-none.d-sm-block Import project %span.d-none.d-sm-block Import project
%span.d-block.d-sm-none Import %span.d-block.d-sm-none Import
-# EE-specific start -# EE-specific start
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
.errors-holder .errors-holder
.card-body .card-body
%p %p
Removing the pages will prevent from exposing them to outside world. Removing pages will prevent them from being exposed to the outside world.
.form-actions .form-actions
= link_to 'Remove pages', project_pages_path(@project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove" = link_to 'Remove pages', project_pages_path(@project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove"
- else - else
......
- with_label = local_assigns.fetch(:with_label, true) - with_label = local_assigns.fetch(:with_label, true)
.form-group.visibility-level-setting .form-group.row.visibility-level-setting
- if with_label - if with_label
= f.label :visibility_level, class: 'col-form-label col-sm-2' do = f.label :visibility_level, class: 'col-form-label col-sm-2' do
Visibility Level Visibility Level
= link_to icon('question-circle'), help_page_path("public_access/public_access") = link_to icon('question-circle'), help_page_path("public_access/public_access")
%div{ :class => ("col-sm-10" if with_label) } %div{ :class => (with_label ? "col-sm-10" : "col-sm-12") }
- if can_change_visibility_level - if can_change_visibility_level
= render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model) = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model)
- else - else
......
...@@ -15,15 +15,15 @@ ...@@ -15,15 +15,15 @@
- else - else
= render "shared/issuable/form/metadata_merge_request_assignee", issuable: issuable, form: form, has_due_date: has_due_date = render "shared/issuable/form/metadata_merge_request_assignee", issuable: issuable, form: form, has_due_date: has_due_date
.form-group.row.issue-milestone .form-group.row.issue-milestone
= form.label :milestone_id, "Milestone", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-2"}" = form.label :milestone_id, "Milestone", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-sm-2"}"
.col-10{ class: ("col-md-8" if has_due_date) } .col-sm-10{ class: ("col-md-8" if has_due_date) }
.issuable-form-select-holder .issuable-form-select-holder
= render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, show_started: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, show_started: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone"
.form-group.row .form-group.row
- has_labels = @labels && @labels.any? - has_labels = @labels && @labels.any?
= form.label :label_ids, "Labels", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-2"}" = form.label :label_ids, "Labels", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-sm-2"}"
= form.hidden_field :label_ids, multiple: true, value: '' = form.hidden_field :label_ids, multiple: true, value: ''
.col-10{ class: "#{"col-md-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } .col-sm-10{ class: "#{"col-md-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" }
.issuable-form-select-holder .issuable-form-select-holder
= render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false }, dropdown_title: "Select label" = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false }, dropdown_title: "Select label"
......
- redirect_params = { redirect: @redirect } if @redirect - redirect_params = { redirect: @redirect } if @redirect
.panel-content.rendered-terms .card-body.rendered-terms
= markdown_field(@term, :terms) = markdown_field(@term, :terms)
.row-content-block.footer-block.clearfix .card-footer.footer-block.clearfix
- if can?(current_user, :accept_terms, @term) - if can?(current_user, :accept_terms, @term)
.float-right .float-right
= button_to accept_term_path(@term, redirect_params), class: 'btn btn-success prepend-left-8' do = button_to accept_term_path(@term, redirect_params), class: 'btn btn-success prepend-left-8' do
= _('Accept terms') = _('Accept terms')
- else
.pull-right
= link_to root_path, class: 'btn btn-success prepend-left-8' do
= _('Continue')
- if can?(current_user, :decline_terms, @term) - if can?(current_user, :decline_terms, @term)
.float-right .float-right
= button_to decline_term_path(@term, redirect_params), class: 'btn btn-default prepend-left-8' do = button_to decline_term_path(@term, redirect_params), class: 'btn btn-default prepend-left-8' do
......
---
title: Include milestones from parent groups when assigning a milestone to an issue or merge request
merge_request:
author:
type: changed
---
title: Fix repository archive generation when hashed storage is enabled
merge_request: 19441
author:
type: fixed
---
title: Add Open in Xcode link for xcode repositories
merge_request:
author:
type: added
---
title: Check for nil AutoDevOps when saving project CI/CD settings.
merge_request: 19190
author:
type: fixed
---
title: Add flash notice if user has already accepted terms and allow users to continue
to root path
merge_request: 19156
author:
type: changed
---
title: Update email_spec to 2.2.0
merge_request: 19164
author: Takuya Noguchi
type: other
---
title: Add background migrations for archiving legacy job traces
merge_request: 19194
author:
type: performance
---
title: Move PR IO operations out of a transaction
merge_request:
author:
type: performance
---
title: Add support for smarter system notes
merge_request: 17164
author:
type: changed
---
title: Optimise PagesWorker usage
merge_request:
author:
type: performance
---
title: Update runner cached informations without performing validations
merge_request:
author:
type: performance
---
title: Adjust insufficient diff hunks being persisted on NoteDiffFile
merge_request:
author:
type: fixed
---
title: Support direct_upload with S3 Multipart uploads
merge_request:
author:
type: added
---
title: Support rails5 in postgres indexes function and fix some migrations
merge_request: 19400
author: Jasper Maes
type: fixed
---
title: Add migration to disable the usage of DSA keys
merge_request: 19299
author:
type: other
---
title: Remove unused running_or_pending_build_count
merge_request:
author:
type: performance
---
title: Remove N+1 query for author in issues API
merge_request:
author:
type: performance
---
title: Eliminate N+1 queries with authors and push_data_payload in Events API
merge_request:
author:
type: performance
---
title: Eliminate N+1 queries for CI job artifacts in /api/prjoects/:id/pipelines/:pipeline_id/jobs
merge_request:
author:
type: performance
artifacts_object_store = Gitlab.config.artifacts.object_store
if artifacts_object_store.enabled &&
artifacts_object_store.direct_upload &&
artifacts_object_store.connection&.provider.to_s != 'Google'
raise "Only 'Google' is supported as a object storage provider when 'direct_upload' of artifacts is used"
end
class DirectUploadsValidator
SUPPORTED_DIRECT_UPLOAD_PROVIDERS = %w(Google AWS).freeze
ValidationError = Class.new(StandardError)
def verify!(object_store)
return unless object_store.enabled
return unless object_store.direct_upload
return if SUPPORTED_DIRECT_UPLOAD_PROVIDERS.include?(object_store.connection&.provider.to_s)
raise ValidationError, "Only #{SUPPORTED_DIRECT_UPLOAD_PROVIDERS.join(',')} are supported as a object storage provider when 'direct_upload' is used"
end
end
DirectUploadsValidator.new.tap do |validator|
[Gitlab.config.artifacts, Gitlab.config.uploads, Gitlab.config.lfs].each do |uploader|
validator.verify!(uploader.object_store)
end
end
...@@ -107,8 +107,15 @@ module ActiveRecord ...@@ -107,8 +107,15 @@ module ActiveRecord
result.map do |row| result.map do |row|
index_name = row[0] index_name = row[0]
unique = row[1] == 't' unique = if Gitlab.rails5?
row[1]
else
row[1] == 't'
end
indkey = row[2].split(" ") indkey = row[2].split(" ")
if Gitlab.rails5?
indkey = indkey.map(&:to_i)
end
inddef = row[3] inddef = row[3]
oid = row[4] oid = row[4]
......
...@@ -37,7 +37,12 @@ class AddTrigramIndexesForSearching < ActiveRecord::Migration ...@@ -37,7 +37,12 @@ class AddTrigramIndexesForSearching < ActiveRecord::Migration
res = execute("SELECT true AS enabled FROM pg_available_extensions WHERE name = 'pg_trgm' AND installed_version IS NOT NULL;") res = execute("SELECT true AS enabled FROM pg_available_extensions WHERE name = 'pg_trgm' AND installed_version IS NOT NULL;")
row = res.first row = res.first
row && row['enabled'] == 't' ? true : false check = if Gitlab.rails5?
true
else
't'
end
row && row['enabled'] == check ? true : false
end end
def create_trigrams_extension def create_trigrams_extension
......
...@@ -2,12 +2,13 @@ class AddUniqueConstraintToCiVariables < ActiveRecord::Migration ...@@ -2,12 +2,13 @@ class AddUniqueConstraintToCiVariables < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
DOWNTIME = false DOWNTIME = false
INDEX_NAME = 'index_ci_variables_on_project_id_and_key_and_environment_scope'
disable_ddl_transaction! disable_ddl_transaction!
def up def up
unless this_index_exists? unless this_index_exists?
add_concurrent_index(:ci_variables, columns, name: index_name, unique: true) add_concurrent_index(:ci_variables, columns, name: INDEX_NAME, unique: true)
end end
end end
...@@ -18,21 +19,17 @@ class AddUniqueConstraintToCiVariables < ActiveRecord::Migration ...@@ -18,21 +19,17 @@ class AddUniqueConstraintToCiVariables < ActiveRecord::Migration
add_concurrent_index(:ci_variables, :project_id) add_concurrent_index(:ci_variables, :project_id)
end end
remove_concurrent_index(:ci_variables, columns, name: index_name) remove_concurrent_index(:ci_variables, columns, name: INDEX_NAME)
end end
end end
private private
def this_index_exists? def this_index_exists?
index_exists?(:ci_variables, columns, name: index_name) index_exists?(:ci_variables, columns, name: INDEX_NAME)
end end
def columns def columns
@columns ||= [:project_id, :key, :environment_scope] @columns ||= [:project_id, :key, :environment_scope]
end end
def index_name
'index_ci_variables_on_project_id_and_key_and_environment_scope'
end
end end
...@@ -20,9 +20,7 @@ class TurnIssuesDueDateIndexToPartialIndex < ActiveRecord::Migration ...@@ -20,9 +20,7 @@ class TurnIssuesDueDateIndexToPartialIndex < ActiveRecord::Migration
name: NEW_INDEX_NAME name: NEW_INDEX_NAME
) )
# We set the column name to nil as otherwise Rails will ignore the custom remove_concurrent_index_by_name(:issues, OLD_INDEX_NAME)
# index name and remove the wrong index.
remove_concurrent_index(:issues, nil, name: OLD_INDEX_NAME)
end end
def down def down
...@@ -32,6 +30,6 @@ class TurnIssuesDueDateIndexToPartialIndex < ActiveRecord::Migration ...@@ -32,6 +30,6 @@ class TurnIssuesDueDateIndexToPartialIndex < ActiveRecord::Migration
name: OLD_INDEX_NAME name: OLD_INDEX_NAME
) )
remove_concurrent_index(:issues, nil, name: NEW_INDEX_NAME) remove_concurrent_index_by_name(:issues, NEW_INDEX_NAME)
end end
end end
...@@ -31,7 +31,7 @@ class AddForeignKeysToTodos < ActiveRecord::Migration ...@@ -31,7 +31,7 @@ class AddForeignKeysToTodos < ActiveRecord::Migration
end end
def down def down
remove_foreign_key :todos, :users remove_foreign_key :todos, column: :user_id
remove_foreign_key :todos, column: :author_id remove_foreign_key :todos, column: :author_id
remove_foreign_key :todos, :notes remove_foreign_key :todos, :notes
end end
......
class ChangeDefaultValueForDsaKeyRestriction < ActiveRecord::Migration
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def up
change_column :application_settings, :dsa_key_restriction, :integer, null: false,
default: -1
execute("UPDATE application_settings SET dsa_key_restriction = -1")
end
def down
change_column :application_settings, :dsa_key_restriction, :integer, null: false,
default: 0
end
end
class ScheduleToArchiveLegacyTraces < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
BATCH_SIZE = 5000
BACKGROUND_MIGRATION_CLASS = 'ArchiveLegacyTraces'
disable_ddl_transaction!
class Build < ActiveRecord::Base
include EachBatch
self.table_name = 'ci_builds'
self.inheritance_column = :_type_disabled # Disable STI
scope :type_build, -> { where(type: 'Ci::Build') }
scope :finished, -> { where(status: [:success, :failed, :canceled]) }
scope :without_archived_trace, -> do
where('NOT EXISTS (SELECT 1 FROM ci_job_artifacts WHERE ci_builds.id = ci_job_artifacts.job_id AND ci_job_artifacts.file_type = 3)')
end
end
def up
queue_background_migration_jobs_by_range_at_intervals(
::ScheduleToArchiveLegacyTraces::Build.type_build.finished.without_archived_trace,
BACKGROUND_MIGRATION_CLASS,
5.minutes,
batch_size: BATCH_SIZE)
end
def down
# noop
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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