Commit b85d69d1 authored by Phil Hughes's avatar Phil Hughes

Merge branch 'master' into new-nav-fix-contextual-breadcrumbs

parents 7b1affd4 0c4f512b
...@@ -10,7 +10,7 @@ linters: ...@@ -10,7 +10,7 @@ linters:
# Reports when you use improper spacing around ! (the "bang") in !default, # Reports when you use improper spacing around ! (the "bang") in !default,
# !global, !important, and !optional flags. # !global, !important, and !optional flags.
BangFormat: BangFormat:
enabled: false enabled: true
# Whether or not to prefer `border: 0` over `border: none`. # Whether or not to prefer `border: 0` over `border: none`.
BorderZero: BorderZero:
...@@ -43,10 +43,11 @@ linters: ...@@ -43,10 +43,11 @@ linters:
# Rule sets should be ordered as follows: # Rule sets should be ordered as follows:
# - @extend declarations # - @extend declarations
# - @include declarations without inner @content # - @include declarations without inner @content
# - properties, @include declarations with inner @content # - properties
# - @include declarations with inner @content
# - nested rule sets. # - nested rule sets.
DeclarationOrder: DeclarationOrder:
enabled: false enabled: true
# `scss-lint:disable` control comments should be preceded by a comment # `scss-lint:disable` control comments should be preceded by a comment
# explaining why these linters are being disabled for this file. # explaining why these linters are being disabled for this file.
...@@ -178,6 +179,10 @@ linters: ...@@ -178,6 +179,10 @@ linters:
SpaceAfterComma: SpaceAfterComma:
enabled: true enabled: true
# Comment literals should be followed by a space.
SpaceAfterComment:
enabled: false
# Properties should be formatted with a single space separating the colon # Properties should be formatted with a single space separating the colon
# from the property's value. # from the property's value.
SpaceAfterPropertyColon: SpaceAfterPropertyColon:
...@@ -240,7 +245,7 @@ linters: ...@@ -240,7 +245,7 @@ linters:
# Do not use parent selector references (&) when they would otherwise # Do not use parent selector references (&) when they would otherwise
# be unnecessary. # be unnecessary.
UnnecessaryParentReference: UnnecessaryParentReference:
enabled: false enabled: true
# URLs should be valid and not contain protocols or domain names. # URLs should be valid and not contain protocols or domain names.
UrlFormat: UrlFormat:
......
5.3.0 5.3.1
\ No newline at end of file
...@@ -334,7 +334,7 @@ group :development, :test do ...@@ -334,7 +334,7 @@ group :development, :test do
gem 'rubocop', '~> 0.47.1', require: false gem 'rubocop', '~> 0.47.1', require: false
gem 'rubocop-rspec', '~> 1.15.0', require: false gem 'rubocop-rspec', '~> 1.15.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false gem 'scss_lint', '~> 0.54.0', require: false
gem 'haml_lint', '~> 0.21.0', require: false gem 'haml_lint', '~> 0.21.0', require: false
gem 'simplecov', '~> 0.14.0', require: false gem 'simplecov', '~> 0.14.0', require: false
gem 'flay', '~> 2.8.0', require: false gem 'flay', '~> 2.8.0', require: false
......
...@@ -762,9 +762,9 @@ GEM ...@@ -762,9 +762,9 @@ GEM
sawyer (0.8.1) sawyer (0.8.1)
addressable (>= 2.3.5, < 2.6) addressable (>= 2.3.5, < 2.6)
faraday (~> 0.8, < 1.0) faraday (~> 0.8, < 1.0)
scss_lint (0.47.1) scss_lint (0.54.0)
rake (>= 0.9, < 11) rake (>= 0.9, < 13)
sass (~> 3.4.15) sass (~> 3.4.20)
securecompare (1.0.0) securecompare (1.0.0)
seed-fu (2.3.6) seed-fu (2.3.6)
activerecord (>= 3.1) activerecord (>= 3.1)
...@@ -1081,7 +1081,7 @@ DEPENDENCIES ...@@ -1081,7 +1081,7 @@ DEPENDENCIES
rugged (~> 0.25.1.1) rugged (~> 0.25.1.1)
sanitize (~> 2.0) sanitize (~> 2.0)
sass-rails (~> 5.0.6) sass-rails (~> 5.0.6)
scss_lint (~> 0.47.0) scss_lint (~> 0.54.0)
seed-fu (~> 2.3.5) seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9) select2-rails (~> 3.5.9)
sentry-raven (~> 2.5.3) sentry-raven (~> 2.5.3)
......
app/assets/images/new_nav.png

23.2 KB | W: | H:

app/assets/images/new_nav.png

14 KB | W: | H:

app/assets/images/new_nav.png
app/assets/images/new_nav.png
app/assets/images/new_nav.png
app/assets/images/new_nav.png
  • 2-up
  • Swipe
  • Onion skin
...@@ -51,8 +51,9 @@ export default () => { ...@@ -51,8 +51,9 @@ export default () => {
methods: { methods: {
loadFile() { loadFile() {
this.$http.get(el.dataset.endpoint) this.$http.get(el.dataset.endpoint)
.then(response => response.json())
.then((res) => { .then((res) => {
this.json = res.json(); this.json = res;
this.loading = false; this.loading = false;
}) })
.catch((e) => { .catch((e) => {
......
...@@ -81,8 +81,9 @@ $(() => { ...@@ -81,8 +81,9 @@ $(() => {
mounted () { mounted () {
Store.disabled = this.disabled; Store.disabled = this.disabled;
gl.boardService.all() gl.boardService.all()
.then(response => response.json())
.then((resp) => { .then((resp) => {
resp.json().forEach((board) => { resp.forEach((board) => {
const list = Store.addList(board, this.defaultAvatar); const list = Store.addList(board, this.defaultAvatar);
if (list.type === 'closed') { if (list.type === 'closed') {
...@@ -97,7 +98,8 @@ $(() => { ...@@ -97,7 +98,8 @@ $(() => {
Store.addBlankState(); Store.addBlankState();
this.loading = false; this.loading = false;
}).catch(() => new Flash('An error occurred. Please try again.')); })
.catch(() => new Flash('An error occurred. Please try again.'));
}, },
methods: { methods: {
updateTokens() { updateTokens() {
......
...@@ -64,8 +64,9 @@ export default { ...@@ -64,8 +64,9 @@ export default {
// Save the labels // Save the labels
gl.boardService.generateDefaultLists() gl.boardService.generateDefaultLists()
.then((resp) => { .then(resp => resp.json())
resp.json().forEach((listObj) => { .then((data) => {
data.forEach((listObj) => {
const list = Store.findList('title', listObj.title); const list = Store.findList('title', listObj.title);
list.id = listObj.id; list.id = listObj.id;
......
...@@ -88,9 +88,9 @@ gl.issueBoards.IssuesModal = Vue.extend({ ...@@ -88,9 +88,9 @@ gl.issueBoards.IssuesModal = Vue.extend({
return gl.boardService.getBacklog(queryData(this.filter.path, { return gl.boardService.getBacklog(queryData(this.filter.path, {
page: this.page, page: this.page,
per: this.perPage, per: this.perPage,
})).then((res) => { }))
const data = res.json(); .then(resp => resp.json())
.then((data) => {
if (clearIssues) { if (clearIssues) {
this.issues = []; this.issues = [];
} }
......
...@@ -40,9 +40,8 @@ class List { ...@@ -40,9 +40,8 @@ class List {
save () { save () {
return gl.boardService.createList(this.label.id) return gl.boardService.createList(this.label.id)
.then((resp) => { .then(resp => resp.json())
const data = resp.json(); .then((data) => {
this.id = data.id; this.id = data.id;
this.type = data.list_type; this.type = data.list_type;
this.position = data.position; this.position = data.position;
...@@ -91,8 +90,8 @@ class List { ...@@ -91,8 +90,8 @@ class List {
} }
return gl.boardService.getIssuesForList(this.id, data) return gl.boardService.getIssuesForList(this.id, data)
.then((resp) => { .then(resp => resp.json())
const data = resp.json(); .then((data) => {
this.loading = false; this.loading = false;
this.issuesSize = data.size; this.issuesSize = data.size;
...@@ -109,8 +108,8 @@ class List { ...@@ -109,8 +108,8 @@ class List {
this.issuesSize += 1; this.issuesSize += 1;
return gl.boardService.newIssue(this.id, issue) return gl.boardService.newIssue(this.id, issue)
.then((resp) => { .then(resp => resp.json())
const data = resp.json(); .then((data) => {
issue.id = data.iid; issue.id = data.iid;
if (this.issuesSize > 1) { if (this.issuesSize > 1) {
......
...@@ -23,11 +23,6 @@ class BoardService { ...@@ -23,11 +23,6 @@ class BoardService {
url: bulkUpdatePath, url: bulkUpdatePath,
}, },
}); });
Vue.http.interceptors.push((request, next) => {
request.headers['X-CSRF-Token'] = $.rails.csrfToken();
next();
});
} }
all () { all () {
......
...@@ -51,11 +51,11 @@ ...@@ -51,11 +51,11 @@
}, },
methods: { methods: {
successCallback(resp) { successCallback(resp) {
const response = resp.json(); return resp.json().then((response) => {
// depending of the endpoint the response can either bring a `pipelines` key or not.
// depending of the endpoint the response can either bring a `pipelines` key or not. const pipelines = response.pipelines || response;
const pipelines = response.pipelines || response; this.setCommonData(pipelines);
this.setCommonData(pipelines); });
}, },
}, },
}; };
......
/* eslint-disable comma-dangle, object-shorthand, func-names, quote-props, no-else-return, camelcase, no-new, max-len */ /* eslint-disable comma-dangle, object-shorthand, func-names, quote-props, no-else-return, camelcase, max-len */
/* global CommentsStore */ /* global CommentsStore */
/* global ResolveService */ /* global ResolveService */
/* global Flash */ /* global Flash */
...@@ -64,8 +64,6 @@ const ResolveBtn = Vue.extend({ ...@@ -64,8 +64,6 @@ const ResolveBtn = Vue.extend({
}); });
}, },
resolve: function () { resolve: function () {
const errorFlashMsg = 'An error occurred when trying to resolve a comment. Please try again.';
if (!this.canResolve) return; if (!this.canResolve) return;
let promise; let promise;
...@@ -79,24 +77,20 @@ const ResolveBtn = Vue.extend({ ...@@ -79,24 +77,20 @@ const ResolveBtn = Vue.extend({
.resolve(this.noteId); .resolve(this.noteId);
} }
promise.then((response) => { promise
this.loading = false; .then(resp => resp.json())
.then((data) => {
this.loading = false;
if (response.status === 200) {
const data = response.json();
const resolved_by = data ? data.resolved_by : null; const resolved_by = data ? data.resolved_by : null;
CommentsStore.update(this.discussionId, this.noteId, !this.isResolved, resolved_by); CommentsStore.update(this.discussionId, this.noteId, !this.isResolved, resolved_by);
this.discussion.updateHeadline(data); this.discussion.updateHeadline(data);
gl.mrWidget.checkStatus(); gl.mrWidget.checkStatus();
} else {
new Flash(errorFlashMsg);
}
this.updateTooltip(); this.updateTooltip();
}).catch(() => { })
new Flash(errorFlashMsg); .catch(() => new Flash('An error occurred when trying to resolve a comment. Please try again.'));
});
} }
}, },
mounted: function () { mounted: function () {
......
/* eslint-disable class-methods-use-this, one-var, camelcase, no-new, comma-dangle, no-param-reassign, max-len */
/* global Flash */ /* global Flash */
/* global CommentsStore */ /* global CommentsStore */
...@@ -32,27 +31,22 @@ class ResolveServiceClass { ...@@ -32,27 +31,22 @@ class ResolveServiceClass {
promise = this.resolveAll(mergeRequestId, discussionId); promise = this.resolveAll(mergeRequestId, discussionId);
} }
promise.then((response) => { promise
discussion.loading = false; .then(resp => resp.json())
.then((data) => {
if (response.status === 200) { discussion.loading = false;
const data = response.json(); const resolvedBy = data ? data.resolved_by : null;
const resolved_by = data ? data.resolved_by : null;
if (isResolved) { if (isResolved) {
discussion.unResolveAllNotes(); discussion.unResolveAllNotes();
} else { } else {
discussion.resolveAllNotes(resolved_by); discussion.resolveAllNotes(resolvedBy);
} }
gl.mrWidget.checkStatus(); gl.mrWidget.checkStatus();
discussion.updateHeadline(data); discussion.updateHeadline(data);
} else { })
throw new Error('An error occurred when trying to resolve discussion.'); .catch(() => new Flash('An error occurred when trying to resolve a discussion. Please try again.'));
}
}).catch(() => {
new Flash('An error occurred when trying to resolve a discussion. Please try again.');
});
} }
resolveAll(mergeRequestId, discussionId) { resolveAll(mergeRequestId, discussionId) {
...@@ -62,7 +56,7 @@ class ResolveServiceClass { ...@@ -62,7 +56,7 @@ class ResolveServiceClass {
return this.discussionResource.save({ return this.discussionResource.save({
mergeRequestId, mergeRequestId,
discussionId discussionId,
}, {}); }, {});
} }
...@@ -73,7 +67,7 @@ class ResolveServiceClass { ...@@ -73,7 +67,7 @@ class ResolveServiceClass {
return this.discussionResource.delete({ return this.discussionResource.delete({
mergeRequestId, mergeRequestId,
discussionId discussionId,
}, {}); }, {});
} }
} }
......
...@@ -498,9 +498,9 @@ export default { ...@@ -498,9 +498,9 @@ export default {
<div class="table-section section-15 hidden-xs hidden-sm" role="gridcell"> <div class="table-section section-15 hidden-xs hidden-sm" role="gridcell">
<a <a
v-if="shouldRenderBuildName" v-if="shouldRenderBuildName"
class="build-link" class="build-link flex-truncate-parent"
:href="buildPath"> :href="buildPath">
{{buildName}} <span class="flex-truncate-child">{{buildName}}</span>
</a> </a>
</div> </div>
......
export default { export default {
methods: { methods: {
saveData(resp) { saveData(resp) {
const response = { const headers = resp.headers;
headers: resp.headers, return resp.json().then((response) => {
body: resp.json(), this.isLoading = false;
};
this.isLoading = false; this.store.storeAvailableCount(response.available_count);
this.store.storeStoppedCount(response.stopped_count);
this.store.storeAvailableCount(response.body.available_count); this.store.storeEnvironments(response.environments);
this.store.storeStoppedCount(response.body.stopped_count); this.store.setPagination(headers);
this.store.storeEnvironments(response.body.environments); });
this.store.setPagination(response.headers);
}, },
}, },
}; };
...@@ -7,5 +7,8 @@ export default () => { ...@@ -7,5 +7,8 @@ export default () => {
Cookies.set(el.name, el.value, { Cookies.set(el.name, el.value, {
expires: 365 * 10, expires: 365 * 10,
}); });
document.body.scrollTop = 0;
window.location.reload();
}); });
}; };
...@@ -99,8 +99,10 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -99,8 +99,10 @@ document.addEventListener('DOMContentLoaded', () => {
page: currentPath, page: currentPath,
}, document.title, currentPath); }, document.title, currentPath);
this.updateGroups(response.json()); return response.json().then((data) => {
this.updatePagination(response.headers); this.updateGroups(data);
this.updatePagination(response.headers);
});
}) })
.catch(this.handleErrorResponse); .catch(this.handleErrorResponse);
}, },
...@@ -114,18 +116,19 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -114,18 +116,19 @@ document.addEventListener('DOMContentLoaded', () => {
}, },
leaveGroup(group, collection) { leaveGroup(group, collection) {
this.service.leaveGroup(group.leavePath) this.service.leaveGroup(group.leavePath)
.then(resp => resp.json())
.then((response) => { .then((response) => {
$.scrollTo(0); $.scrollTo(0);
this.store.removeGroup(group, collection); this.store.removeGroup(group, collection);
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Flash(response.json().notice, 'notice'); new Flash(response.notice, 'notice');
}) })
.catch((response) => { .catch((error) => {
let message = 'An error occurred. Please try again.'; let message = 'An error occurred. Please try again.';
if (response.status === 403) { if (error.status === 403) {
message = 'Failed to leave the group. Please make sure you are not the only owner'; message = 'Failed to leave the group. Please make sure you are not the only owner';
} }
......
...@@ -202,10 +202,7 @@ export default { ...@@ -202,10 +202,7 @@ export default {
this.poll = new Poll({ this.poll = new Poll({
resource: this.service, resource: this.service,
method: 'getData', method: 'getData',
successCallback: (res) => { successCallback: res => res.json().then(data => this.store.updateState(data)),
const data = res.json();
this.store.updateState(data);
},
errorCallback(err) { errorCallback(err) {
throw new Error(err); throw new Error(err);
}, },
......
...@@ -54,9 +54,8 @@ export default class JobMediator { ...@@ -54,9 +54,8 @@ export default class JobMediator {
} }
successCallback(response) { successCallback(response) {
const data = response.json();
this.state.isLoading = false; this.state.isLoading = false;
this.store.storeJob(data); return response.json().then(data => this.store.storeJob(data));
} }
errorCallback() { errorCallback() {
......
...@@ -1270,7 +1270,7 @@ export default class Notes { ...@@ -1270,7 +1270,7 @@ export default class Notes {
<div class="timeline-entry-inner"> <div class="timeline-entry-inner">
<div class="timeline-icon"> <div class="timeline-icon">
<a href="/${currentUsername}"> <a href="/${currentUsername}">
<img class="avatar s40" src="${currentUserAvatar}"> <img class="avatar s40" src="${currentUserAvatar}" />
</a> </a>
</div> </div>
<div class="timeline-content ${discussionClass}"> <div class="timeline-content ${discussionClass}">
......
...@@ -129,14 +129,11 @@ ...@@ -129,14 +129,11 @@
}, },
successCallback(resp) { successCallback(resp) {
const response = { return resp.json().then((response) => {
headers: resp.headers, this.store.storeCount(response.count);
body: resp.json(), this.store.storePagination(resp.headers);
}; this.setCommonData(response.pipelines);
});
this.store.storeCount(response.body.count);
this.store.storePagination(response.headers);
this.setCommonData(response.body.pipelines);
}, },
}, },
}; };
......
...@@ -73,8 +73,9 @@ export default { ...@@ -73,8 +73,9 @@ export default {
fetchJobs() { fetchJobs() {
this.$http.get(this.stage.dropdown_path) this.$http.get(this.stage.dropdown_path)
.then((response) => { .then(response => response.json())
this.dropdownContent = response.json().html; .then((data) => {
this.dropdownContent = data.html;
this.isLoading = false; this.isLoading = false;
}) })
.catch(() => { .catch(() => {
......
...@@ -40,10 +40,10 @@ export default class pipelinesMediator { ...@@ -40,10 +40,10 @@ export default class pipelinesMediator {
} }
successCallback(response) { successCallback(response) {
const data = response.json(); return response.json().then((data) => {
this.state.isLoading = false;
this.state.isLoading = false; this.store.storePipeline(data);
this.store.storePipeline(data); });
} }
errorCallback() { errorCallback() {
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-else-return, comma-dangle, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-else-return, comma-dangle, max-len */
/* global Mousetrap */ /* global Mousetrap */
/* global findFileURL */
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import findAndFollowLink from './shortcuts_dashboard_navigation'; import findAndFollowLink from './shortcuts_dashboard_navigation';
...@@ -20,6 +19,7 @@ import findAndFollowLink from './shortcuts_dashboard_navigation'; ...@@ -20,6 +19,7 @@ import findAndFollowLink from './shortcuts_dashboard_navigation';
const $globalDropdownMenu = $('.global-dropdown-menu'); const $globalDropdownMenu = $('.global-dropdown-menu');
const $globalDropdownToggle = $('.global-dropdown-toggle'); const $globalDropdownToggle = $('.global-dropdown-toggle');
const findFileURL = document.body.dataset.findFile;
$('.global-dropdown').on('hide.bs.dropdown', () => { $('.global-dropdown').on('hide.bs.dropdown', () => {
$globalDropdownMenu.removeClass('shortcuts'); $globalDropdownMenu.removeClass('shortcuts');
......
...@@ -28,8 +28,8 @@ export default class SidebarMediator { ...@@ -28,8 +28,8 @@ export default class SidebarMediator {
fetch() { fetch() {
this.service.get() this.service.get()
.then((response) => { .then(response => response.json())
const data = response.json(); .then((data) => {
this.store.setAssigneeData(data); this.store.setAssigneeData(data);
this.store.setTimeTrackingData(data); this.store.setTimeTrackingData(data);
}) })
......
...@@ -92,13 +92,13 @@ export default { ...@@ -92,13 +92,13 @@ export default {
:class="{'label-truncated has-tooltip': isBranchTitleLong(mr.targetBranch)}" :class="{'label-truncated has-tooltip': isBranchTitleLong(mr.targetBranch)}"
:title="isBranchTitleLong(mr.targetBranch) ? mr.targetBranch : ''" :title="isBranchTitleLong(mr.targetBranch) ? mr.targetBranch : ''"
data-placement="bottom"> data-placement="bottom">
<a :href="mr.targetBranchPath">{{mr.targetBranch}}</a> <a :href="mr.targetBranchTreePath">{{mr.targetBranch}}</a>
</span> </span>
</strong> </strong>
<span <span
v-if="shouldShowCommitsBehindText" v-if="shouldShowCommitsBehindText"
class="diverged-commits-count"> class="diverged-commits-count">
({{mr.divergedCommitsCount}} {{commitsText}} behind) (<a :href="mr.targetBranchPath">{{mr.divergedCommitsCount}} {{commitsText}} behind</a>)
</span> </span>
</div> </div>
</div> </div>
......
...@@ -48,6 +48,7 @@ export default class MergeRequestStore { ...@@ -48,6 +48,7 @@ export default class MergeRequestStore {
this.sourceBranchLink = data.source_branch_with_namespace_link; this.sourceBranchLink = data.source_branch_with_namespace_link;
this.mergeError = data.merge_error; this.mergeError = data.merge_error;
this.targetBranchPath = data.target_branch_commits_path; this.targetBranchPath = data.target_branch_commits_path;
this.targetBranchTreePath = data.target_branch_tree_path;
this.conflictResolutionPath = data.conflict_resolution_path; this.conflictResolutionPath = data.conflict_resolution_path;
this.cancelAutoMergePath = data.cancel_merge_when_pipeline_succeeds_path; this.cancelAutoMergePath = data.cancel_merge_when_pipeline_succeeds_path;
this.removeWIPPath = data.remove_wip_path; this.removeWIPPath = data.remove_wip_path;
......
...@@ -44,9 +44,8 @@ ...@@ -44,9 +44,8 @@
text: this.$slots.textarea[0].elm.value, text: this.$slots.textarea[0].elm.value,
}, },
) )
.then((res) => { .then(resp => resp.json())
const data = res.json(); .then((data) => {
this.markdownPreviewLoading = false; this.markdownPreviewLoading = false;
this.markdownPreview = data.body; this.markdownPreview = data.body;
......
...@@ -14,11 +14,22 @@ Vue.http.interceptors.push((request, next) => { ...@@ -14,11 +14,22 @@ Vue.http.interceptors.push((request, next) => {
}); });
}); });
// Inject CSRF token so we don't break any tests. // Inject CSRF token and parse headers.
// New Vue Resource version uses Headers, we are expecting a plain object to render pagination
// and polling.
Vue.http.interceptors.push((request, next) => { Vue.http.interceptors.push((request, next) => {
if ($.rails) { if ($.rails) {
// eslint-disable-next-line no-param-reassign request.headers.set('X-CSRF-Token', $.rails.csrfToken());
request.headers['X-CSRF-Token'] = $.rails.csrfToken();
} }
next();
next((response) => {
// Headers object has a `forEach` property that iterates through all values.
const headers = {};
response.headers.forEach((value, key) => {
headers[key] = value;
});
// eslint-disable-next-line no-param-reassign
response.headers = headers;
});
}); });
...@@ -231,11 +231,11 @@ ...@@ -231,11 +231,11 @@
.award-control-icon-positive, .award-control-icon-positive,
.award-control-icon-super-positive { .award-control-icon-super-positive {
@include transition(opacity, transform);
position: absolute; position: absolute;
left: 10px; left: 10px;
bottom: 6px; bottom: 6px;
opacity: 0; opacity: 0;
@include transition(opacity, transform);
} }
.award-control-text { .award-control-text {
......
...@@ -35,8 +35,8 @@ ...@@ -35,8 +35,8 @@
.open { .open {
.dropdown-menu, .dropdown-menu,
.dropdown-menu-nav { .dropdown-menu-nav {
display: block;
@include set-visible; @include set-visible;
display: block;
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
width: 100%; width: 100%;
...@@ -184,13 +184,15 @@ ...@@ -184,13 +184,15 @@
.dropdown-menu, .dropdown-menu,
.dropdown-menu-nav { .dropdown-menu-nav {
@include set-invisible;
display: block; display: block;
position: absolute; position: absolute;
width: 100%; width: auto;
top: 100%; top: 100%;
left: 0; left: 0;
z-index: 9; z-index: 9;
min-width: 240px; min-width: 240px;
max-width: 500px;
margin-top: 2px; margin-top: 2px;
margin-bottom: 0; margin-bottom: 0;
font-size: 14px; font-size: 14px;
...@@ -200,7 +202,6 @@ ...@@ -200,7 +202,6 @@
border: 1px solid $dropdown-border-color; border: 1px solid $dropdown-border-color;
border-radius: $border-radius-base; border-radius: $border-radius-base;
box-shadow: 0 2px 4px $dropdown-shadow-color; box-shadow: 0 2px 4px $dropdown-shadow-color;
@include set-invisible;
@media (max-width: $screen-sm-min) { @media (max-width: $screen-sm-min) {
width: 100%; width: 100%;
...@@ -675,8 +676,8 @@ ...@@ -675,8 +676,8 @@
} }
.pika-single { .pika-single {
position: relative!important; position: relative !important;
top: 0!important; top: 0 !important;
border: 0; border: 0;
box-shadow: none; box-shadow: none;
} }
......
...@@ -368,7 +368,7 @@ ...@@ -368,7 +368,7 @@
margin-right: 0.3em; margin-right: 0.3em;
} }
& > .value { > .value {
font-weight: 600; font-weight: 600;
} }
} }
...@@ -467,7 +467,7 @@ ...@@ -467,7 +467,7 @@
-webkit-flex-direction: column; -webkit-flex-direction: column;
flex-direction: column; flex-direction: column;
&> span { > span {
white-space: normal; white-space: normal;
word-break: break-all; word-break: break-all;
} }
......
...@@ -330,7 +330,7 @@ header { ...@@ -330,7 +330,7 @@ header {
padding-left: 5px; padding-left: 5px;
.nav > li:not(.hidden-xs) { .nav > li:not(.hidden-xs) {
display: table-cell!important; display: table-cell !important;
width: 25%; width: 25%;
a { a {
......
...@@ -61,7 +61,7 @@ ...@@ -61,7 +61,7 @@
&:focus { &:focus {
outline: none; outline: none;
& i { i {
visibility: visible; visibility: visible;
} }
} }
......
...@@ -165,8 +165,8 @@ ...@@ -165,8 +165,8 @@
.cur { .cur {
.avatar { .avatar {
border: 1px solid $white-light;
@include disableAllAnimation; @include disableAllAnimation;
border: 1px solid $white-light;
} }
} }
......
...@@ -100,9 +100,9 @@ ...@@ -100,9 +100,9 @@
} }
.table-mobile-header { .table-mobile-header {
@include flex-max-width(40);
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
text-align: left; text-align: left;
@include flex-max-width(40);
@media (min-width: $screen-md-min) { @media (min-width: $screen-md-min) {
display: none; display: none;
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
.is-dragging { .is-dragging {
// Important because plugin sets inline CSS // Important because plugin sets inline CSS
opacity: 1!important; opacity: 1 !important;
* { * {
-webkit-user-select: none; -webkit-user-select: none;
...@@ -19,8 +19,8 @@ ...@@ -19,8 +19,8 @@
-ms-user-select: none; -ms-user-select: none;
user-select: none; user-select: none;
// !important to make sure no style can override this when dragging // !important to make sure no style can override this when dragging
cursor: -webkit-grabbing!important; cursor: -webkit-grabbing !important;
cursor: grabbing!important; cursor: grabbing !important;
} }
} }
......
...@@ -244,6 +244,10 @@ ...@@ -244,6 +244,10 @@
} }
} }
.block-last {
padding: 16px 0;
}
.trigger-build-variable { .trigger-build-variable {
color: $code-color; color: $code-color;
} }
......
...@@ -250,8 +250,8 @@ ...@@ -250,8 +250,8 @@
} }
.committed_ago { .committed_ago {
float: right;
@extend .cgray; @extend .cgray;
float: right;
} }
} }
} }
......
...@@ -24,9 +24,9 @@ ...@@ -24,9 +24,9 @@
.col-headers { .col-headers {
ul { ul {
@include clearfix;
margin: 0; margin: 0;
padding: 0; padding: 0;
@include clearfix;
} }
li { li {
...@@ -189,8 +189,8 @@ ...@@ -189,8 +189,8 @@
} }
li { li {
list-style-type: none;
@include clearfix; @include clearfix;
list-style-type: none;
} }
.stage-nav-item { .stage-nav-item {
...@@ -281,11 +281,11 @@ ...@@ -281,11 +281,11 @@
} }
.stage-event-item { .stage-event-item {
@include clearfix;
list-style-type: none; list-style-type: none;
padding: 0 0 $gl-padding; padding: 0 0 $gl-padding;
margin: 0 $gl-padding $gl-padding; margin: 0 $gl-padding $gl-padding;
border-bottom: 1px solid $gray-darker; border-bottom: 1px solid $gray-darker;
@include clearfix;
&:last-child { &:last-child {
border-bottom: none; border-bottom: none;
...@@ -307,9 +307,9 @@ ...@@ -307,9 +307,9 @@
&.issue-title, &.issue-title,
&.commit-title, &.commit-title,
&.merge-merquest-title { &.merge-merquest-title {
@include text-overflow();
max-width: 100%; max-width: 100%;
display: block; display: block;
@include text-overflow();
a { a {
color: $gl-text-color; color: $gl-text-color;
......
...@@ -91,6 +91,7 @@ ...@@ -91,6 +91,7 @@
.old_line, .old_line,
.new_line { .new_line {
@include user-select(none);
margin: 0; margin: 0;
border: none; border: none;
padding: 0 5px; padding: 0 5px;
...@@ -99,7 +100,6 @@ ...@@ -99,7 +100,6 @@
min-width: 35px; min-width: 35px;
max-width: 50px; max-width: 50px;
width: 35px; width: 35px;
@include user-select(none);
a { a {
float: left; float: left;
...@@ -354,12 +354,12 @@ ...@@ -354,12 +354,12 @@
} }
&.active { &.active {
cursor: default;
color: $gl-text-color;
&:hover { &:hover {
text-decoration: none; text-decoration: none;
} }
cursor: default;
color: $gl-text-color;
} }
&.disabled { &.disabled {
......
...@@ -96,7 +96,7 @@ ...@@ -96,7 +96,7 @@
overflow: visible; overflow: visible;
} }
& > span { > span {
padding-right: 4px; padding-right: 4px;
} }
......
...@@ -121,10 +121,11 @@ ul.notes { ...@@ -121,10 +121,11 @@ ul.notes {
overflow-y: hidden; overflow-y: hidden;
.note-text { .note-text {
word-wrap: break-word;
@include md-typography; @include md-typography;
// Reset ul style types since we're nested inside a ul already // Reset ul style types since we're nested inside a ul already
@include bulleted-list; @include bulleted-list;
word-wrap: break-word;
ul.task-list { ul.task-list {
ul:not(.task-list) { ul:not(.task-list) {
padding-left: 1.3em; padding-left: 1.3em;
...@@ -250,7 +251,7 @@ ul.notes { ...@@ -250,7 +251,7 @@ ul.notes {
} }
.note-text { .note-text {
& p:first-child { p:first-child {
display: none; display: none;
} }
......
.js-pipeline-schedule-form { .js-pipeline-schedule-form {
.dropdown-select, .dropdown-select,
.dropdown-menu-toggle { .dropdown-menu-toggle {
width: 100%!important; width: 100% !important;
} }
.gl-field-error { .gl-field-error {
...@@ -96,12 +96,12 @@ ...@@ -96,12 +96,12 @@
} }
&:last-child { &:last-child {
& .pipeline-variable-row-remove-button { .pipeline-variable-row-remove-button {
display: none; display: none;
} }
@media (max-width: $screen-sm-max) { @media (max-width: $screen-sm-max) {
& .pipeline-variable-value-input { .pipeline-variable-value-input {
margin-right: $pipeline-variable-remove-button-width; margin-right: $pipeline-variable-remove-button-width;
} }
} }
...@@ -137,6 +137,7 @@ ...@@ -137,6 +137,7 @@
} }
.pipeline-variable-row-remove-button { .pipeline-variable-row-remove-button {
@include transition(color);
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
justify-content: center; justify-content: center;
...@@ -147,7 +148,6 @@ ...@@ -147,7 +148,6 @@
background: transparent; background: transparent;
border: 0; border: 0;
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
@include transition(color);
&:hover, &:hover,
&:focus { &:focus {
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
margin-bottom: 5px; margin-bottom: 5px;
} }
& > .form-group { > .form-group {
padding-left: 0; padding-left: 0;
} }
...@@ -83,7 +83,7 @@ ...@@ -83,7 +83,7 @@
border: 1px solid $border-color; border: 1px solid $border-color;
} }
& + .select2 a { + .select2 a {
border-top-left-radius: 0; border-top-left-radius: 0;
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
} }
...@@ -587,9 +587,9 @@ pre.light-well { ...@@ -587,9 +587,9 @@ pre.light-well {
} }
.project-row { .project-row {
@include basic-list-stats;
display: flex; display: flex;
align-items: center; align-items: center;
@include basic-list-stats;
} }
h3 { h3 {
......
...@@ -81,7 +81,7 @@ ...@@ -81,7 +81,7 @@
.todo-title { .todo-title {
display: flex; display: flex;
& > .title-item { > .title-item {
-webkit-flex: 0 0 auto; -webkit-flex: 0 0 auto;
flex: 0 0 auto; flex: 0 0 auto;
margin: 0 2px; margin: 0 2px;
......
...@@ -5,13 +5,13 @@ ...@@ -5,13 +5,13 @@
} }
.example { .example {
padding: 15px;
border: 1px dashed $ui-dev-kit-example-border;
margin-bottom: 15px;
&::before { &::before {
content: "Example"; content: "Example";
color: $ui-dev-kit-example-color; color: $ui-dev-kit-example-color;
} }
padding: 15px;
border: 1px dashed $ui-dev-kit-example-border;
margin-bottom: 15px;
} }
} }
...@@ -147,13 +147,13 @@ ...@@ -147,13 +147,13 @@
} }
ul.wiki-pages-list.content-list { ul.wiki-pages-list.content-list {
& ul { ul {
list-style: none; list-style: none;
margin-left: 0; margin-left: 0;
padding-left: 15px; padding-left: 15px;
} }
& ul li { ul li {
padding: 5px 0; padding: 5px 0;
} }
} }
......
...@@ -37,7 +37,7 @@ ul.notes-form, ...@@ -37,7 +37,7 @@ ul.notes-form,
.issuable-details .content-block-small, .issuable-details .content-block-small,
.edit-link, .edit-link,
.note-action-button { .note-action-button {
display: none!important; display: none !important;
} }
pre { pre {
......
...@@ -113,6 +113,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -113,6 +113,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:html_emails_enabled, :html_emails_enabled,
:koding_enabled, :koding_enabled,
:koding_url, :koding_url,
:password_authentication_enabled,
:plantuml_enabled, :plantuml_enabled,
:plantuml_url, :plantuml_url,
:max_artifacts_size, :max_artifacts_size,
...@@ -135,7 +136,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -135,7 +136,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:require_two_factor_authentication, :require_two_factor_authentication,
:session_expire_delay, :session_expire_delay,
:sign_in_text, :sign_in_text,
:signin_enabled,
:signup_enabled, :signup_enabled,
:sentry_dsn, :sentry_dsn,
:sentry_enabled, :sentry_enabled,
......
...@@ -170,7 +170,7 @@ class ApplicationController < ActionController::Base ...@@ -170,7 +170,7 @@ class ApplicationController < ActionController::Base
end end
def check_password_expiration def check_password_expiration
if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user? if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && current_user.allow_password_authentication?
return redirect_to new_profile_password_path return redirect_to new_profile_password_path
end end
end end
......
class PasswordsController < Devise::PasswordsController class PasswordsController < Devise::PasswordsController
include Gitlab::CurrentSettings
before_action :resource_from_email, only: [:create] before_action :resource_from_email, only: [:create]
before_action :prevent_ldap_reset, only: [:create] before_action :check_password_authentication_available, only: [:create]
before_action :throttle_reset, only: [:create] before_action :throttle_reset, only: [:create]
def edit def edit
...@@ -25,7 +27,7 @@ class PasswordsController < Devise::PasswordsController ...@@ -25,7 +27,7 @@ class PasswordsController < Devise::PasswordsController
def update def update
super do |resource| super do |resource|
if resource.valid? && resource.require_password? if resource.valid? && resource.require_password_creation?
resource.update_attribute(:password_automatically_set, false) resource.update_attribute(:password_automatically_set, false)
end end
end end
...@@ -38,11 +40,11 @@ class PasswordsController < Devise::PasswordsController ...@@ -38,11 +40,11 @@ class PasswordsController < Devise::PasswordsController
self.resource = resource_class.find_by_email(email) self.resource = resource_class.find_by_email(email)
end end
def prevent_ldap_reset def check_password_authentication_available
return unless resource && resource.ldap_user? return if current_application_settings.password_authentication_enabled? && (resource.nil? || resource.allow_password_authentication?)
redirect_to after_sending_reset_password_instructions_path_for(resource_name), redirect_to after_sending_reset_password_instructions_path_for(resource_name),
alert: "Cannot reset password for LDAP user." alert: "Password authentication is unavailable."
end end
def throttle_reset def throttle_reset
......
...@@ -77,7 +77,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController ...@@ -77,7 +77,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController
end end
def authorize_change_password! def authorize_change_password!
return render_404 if @user.ldap_user? render_404 unless @user.allow_password_authentication?
end end
def user_params def user_params
......
...@@ -107,7 +107,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap ...@@ -107,7 +107,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
@target_project = @merge_request.target_project @target_project = @merge_request.target_project
@source_project = @merge_request.source_project @source_project = @merge_request.source_project
@commits = @merge_request.compare_commits.reverse @commits = @merge_request.commits
@commit = @merge_request.diff_head_commit @commit = @merge_request.diff_head_commit
@note_counts = Note.where(commit_id: @commits.map(&:id)) @note_counts = Note.where(commit_id: @commits.map(&:id))
......
...@@ -58,7 +58,7 @@ class SessionsController < Devise::SessionsController ...@@ -58,7 +58,7 @@ class SessionsController < Devise::SessionsController
user = User.admins.last user = User.admins.last
return unless user && user.require_password? return unless user && user.require_password_creation?
Users::UpdateService.new(user).execute do |user| Users::UpdateService.new(user).execute do |user|
@token = user.generate_reset_token @token = user.generate_reset_token
......
module ApplicationSettingsHelper module ApplicationSettingsHelper
delegate :gravatar_enabled?, delegate :gravatar_enabled?,
:signup_enabled?, :signup_enabled?,
:signin_enabled?, :password_authentication_enabled?,
:akismet_enabled?, :akismet_enabled?,
:koding_enabled?, :koding_enabled?,
to: :current_application_settings to: :current_application_settings
...@@ -35,7 +35,7 @@ module ApplicationSettingsHelper ...@@ -35,7 +35,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)
Gitlab::VisibilityLevel.options.map do |name, 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' : ''
tag_name = "application_setting_visibility_level_#{level}" tag_name = "application_setting_visibility_level_#{level}"
...@@ -44,7 +44,7 @@ module ApplicationSettingsHelper ...@@ -44,7 +44,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,
id: tag_name) + visibility_level_icon(level) + name id: tag_name) + visibility_level_icon(level) + visibility_level_label(level)
end end
end end
end end
......
...@@ -50,12 +50,12 @@ module ButtonHelper ...@@ -50,12 +50,12 @@ module ButtonHelper
def http_clone_button(project, placement = 'right', append_link: true) def http_clone_button(project, placement = 'right', append_link: true)
klass = 'http-selector' klass = 'http-selector'
klass << ' has-tooltip' if current_user.try(:require_password?) || current_user.try(:require_personal_access_token?) klass << ' has-tooltip' if current_user.try(:require_password_creation?) || current_user.try(:require_personal_access_token_creation_for_git_auth?)
protocol = gitlab_config.protocol.upcase protocol = gitlab_config.protocol.upcase
tooltip_title = tooltip_title =
if current_user.try(:require_password?) if current_user.try(:require_password_creation?)
_("Set a password on your account to pull or push via %{protocol}.") % { protocol: protocol } _("Set a password on your account to pull or push via %{protocol}.") % { protocol: protocol }
else else
_("Create a personal access token on your account to pull or push via %{protocol}.") % { protocol: protocol } _("Create a personal access token on your account to pull or push via %{protocol}.") % { protocol: protocol }
......
...@@ -214,11 +214,11 @@ module ProjectsHelper ...@@ -214,11 +214,11 @@ module ProjectsHelper
def show_no_password_message? def show_no_password_message?
cookies[:hide_no_password_message].blank? && !current_user.hide_no_password && cookies[:hide_no_password_message].blank? && !current_user.hide_no_password &&
( current_user.require_password? || current_user.require_personal_access_token? ) ( current_user.require_password_creation? || current_user.require_personal_access_token_creation_for_git_auth? )
end end
def link_to_set_password def link_to_set_password
if current_user.require_password? if current_user.require_password_creation?
link_to s_('SetPasswordToCloneLink|set a password'), edit_profile_password_path link_to s_('SetPasswordToCloneLink|set a password'), edit_profile_password_path
else else
link_to s_('CreateTokenToCloneLink|create a personal access token'), profile_personal_access_tokens_path link_to s_('CreateTokenToCloneLink|create a personal access token'), profile_personal_access_tokens_path
...@@ -518,4 +518,12 @@ module ProjectsHelper ...@@ -518,4 +518,12 @@ module ProjectsHelper
current_application_settings.restricted_visibility_levels || [] current_application_settings.restricted_visibility_levels || []
end end
def find_file_path
return unless @project && !@project.empty_repo?
ref = @ref || @project.repository.root_ref
project_find_file_path(@project, ref)
end
end end
...@@ -237,6 +237,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -237,6 +237,7 @@ class ApplicationSetting < ActiveRecord::Base
koding_url: nil, koding_url: nil,
max_artifacts_size: Settings.artifacts['max_size'], max_artifacts_size: Settings.artifacts['max_size'],
max_attachment_size: Settings.gitlab['max_attachment_size'], max_attachment_size: Settings.gitlab['max_attachment_size'],
password_authentication_enabled: Settings.gitlab['password_authentication_enabled'],
performance_bar_allowed_group_id: nil, performance_bar_allowed_group_id: nil,
plantuml_enabled: false, plantuml_enabled: false,
plantuml_url: nil, plantuml_url: nil,
...@@ -251,7 +252,6 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -251,7 +252,6 @@ class ApplicationSetting < ActiveRecord::Base
shared_runners_text: nil, shared_runners_text: nil,
sidekiq_throttling_enabled: false, sidekiq_throttling_enabled: false,
sign_in_text: nil, sign_in_text: nil,
signin_enabled: Settings.gitlab['signin_enabled'],
signup_enabled: Settings.gitlab['signup_enabled'], signup_enabled: Settings.gitlab['signup_enabled'],
terminal_max_session_time: 0, terminal_max_session_time: 0,
two_factor_grace_period: 48, two_factor_grace_period: 48,
......
...@@ -32,9 +32,6 @@ class MergeRequest < ActiveRecord::Base ...@@ -32,9 +32,6 @@ class MergeRequest < ActiveRecord::Base
after_create :ensure_merge_request_diff, unless: :importing? after_create :ensure_merge_request_diff, unless: :importing?
after_update :reload_diff_if_branch_changed after_update :reload_diff_if_branch_changed
delegate :commits, :real_size, :commit_shas, :commits_count,
to: :merge_request_diff, prefix: nil
# When this attribute is true some MR validation is ignored # When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests # It allows us to close or modify broken merge requests
attr_accessor :allow_broken attr_accessor :allow_broken
...@@ -224,6 +221,36 @@ class MergeRequest < ActiveRecord::Base ...@@ -224,6 +221,36 @@ class MergeRequest < ActiveRecord::Base
"#{project.to_reference(from, full: full)}#{reference}" "#{project.to_reference(from, full: full)}#{reference}"
end end
def commits
if persisted?
merge_request_diff.commits
elsif compare_commits
compare_commits.reverse
else
[]
end
end
def commits_count
if persisted?
merge_request_diff.commits_count
elsif compare_commits
compare_commits.size
else
0
end
end
def commit_shas
if persisted?
merge_request_diff.commit_shas
elsif compare_commits
compare_commits.reverse.map(&:sha)
else
[]
end
end
def first_commit def first_commit
merge_request_diff ? merge_request_diff.first_commit : compare_commits.first merge_request_diff ? merge_request_diff.first_commit : compare_commits.first
end end
...@@ -246,9 +273,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -246,9 +273,7 @@ class MergeRequest < ActiveRecord::Base
def diff_size def diff_size
# Calling `merge_request_diff.diffs.real_size` will also perform # Calling `merge_request_diff.diffs.real_size` will also perform
# highlighting, which we don't need here. # highlighting, which we don't need here.
return real_size if merge_request_diff merge_request_diff&.real_size || diffs.real_size
diffs.real_size
end end
def diff_base_commit def diff_base_commit
......
...@@ -580,16 +580,20 @@ class User < ActiveRecord::Base ...@@ -580,16 +580,20 @@ class User < ActiveRecord::Base
keys.count == 0 && Gitlab::ProtocolAccess.allowed?('ssh') keys.count == 0 && Gitlab::ProtocolAccess.allowed?('ssh')
end end
def require_password? def require_password_creation?
password_automatically_set? && !ldap_user? && current_application_settings.signin_enabled? password_automatically_set? && allow_password_authentication?
end end
def require_personal_access_token? def require_personal_access_token_creation_for_git_auth?
return false if current_application_settings.signin_enabled? || ldap_user? return false if allow_password_authentication? || ldap_user?
PersonalAccessTokensFinder.new(user: self, impersonation: false, state: 'active').execute.none? PersonalAccessTokensFinder.new(user: self, impersonation: false, state: 'active').execute.none?
end end
def allow_password_authentication?
!ldap_user? && current_application_settings.password_authentication_enabled?
end
def can_change_username? def can_change_username?
gitlab_config.username_changing_enabled gitlab_config.username_changing_enabled
end end
...@@ -699,7 +703,7 @@ class User < ActiveRecord::Base ...@@ -699,7 +703,7 @@ class User < ActiveRecord::Base
end end
def sanitize_attrs def sanitize_attrs
%w[name username skype linkedin twitter].each do |attr| %w[username skype linkedin twitter].each do |attr|
value = public_send(attr) value = public_send(attr)
public_send("#{attr}=", Sanitize.clean(value)) if value.present? public_send("#{attr}=", Sanitize.clean(value)) if value.present?
end end
......
...@@ -76,6 +76,12 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated ...@@ -76,6 +76,12 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end end
end end
def target_branch_tree_path
if target_branch_exists?
project_tree_path(project, target_branch)
end
end
def target_branch_commits_path def target_branch_commits_path
if target_branch_exists? if target_branch_exists?
project_commits_path(project, target_branch) project_commits_path(project, target_branch)
...@@ -94,7 +100,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated ...@@ -94,7 +100,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
if source_branch_exists? if source_branch_exists?
namespace = link_to(namespace, project_path(source_project)) namespace = link_to(namespace, project_path(source_project))
branch = link_to(branch, project_commits_path(source_project, source_branch)) branch = link_to(branch, project_tree_path(source_project, source_branch))
end end
if for_fork? if for_fork?
......
...@@ -97,6 +97,10 @@ class MergeRequestEntity < IssuableEntity ...@@ -97,6 +97,10 @@ class MergeRequestEntity < IssuableEntity
presenter(merge_request).target_branch_commits_path presenter(merge_request).target_branch_commits_path
end end
expose :target_branch_tree_path do |merge_request|
presenter(merge_request).target_branch_tree_path
end
expose :new_blob_path do |merge_request| expose :new_blob_path do |merge_request|
if can?(current_user, :push_code, merge_request.project) if can?(current_user, :push_code, merge_request.project)
project_new_blob_path(merge_request.project, merge_request.source_branch) project_new_blob_path(merge_request.project, merge_request.source_branch)
......
...@@ -7,9 +7,8 @@ module MergeRequests ...@@ -7,9 +7,8 @@ module MergeRequests
source_project = @project source_project = @project
@project = Project.find(params[:target_project_id]) if params[:target_project_id] @project = Project.find(params[:target_project_id]) if params[:target_project_id]
params[:target_project_id] ||= source_project.id
merge_request = MergeRequest.new merge_request = MergeRequest.new
merge_request.target_project = @project
merge_request.source_project = source_project merge_request.source_project = source_project
merge_request.source_branch = params[:source_branch] merge_request.source_branch = params[:source_branch]
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
......
...@@ -145,9 +145,9 @@ ...@@ -145,9 +145,9 @@
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
.checkbox .checkbox
= f.label :signin_enabled do = f.label :password_authentication_enabled do
= f.check_box :signin_enabled = f.check_box :password_authentication_enabled
Sign-in enabled Password authentication enabled
- if omniauth_enabled? && button_based_providers.any? - if omniauth_enabled? && button_based_providers.any?
.form-group .form-group
= f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth sign-in sources', class: 'control-label col-sm-2' = f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth sign-in sources', class: 'control-label col-sm-2'
......
...@@ -6,15 +6,15 @@ ...@@ -6,15 +6,15 @@
- else - else
= render 'devise/shared/tabs_normal' = render 'devise/shared/tabs_normal'
.tab-content .tab-content
- if signin_enabled? || ldap_enabled? || crowd_enabled? - if password_authentication_enabled? || ldap_enabled? || crowd_enabled?
= render 'devise/shared/signin_box' = render 'devise/shared/signin_box'
-# Signup only makes sense if you can also sign-in -# Signup only makes sense if you can also sign-in
- if signin_enabled? && signup_enabled? - if password_authentication_enabled? && signup_enabled?
= render 'devise/shared/signup_box' = render 'devise/shared/signup_box'
-# Show a message if none of the mechanisms above are enabled -# Show a message if none of the mechanisms above are enabled
- if !signin_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?) - if !password_authentication_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?)
%div %div
No authentication methods configured. No authentication methods configured.
......
...@@ -7,12 +7,12 @@ ...@@ -7,12 +7,12 @@
.login-box.tab-pane{ id: "#{server['provider_name']}", role: 'tabpanel', class: active_when(i.zero? && !crowd_enabled?) } .login-box.tab-pane{ id: "#{server['provider_name']}", role: 'tabpanel', class: active_when(i.zero? && !crowd_enabled?) }
.login-body .login-body
= render 'devise/sessions/new_ldap', server: server = render 'devise/sessions/new_ldap', server: server
- if signin_enabled? - if password_authentication_enabled?
.login-box.tab-pane{ id: 'ldap-standard', role: 'tabpanel' } .login-box.tab-pane{ id: 'ldap-standard', role: 'tabpanel' }
.login-body .login-body
= render 'devise/sessions/new_base' = render 'devise/sessions/new_base'
- elsif signin_enabled? - elsif password_authentication_enabled?
.login-box.tab-pane.active{ id: 'login-pane', role: 'tabpanel' } .login-box.tab-pane.active{ id: 'login-pane', role: 'tabpanel' }
.login-body .login-body
= render 'devise/sessions/new_base' = render 'devise/sessions/new_base'
...@@ -5,9 +5,9 @@ ...@@ -5,9 +5,9 @@
- @ldap_servers.each_with_index do |server, i| - @ldap_servers.each_with_index do |server, i|
%li{ class: active_when(i.zero? && !crowd_enabled?) } %li{ class: active_when(i.zero? && !crowd_enabled?) }
= link_to server['label'], "##{server['provider_name']}", 'data-toggle' => 'tab' = link_to server['label'], "##{server['provider_name']}", 'data-toggle' => 'tab'
- if signin_enabled? - if password_authentication_enabled?
%li %li
= link_to 'Standard', '#ldap-standard', 'data-toggle' => 'tab' = link_to 'Standard', '#ldap-standard', 'data-toggle' => 'tab'
- if signin_enabled? && signup_enabled? - if password_authentication_enabled? && signup_enabled?
%li %li
= link_to 'Register', '#register-pane', 'data-toggle' => 'tab' = link_to 'Register', '#register-pane', 'data-toggle' => 'tab'
%ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist' } %ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist' }
%li.active{ role: 'presentation' } %li.active{ role: 'presentation' }
%a{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab' } Sign in %a{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab' } Sign in
- if signin_enabled? && signup_enabled? - if password_authentication_enabled? && signup_enabled?
%li{ role: 'presentation' } %li{ role: 'presentation' }
%a{ href: '#register-pane', data: { toggle: 'tab' }, role: 'tab' } Register %a{ href: '#register-pane', data: { toggle: 'tab' }, role: 'tab' } Register
- BroadcastMessage.current.each do |message| - BroadcastMessage.current&.each do |message|
= broadcast_message(message) = broadcast_message(message)
!!! 5 !!! 5
%html{ lang: I18n.locale, class: "#{page_class}" } %html{ lang: I18n.locale, class: "#{page_class}" }
= render "layouts/head" = render "layouts/head"
%body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } } %body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}", find_file: find_file_path } }
= render "layouts/init_auto_complete" if @gfm_form = render "layouts/init_auto_complete" if @gfm_form
- if show_new_nav? - if show_new_nav?
= render "layouts/header/new" = render "layouts/header/new"
......
...@@ -75,7 +75,7 @@ ...@@ -75,7 +75,7 @@
%li %li
= link_to "Settings", profile_path = link_to "Settings", profile_path
%li %li
= link_to "Turn on new nav", profile_preferences_path(anchor: "new-navigation") = link_to "Turn on new navigation", profile_preferences_path(anchor: "new-navigation")
%li.divider %li.divider
%li %li
= link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link" = link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link"
...@@ -91,8 +91,3 @@ ...@@ -91,8 +91,3 @@
= yield :header_content = yield :header_content
= render 'shared/outdated_browser' = render 'shared/outdated_browser'
- if @project && !@project.empty_repo?
- if ref = @ref || @project.repository.root_ref
:javascript
var findFileURL = "#{project_find_file_path(@project, ref)}";
...@@ -69,7 +69,7 @@ ...@@ -69,7 +69,7 @@
%li %li
= link_to "Settings", profile_path = link_to "Settings", profile_path
%li %li
= link_to "Turn off new nav", profile_preferences_path(anchor: "new-navigation") = link_to "Turn off new navigation", profile_preferences_path(anchor: "new-navigation")
%li.divider %li.divider
%li %li
= link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link" = link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link"
...@@ -84,8 +84,3 @@ ...@@ -84,8 +84,3 @@
= icon('times', class: 'js-navbar-toggle-left', style: 'display: none;') = icon('times', class: 'js-navbar-toggle-left', style: 'display: none;')
= render 'shared/outdated_browser' = render 'shared/outdated_browser'
- if @project && !@project.empty_repo?
- if ref = @ref || @project.repository.root_ref
:javascript
var findFileURL = "#{project_find_file_path(@project, ref)}";
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
= link_to profile_emails_path, title: 'Emails' do = link_to profile_emails_path, title: 'Emails' do
%span %span
Emails Emails
- unless current_user.ldap_user? - if current_user.allow_password_authentication?
= nav_link(controller: :passwords) do = nav_link(controller: :passwords) do
= link_to edit_profile_password_path, title: 'Password' do = link_to edit_profile_password_path, title: 'Password' do
%span %span
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
%ul.dropdown-menu.dropdown-menu-align-right %ul.dropdown-menu.dropdown-menu-align-right
%li.visible-xs-block.visible-sm-block %li.visible-xs-block.visible-sm-block
= link_to project_tree_path(@project, @commit) do = link_to project_tree_path(@project, @commit) do
_('Browse Files') #{ _('Browse Files') }
- unless @commit.has_been_reverted?(current_user) - unless @commit.has_been_reverted?(current_user)
%li.clearfix %li.clearfix
= revert_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false) = revert_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
#js-details-block-vue #js-details-block-vue
- if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?) - if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?)
.block{ class: ("block-first" if !@build.coverage) } .block
.title .title
Job artifacts Job artifacts
- if @build.artifacts_expired? - if @build.artifacts_expired?
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
Browse Browse
- if @build.trigger_request - if @build.trigger_request
.build-widget .build-widget.block
%h4.title %h4.title
Trigger Trigger
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
.js-build-variable.trigger-build-variable= key .js-build-variable.trigger-build-variable= key
.js-build-value.trigger-build-value= value .js-build-value.trigger-build-value= value
.block %div{ class: (@build.pipeline.stages_count > 1 ? "block" : "block-last") }
%p %p
Commit Commit
= link_to @build.pipeline.short_sha, project_commit_path(@project, @build.pipeline.sha), class: 'commit-sha link-commit' = link_to @build.pipeline.short_sha, project_commit_path(@project, @build.pipeline.sha), class: 'commit-sha link-commit'
...@@ -69,7 +69,7 @@ ...@@ -69,7 +69,7 @@
- if @build.pipeline.stages_count > 1 - if @build.pipeline.stages_count > 1
.dropdown.build-dropdown .dropdown.build-dropdown
.title %div
%span{ class: "ci-status-icon-#{@build.pipeline.status}" } %span{ class: "ci-status-icon-#{@build.pipeline.status}" }
= ci_icon_for_status(@build.pipeline.status) = ci_icon_for_status(@build.pipeline.status)
Pipeline Pipeline
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
%p %p
Metrics are automatically configured and monitored Metrics are automatically configured and monitored
based on a library of metrics from popular exporters. based on a library of metrics from popular exporters.
= link_to 'More information', '#' = link_to 'More information', help_page_path('user/project/integrations/prometheus')
.col-lg-9 .col-lg-9
.panel.panel-default.js-panel-monitored-metrics{ data: { "active-metrics" => "#{project_prometheus_active_metrics_path(@project, :json)}" } } .panel.panel-default.js-panel-monitored-metrics{ data: { "active-metrics" => "#{project_prometheus_active_metrics_path(@project, :json)}" } }
...@@ -41,5 +41,5 @@ ...@@ -41,5 +41,5 @@
%code %code
$CI_ENVIRONMENT_SLUG $CI_ENVIRONMENT_SLUG
to exporter&rsquo;s queries. to exporter&rsquo;s queries.
= link_to 'More information', '#' = link_to 'More information', help_page_path('user/project/integrations/prometheus', anchor: 'metrics-and-labels')
%ul.list-unstyled.metrics-list.js-missing-var-metrics-list %ul.list-unstyled.metrics-list.js-missing-var-metrics-list
---
title: "Insert user name directly without encoding"
merge_request: 10085
author: Nathan Neulinger <nneul@neulinger.org>
---
title: Supplement Portuguese Brazil translation of Project Page & Repository Page
merge_request: 12156
author: Huang Tao
---
title: Return `is_admin` attribute in the GET /user endpoint for admins
merge_request: 12811
author:
---
title: Updates vue resource and code according to breaking changes
merge_request:
author:
---
title: Bump scss-lint to 0.54.0
merge_request: 12733
author: Takuya Noguchi
---
title: Remove public/ci/favicon.ico
merge_request: 12803
author: Takuya Noguchi
---
title: Fix vertical space in job details sidebar
merge_request:
author:
---
title: Increase width of dropdown menus automatically
merge_request: 12809
author: Thomas Wucher
---
title: Enable BangFormat in scss-lint [ci skip]
merge_request: 12815
author: Takuya Noguchi
---
title: Enable DeclarationOrder in scss-lint
merge_request: 12805
author: Takuya Noguchi
---
title: Enable UnnecessaryParentReference in scss-lint
merge_request: 12738
author: Takuya Noguchi
---
title: Fixes needed when GitLab sign-in is not enabled
merge_request: 12491
author: Robin Bobbitt
---
title: MR branch link now links to tree instead of commits
merge_request:
author:
---
title: Replace 'browse_files.feature' spinach test with an rspec analog
merge_request: 12251
author: @blackst0ne
...@@ -223,7 +223,7 @@ rescue ArgumentError # no user configured ...@@ -223,7 +223,7 @@ rescue ArgumentError # no user configured
end end
Settings.gitlab['time_zone'] ||= nil Settings.gitlab['time_zone'] ||= nil
Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled'].nil? Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled'].nil?
Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil? Settings.gitlab['password_authentication_enabled'] ||= true if Settings.gitlab['password_authentication_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.__send__(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], []) Settings.gitlab['restricted_visibility_levels'] = Settings.__send__(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil? Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing))(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil? Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing))(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil?
......
class RenameApplicationSettingsSigninEnabledToPasswordAuthenticationEnabled < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
rename_column_concurrently :application_settings, :signin_enabled, :password_authentication_enabled
end
def down
cleanup_concurrent_column_rename :application_settings, :password_authentication_enabled, :signin_enabled
end
end
class CleanupApplicationSettingsSigninEnabledRename < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
cleanup_concurrent_column_rename :application_settings, :signin_enabled, :password_authentication_enabled
end
def down
rename_column_concurrently :application_settings, :password_authentication_enabled, :signin_enabled
end
end
...@@ -41,7 +41,6 @@ ActiveRecord::Schema.define(version: 20170707184244) do ...@@ -41,7 +41,6 @@ ActiveRecord::Schema.define(version: 20170707184244) do
create_table "application_settings", force: :cascade do |t| create_table "application_settings", force: :cascade do |t|
t.integer "default_projects_limit" t.integer "default_projects_limit"
t.boolean "signup_enabled" t.boolean "signup_enabled"
t.boolean "signin_enabled"
t.boolean "gravatar_enabled" t.boolean "gravatar_enabled"
t.text "sign_in_text" t.text "sign_in_text"
t.datetime "created_at" t.datetime "created_at"
...@@ -127,6 +126,7 @@ ActiveRecord::Schema.define(version: 20170707184244) do ...@@ -127,6 +126,7 @@ ActiveRecord::Schema.define(version: 20170707184244) do
t.boolean "help_page_hide_commercial_content", default: false t.boolean "help_page_hide_commercial_content", default: false
t.string "help_page_support_url" t.string "help_page_support_url"
t.integer "performance_bar_allowed_group_id" t.integer "performance_bar_allowed_group_id"
t.boolean "password_authentication_enabled"
end end
create_table "audit_events", force: :cascade do |t| create_table "audit_events", force: :cascade do |t|
......
...@@ -25,7 +25,7 @@ Example response: ...@@ -25,7 +25,7 @@ Example response:
"id" : 1, "id" : 1,
"default_branch_protection" : 2, "default_branch_protection" : 2,
"restricted_visibility_levels" : [], "restricted_visibility_levels" : [],
"signin_enabled" : true, "password_authentication_enabled" : true,
"after_sign_out_path" : null, "after_sign_out_path" : null,
"max_attachment_size" : 10, "max_attachment_size" : 10,
"user_oauth_applications" : true, "user_oauth_applications" : true,
...@@ -63,7 +63,7 @@ PUT /application/settings ...@@ -63,7 +63,7 @@ PUT /application/settings
| --------- | ---- | :------: | ----------- | | --------- | ---- | :------: | ----------- |
| `default_projects_limit` | integer | no | Project limit per user. Default is `100000` | | `default_projects_limit` | integer | no | Project limit per user. Default is `100000` |
| `signup_enabled` | boolean | no | Enable registration. Default is `true`. | | `signup_enabled` | boolean | no | Enable registration. Default is `true`. |
| `signin_enabled` | boolean | no | Enable login via a GitLab account. Default is `true`. | | `password_authentication_enabled` | boolean | no | Enable authentication via a GitLab account password. Default is `true`. |
| `gravatar_enabled` | boolean | no | Enable Gravatar | | `gravatar_enabled` | boolean | no | Enable Gravatar |
| `sign_in_text` | string | no | Text on login page | | `sign_in_text` | string | no | Text on login page |
| `home_page_url` | string | no | Redirect to this URL when not logged in | | `home_page_url` | string | no | Redirect to this URL when not logged in |
...@@ -102,7 +102,7 @@ Example response: ...@@ -102,7 +102,7 @@ Example response:
"id": 1, "id": 1,
"default_projects_limit": 100000, "default_projects_limit": 100000,
"signup_enabled": true, "signup_enabled": true,
"signin_enabled": true, "password_authentication_enabled": true,
"gravatar_enabled": true, "gravatar_enabled": true,
"sign_in_text": "", "sign_in_text": "",
"created_at": "2015-06-12T15:51:55.432Z", "created_at": "2015-06-12T15:51:55.432Z",
......
...@@ -364,7 +364,7 @@ GET /user ...@@ -364,7 +364,7 @@ GET /user
Parameters: Parameters:
- `sudo` (required) - the ID of a user - `sudo` (optional) - the ID of a user to make the call in their place
``` ```
GET /user GET /user
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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