Commit 9cee280d authored by Luke "Jared" Bennett's avatar Luke "Jared" Bennett

Merge branch 'update-droplab-to-webpack-version' into new-resolvable-discussion

parents 2a18425e 66d03aae
...@@ -2,6 +2,14 @@ ...@@ -2,6 +2,14 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 9.0.4 (2017-04-05)
- Don’t show source project name when user does not have access.
- Remove the class attribute from the whitelist for HTML generated from Markdown.
- Fix path disclosure in project import/export.
- Fix for open redirect vulnerability using continue[to] in URL when requesting project import status.
- Fix for open redirect vulnerabilities in todos, issues, and MR controllers.
## 9.0.3 (2017-04-05) ## 9.0.3 (2017-04-05)
- Fix name colision when importing GitHub pull requests from forked repositories. !9719 - Fix name colision when importing GitHub pull requests from forked repositories. !9719
...@@ -320,6 +328,14 @@ entry. ...@@ -320,6 +328,14 @@ entry.
- Change development tanuki favicon colors to match logo color order. - Change development tanuki favicon colors to match logo color order.
- API issues - support filtering by iids. - API issues - support filtering by iids.
## 8.17.5 (2017-04-05)
- Don’t show source project name when user does not have access.
- Remove the class attribute from the whitelist for HTML generated from Markdown.
- Fix path disclosure in project import/export.
- Fix for open redirect vulnerability using continue[to] in URL when requesting project import status.
- Fix for open redirect vulnerabilities in todos, issues, and MR controllers.
## 8.17.4 (2017-03-19) ## 8.17.4 (2017-03-19)
- Only show public emails in atom feeds. - Only show public emails in atom feeds.
...@@ -533,6 +549,14 @@ entry. ...@@ -533,6 +549,14 @@ entry.
- Remove deprecated GitlabCiService. - Remove deprecated GitlabCiService.
- Requeue pending deletion projects. - Requeue pending deletion projects.
## 8.16.9 (2017-04-05)
- Don’t show source project name when user does not have access.
- Remove the class attribute from the whitelist for HTML generated from Markdown.
- Fix path disclosure in project import/export.
- Fix for open redirect vulnerability using continue[to] in URL when requesting project import status.
- Fix for open redirect vulnerabilities in todos, issues, and MR controllers.
## 8.16.8 (2017-03-19) ## 8.16.8 (2017-03-19)
- Only show public emails in atom feeds. - Only show public emails in atom feeds.
......
import * as THREE from 'three/build/three.module';
import STLLoaderClass from 'three-stl-loader';
import OrbitControlsClass from 'three-orbit-controls';
import MeshObject from './mesh_object';
const STLLoader = STLLoaderClass(THREE);
const OrbitControls = OrbitControlsClass(THREE);
export default class Renderer {
constructor(container) {
this.renderWrapper = this.render.bind(this);
this.objects = [];
this.container = container;
this.width = this.container.offsetWidth;
this.height = 500;
this.loader = new STLLoader();
this.fov = 45;
this.camera = new THREE.PerspectiveCamera(
this.fov,
this.width / this.height,
1,
1000,
);
this.scene = new THREE.Scene();
this.scene.add(this.camera);
// Setup the viewer
this.setupRenderer();
this.setupGrid();
this.setupLight();
// Setup OrbitControls
this.controls = new OrbitControls(
this.camera,
this.renderer.domElement,
);
this.controls.minDistance = 5;
this.controls.maxDistance = 30;
this.controls.enableKeys = false;
this.loadFile();
}
setupRenderer() {
this.renderer = new THREE.WebGLRenderer({
antialias: true,
});
this.renderer.setClearColor(0xFFFFFF);
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(
this.width,
this.height,
);
}
setupLight() {
// Point light illuminates the object
const pointLight = new THREE.PointLight(
0xFFFFFF,
2,
0,
);
pointLight.castShadow = true;
this.camera.add(pointLight);
// Ambient light illuminates the scene
const ambientLight = new THREE.AmbientLight(
0xFFFFFF,
1,
);
this.scene.add(ambientLight);
}
setupGrid() {
this.grid = new THREE.GridHelper(
20,
20,
0x000000,
0x000000,
);
this.scene.add(this.grid);
}
loadFile() {
this.loader.load(this.container.dataset.endpoint, (geo) => {
const obj = new MeshObject(geo);
this.objects.push(obj);
this.scene.add(obj);
this.start();
this.setDefaultCameraPosition();
});
}
start() {
// Empty the container first
this.container.innerHTML = '';
// Add to DOM
this.container.appendChild(this.renderer.domElement);
// Make controls visible
this.container.parentNode.classList.remove('is-stl-loading');
this.render();
}
render() {
this.renderer.render(
this.scene,
this.camera,
);
requestAnimationFrame(this.renderWrapper);
}
changeObjectMaterials(type) {
this.objects.forEach((obj) => {
obj.changeMaterial(type);
});
}
setDefaultCameraPosition() {
const obj = this.objects[0];
const radius = (obj.geometry.boundingSphere.radius / 1.5);
const dist = radius / (Math.sin((this.fov * (Math.PI / 180)) / 2));
this.camera.position.set(
0,
dist + 1,
dist,
);
this.camera.lookAt(this.grid);
this.controls.update();
}
}
import {
Matrix4,
MeshLambertMaterial,
Mesh,
} from 'three/build/three.module';
const defaultColor = 0xE24329;
const materials = {
default: new MeshLambertMaterial({
color: defaultColor,
}),
wireframe: new MeshLambertMaterial({
color: defaultColor,
wireframe: true,
}),
};
export default class MeshObject extends Mesh {
constructor(geo) {
super(
geo,
materials.default,
);
this.geometry.computeBoundingSphere();
this.rotation.set(-Math.PI / 2, 0, 0);
if (this.geometry.boundingSphere.radius > 4) {
const scale = 4 / this.geometry.boundingSphere.radius;
this.geometry.applyMatrix(
new Matrix4().makeScale(
scale,
scale,
scale,
),
);
this.geometry.computeBoundingSphere();
this.position.x = -this.geometry.boundingSphere.center.x;
this.position.z = this.geometry.boundingSphere.center.y;
}
}
changeMaterial(type) {
this.material = materials[type];
}
}
...@@ -10,7 +10,7 @@ Vue.use(PDFLab, { ...@@ -10,7 +10,7 @@ Vue.use(PDFLab, {
export default () => { export default () => {
const el = document.getElementById('js-pdf-viewer'); const el = document.getElementById('js-pdf-viewer');
new Vue({ return new Vue({
el, el,
data() { data() {
return { return {
......
import Renderer from './3d_viewer';
document.addEventListener('DOMContentLoaded', () => {
const viewer = new Renderer(document.getElementById('js-stl-viewer'));
[].slice.call(document.querySelectorAll('.js-material-changer')).forEach((el) => {
el.addEventListener('click', (e) => {
const target = e.target;
e.preventDefault();
document.querySelector('.js-material-changer.active').classList.remove('active');
target.classList.add('active');
target.blur();
viewer.changeObjectMaterials(target.dataset.type);
});
});
});
...@@ -88,6 +88,7 @@ window.Build = (function() { ...@@ -88,6 +88,7 @@ window.Build = (function() {
dataType: 'json', dataType: 'json',
success: function(buildData) { success: function(buildData) {
$('.js-build-output').html(buildData.trace_html); $('.js-build-output').html(buildData.trace_html);
gl.utils.setCiStatusFavicon(`${this.pageUrl}/status.json`);
if (window.location.hash === DOWN_BUILD_TRACE) { if (window.location.hash === DOWN_BUILD_TRACE) {
$("html,body").scrollTop(this.$buildTrace.height()); $("html,body").scrollTop(this.$buildTrace.height());
} }
......
...@@ -226,9 +226,11 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -226,9 +226,11 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'projects:pipelines:builds': case 'projects:pipelines:builds':
case 'projects:pipelines:show': case 'projects:pipelines:show':
const { controllerAction } = document.querySelector('.js-pipeline-container').dataset; const { controllerAction } = document.querySelector('.js-pipeline-container').dataset;
const pipelineStatusUrl = `${document.querySelector('.js-pipeline-tab-link a').getAttribute('href')}/status.json`;
new gl.Pipelines({ new gl.Pipelines({
initTabs: true, initTabs: true,
pipelineStatusUrl,
tabsOptions: { tabsOptions: {
action: controllerAction, action: controllerAction,
defaultAction: 'pipelines', defaultAction: 'pipelines',
......
/* eslint-disable */ /* eslint-disable */
function droplabAjaxException(message) {
this.message = message;
}
const Ajax = { const Ajax = {
_loadUrlData: function _loadUrlData(url) { _loadUrlData: function _loadUrlData(url) {
var self = this; var self = this;
...@@ -58,9 +54,7 @@ const Ajax = { ...@@ -58,9 +54,7 @@ const Ajax = {
this._loadUrlData(config.endpoint) this._loadUrlData(config.endpoint)
.then(function(d) { .then(function(d) {
self._loadData(d, config, self); self._loadData(d, config, self);
}, config.onError).catch(function(e) { }, config.onError).catch(config.onError);
throw new droplabAjaxException(e.message || e);
});
} }
}, },
destroy: function() { destroy: function() {
......
...@@ -68,7 +68,7 @@ const AjaxFilter = { ...@@ -68,7 +68,7 @@ const AjaxFilter = {
this._loadUrlData(url) this._loadUrlData(url)
.then(function(data) { .then(function(data) {
self._loadData(data, config, self); self._loadData(data, config, self);
}, config.onError); }, config.onError).catch(config.onError);
} }
}, },
......
...@@ -45,11 +45,20 @@ export default { ...@@ -45,11 +45,20 @@ export default {
new Flash('An error occured while making the request.'); new Flash('An error occured while making the request.');
}); });
}, },
isActionDisabled(action) {
if (action.playable === undefined) {
return false;
}
return !action.playable;
},
}, },
template: ` template: `
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<button <button
type="button"
class="dropdown btn btn-default dropdown-new js-dropdown-play-icon-container has-tooltip" class="dropdown btn btn-default dropdown-new js-dropdown-play-icon-container has-tooltip"
data-container="body" data-container="body"
data-toggle="dropdown" data-toggle="dropdown"
...@@ -58,15 +67,24 @@ export default { ...@@ -58,15 +67,24 @@ export default {
:disabled="isLoading"> :disabled="isLoading">
<span> <span>
<span v-html="playIconSvg"></span> <span v-html="playIconSvg"></span>
<i class="fa fa-caret-down" aria-hidden="true"></i> <i
<i v-if="isLoading" class="fa fa-spinner fa-spin" aria-hidden="true"></i> class="fa fa-caret-down"
aria-hidden="true"/>
<i
v-if="isLoading"
class="fa fa-spinner fa-spin"
aria-hidden="true"/>
</span> </span>
</button>
<ul class="dropdown-menu dropdown-menu-align-right"> <ul class="dropdown-menu dropdown-menu-align-right">
<li v-for="action in actions"> <li v-for="action in actions">
<button <button
type="button"
class="js-manual-action-link no-btn btn"
@click="onClickAction(action.play_path)" @click="onClickAction(action.play_path)"
class="js-manual-action-link no-btn"> :class="{ 'disabled': isActionDisabled(action) }"
:disabled="isActionDisabled(action)">
${playIconSvg} ${playIconSvg}
<span> <span>
{{action.name}} {{action.name}}
...@@ -74,7 +92,6 @@ export default { ...@@ -74,7 +92,6 @@ export default {
</button> </button>
</li> </li>
</ul> </ul>
</button>
</div> </div>
`, `,
}; };
...@@ -142,6 +142,7 @@ export default { ...@@ -142,6 +142,7 @@ export default {
const parsedAction = { const parsedAction = {
name: gl.text.humanize(action.name), name: gl.text.humanize(action.name),
play_path: action.play_path, play_path: action.play_path,
playable: action.playable,
}; };
return parsedAction; return parsedAction;
}); });
......
/* global Flash */
import Ajax from '~/droplab/plugins/ajax'; import Ajax from '~/droplab/plugins/ajax';
import Filter from '~/droplab/plugins/filter'; import Filter from '~/droplab/plugins/filter';
...@@ -13,6 +15,11 @@ require('./filtered_search_dropdown'); ...@@ -13,6 +15,11 @@ require('./filtered_search_dropdown');
endpoint, endpoint,
method: 'setData', method: 'setData',
loadingTemplate: this.loadingTemplate, loadingTemplate: this.loadingTemplate,
onError() {
/* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.');
/* eslint-enable no-new */
},
}, },
Filter: { Filter: {
filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol, input), filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol, input),
......
/* global Flash */
import AjaxFilter from '~/droplab/plugins/ajax_filter'; import AjaxFilter from '~/droplab/plugins/ajax_filter';
require('./filtered_search_dropdown'); require('./filtered_search_dropdown');
...@@ -18,6 +20,11 @@ require('./filtered_search_dropdown'); ...@@ -18,6 +20,11 @@ require('./filtered_search_dropdown');
}, },
searchValueFunction: this.getSearchInput.bind(this), searchValueFunction: this.getSearchInput.bind(this),
loadingTemplate: this.loadingTemplate, loadingTemplate: this.loadingTemplate,
onError() {
/* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.');
/* eslint-enable no-new */
},
}, },
}; };
} }
......
import Vue from 'vue';
import IssueTitle from './issue_title';
import '../vue_shared/vue_resource_interceptor';
const vueOptions = () => ({
el: '.issue-title-entrypoint',
components: {
IssueTitle,
},
data() {
const issueTitleData = document.querySelector('.issue-title-data').dataset;
return {
initialTitle: issueTitleData.initialTitle,
endpoint: issueTitleData.endpoint,
};
},
template: `
<IssueTitle
:initialTitle="initialTitle"
:endpoint="endpoint"
/>
`,
});
(() => new Vue(vueOptions()))();
import Visibility from 'visibilityjs';
import Poll from './../lib/utils/poll';
import Service from './services/index';
export default {
props: {
initialTitle: { required: true, type: String },
endpoint: { required: true, type: String },
},
data() {
const resource = new Service(this.$http, this.endpoint);
const poll = new Poll({
resource,
method: 'getTitle',
successCallback: (res) => {
this.renderResponse(res);
},
errorCallback: (err) => {
if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line no-console
console.error('ISSUE SHOW TITLE REALTIME ERROR', err);
} else {
throw new Error(err);
}
},
});
return {
poll,
timeoutId: null,
title: this.initialTitle,
};
},
methods: {
fetch() {
this.poll.makeRequest();
Visibility.change(() => {
if (!Visibility.hidden()) {
this.poll.restart();
} else {
this.poll.stop();
}
});
},
renderResponse(res) {
const body = JSON.parse(res.body);
this.triggerAnimation(body);
},
triggerAnimation(body) {
const { title } = body;
/**
* since opacity is changed, even if there is no diff for Vue to update
* we must check the title even on a 304 to ensure no visual change
*/
if (this.title === title) return;
this.$el.style.opacity = 0;
this.timeoutId = setTimeout(() => {
this.title = title;
this.$el.style.transition = 'opacity 0.2s ease';
this.$el.style.opacity = 1;
clearTimeout(this.timeoutId);
}, 100);
},
},
created() {
this.fetch();
},
template: `
<h2 class='title' v-html='title'></h2>
`,
};
export default class Service {
constructor(resource, endpoint) {
this.resource = resource;
this.endpoint = endpoint;
}
getTitle() {
return this.resource.get(this.endpoint);
}
}
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
(function() { (function() {
(function(w) { (function(w) {
var base; var base;
const faviconEl = document.getElementById('favicon');
const originalFavicon = faviconEl ? faviconEl.getAttribute('href') : null;
w.gl || (w.gl = {}); w.gl || (w.gl = {});
(base = w.gl).utils || (base.utils = {}); (base = w.gl).utils || (base.utils = {});
w.gl.utils.isInGroupsPage = function() { w.gl.utils.isInGroupsPage = function() {
...@@ -361,5 +363,34 @@ ...@@ -361,5 +363,34 @@
fn(next, stop); fn(next, stop);
}); });
}; };
w.gl.utils.setFavicon = (iconName) => {
if (faviconEl && iconName) {
faviconEl.setAttribute('href', `/assets/${iconName}.ico`);
}
};
w.gl.utils.resetFavicon = () => {
if (faviconEl) {
faviconEl.setAttribute('href', originalFavicon);
}
};
w.gl.utils.setCiStatusFavicon = (pageUrl) => {
$.ajax({
url: pageUrl,
dataType: 'json',
success: function(data) {
if (data && data.icon) {
gl.utils.setFavicon(`ci_favicons/${data.icon}`);
} else {
gl.utils.resetFavicon();
}
},
error: function() {
gl.utils.resetFavicon();
}
});
};
})(window); })(window);
}).call(window); }).call(window);
...@@ -38,11 +38,13 @@ import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; ...@@ -38,11 +38,13 @@ import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
function MergeRequestWidget(opts) { function MergeRequestWidget(opts) {
// Initialize MergeRequestWidget behavior // Initialize MergeRequestWidget behavior
// //
// check_enable - Boolean, whether to check automerge status // check_enable - Boolean, whether to check automerge status
// merge_check_url - String, URL to use to check automerge status // merge_check_url - String, URL to use to check automerge status
// ci_status_url - String, URL to use to check CI status // ci_status_url - String, URL to use to check CI status
// pipeline_status_url - String, URL to use to get CI status for Favicon
// //
this.opts = opts; this.opts = opts;
this.opts.pipeline_status_url = `${this.opts.pipeline_status_url}.json`;
this.$widgetBody = $('.mr-widget-body'); this.$widgetBody = $('.mr-widget-body');
$('#modal_merge_info').modal({ $('#modal_merge_info').modal({
show: false show: false
...@@ -159,6 +161,7 @@ import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; ...@@ -159,6 +161,7 @@ import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
_this.status = data.status; _this.status = data.status;
_this.hasCi = data.has_ci; _this.hasCi = data.has_ci;
_this.updateMergeButton(_this.status, _this.hasCi); _this.updateMergeButton(_this.status, _this.hasCi);
gl.utils.setCiStatusFavicon(_this.opts.pipeline_status_url);
if (data.environments && data.environments.length) _this.renderEnvironments(data.environments); if (data.environments && data.environments.length) _this.renderEnvironments(data.environments);
if (data.status !== _this.opts.ci_status || if (data.status !== _this.opts.ci_status ||
data.sha !== _this.opts.ci_sha || data.sha !== _this.opts.ci_sha ||
......
...@@ -9,6 +9,10 @@ require('./lib/utils/bootstrap_linked_tabs'); ...@@ -9,6 +9,10 @@ require('./lib/utils/bootstrap_linked_tabs');
new global.LinkedTabs(options.tabsOptions); new global.LinkedTabs(options.tabsOptions);
} }
if (options.pipelineStatusUrl) {
gl.utils.setCiStatusFavicon(options.pipelineStatusUrl);
}
this.addMarginToBuildColumns(); this.addMarginToBuildColumns();
} }
......
...@@ -38,6 +38,14 @@ export default { ...@@ -38,6 +38,14 @@ export default {
new Flash('An error occured while making the request.'); new Flash('An error occured while making the request.');
}); });
}, },
isActionDisabled(action) {
if (action.playable === undefined) {
return false;
}
return !action.playable;
},
}, },
template: ` template: `
...@@ -51,16 +59,23 @@ export default { ...@@ -51,16 +59,23 @@ export default {
aria-label="Manual job" aria-label="Manual job"
:disabled="isLoading"> :disabled="isLoading">
${playIconSvg} ${playIconSvg}
<i class="fa fa-caret-down" aria-hidden="true"></i> <i
<i v-if="isLoading" class="fa fa-spinner fa-spin" aria-hidden="true"></i> class="fa fa-caret-down"
aria-hidden="true" />
<i
v-if="isLoading"
class="fa fa-spinner fa-spin"
aria-hidden="true" />
</button> </button>
<ul class="dropdown-menu dropdown-menu-align-right"> <ul class="dropdown-menu dropdown-menu-align-right">
<li v-for="action in actions"> <li v-for="action in actions">
<button <button
type="button" type="button"
class="js-pipeline-action-link no-btn" class="js-pipeline-action-link no-btn btn"
@click="onClickAction(action.path)"> @click="onClickAction(action.path)"
:class="{ 'disabled': isActionDisabled(action) }"
:disabled="isActionDisabled(action)">
${playIconSvg} ${playIconSvg}
<span>{{action.name}}</span> <span>{{action.name}}</span>
</button> </button>
......
/* eslint-disable no-underscore-dangle*/ /* eslint-disable no-underscore-dangle*/
import '../../vue_realtime_listener'; import VueRealtimeListener from '../../vue_realtime_listener';
export default class PipelinesStore { export default class PipelinesStore {
constructor() { constructor() {
...@@ -56,6 +56,6 @@ export default class PipelinesStore { ...@@ -56,6 +56,6 @@ export default class PipelinesStore {
const removeIntervals = () => clearInterval(this.timeLoopInterval); const removeIntervals = () => clearInterval(this.timeLoopInterval);
const startIntervals = () => startTimeLoops(); const startIntervals = () => startTimeLoops();
gl.VueRealtimeListener(removeIntervals, startIntervals); VueRealtimeListener(removeIntervals, startIntervals);
} }
} }
/* eslint-disable no-param-reassign */ export default (removeIntervals, startIntervals) => {
window.removeEventListener('focus', startIntervals);
((gl) => { window.removeEventListener('blur', removeIntervals);
gl.VueRealtimeListener = (removeIntervals, startIntervals) => { window.removeEventListener('onbeforeload', removeIntervals);
const removeAll = () => {
removeIntervals(); window.addEventListener('focus', startIntervals);
window.removeEventListener('beforeunload', removeIntervals); window.addEventListener('blur', removeIntervals);
window.removeEventListener('focus', startIntervals); window.addEventListener('onbeforeload', removeIntervals);
window.removeEventListener('blur', removeIntervals); };
document.removeEventListener('beforeunload', removeAll);
};
window.addEventListener('beforeunload', removeIntervals);
window.addEventListener('focus', startIntervals);
window.addEventListener('blur', removeIntervals);
document.addEventListener('beforeunload', removeAll);
// add removeAll methods to stack
const stack = gl.VueRealtimeListener.reset;
gl.VueRealtimeListener.reset = () => {
gl.VueRealtimeListener.reset = stack;
removeAll();
stack();
};
};
// remove all event listeners and intervals
gl.VueRealtimeListener.reset = () => undefined; // noop
})(window.gl || (window.gl = {}));
...@@ -275,3 +275,9 @@ span.idiff { ...@@ -275,3 +275,9 @@ span.idiff {
} }
} }
} }
.is-stl-loading {
.stl-controls {
display: none;
}
}
...@@ -3,6 +3,7 @@ class Admin::ProjectsController < Admin::ApplicationController ...@@ -3,6 +3,7 @@ class Admin::ProjectsController < Admin::ApplicationController
before_action :group, only: [:show, :transfer] before_action :group, only: [:show, :transfer]
def index def index
params[:sort] ||= 'latest_activity_desc'
@projects = Project.with_statistics @projects = Project.with_statistics
@projects = @projects.in_namespace(params[:namespace_id]) if params[:namespace_id].present? @projects = @projects.in_namespace(params[:namespace_id]) if params[:namespace_id].present?
@projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present? @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
......
...@@ -7,6 +7,7 @@ module ContinueParams ...@@ -7,6 +7,7 @@ module ContinueParams
continue_params = continue_params.permit(:to, :notice, :notice_now) continue_params = continue_params.permit(:to, :notice, :notice_now)
return unless continue_params[:to] && continue_params[:to].start_with?('/') return unless continue_params[:to] && continue_params[:to].start_with?('/')
return if continue_params[:to].start_with?('//')
continue_params continue_params
end end
......
# == FilterProjects
#
# Controller concern to handle projects filtering
# * by name
# * by archived state
#
module FilterProjects
extend ActiveSupport::Concern
def filter_projects(projects)
projects = projects.search(params[:name]) if params[:name].present?
projects = projects.non_archived if params[:archived].blank?
projects = projects.personal(current_user) if params[:personal].present? && current_user
projects
end
end
module ParamsBackwardCompatibility
private
def set_non_archived_param
params[:non_archived] = params[:archived].blank?
end
end
class Dashboard::ProjectsController < Dashboard::ApplicationController class Dashboard::ProjectsController < Dashboard::ApplicationController
include FilterProjects include ParamsBackwardCompatibility
before_action :set_non_archived_param
before_action :default_sorting
def index def index
@projects = load_projects(current_user.authorized_projects) @projects = load_projects(params.merge(non_public: true)).page(params[:page])
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page])
respond_to do |format| respond_to do |format|
format.html { @last_push = current_user.recent_push } format.html { @last_push = current_user.recent_push }
...@@ -21,10 +22,8 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -21,10 +22,8 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
end end
def starred def starred
@projects = load_projects(current_user.viewable_starred_projects) @projects = load_projects(params.merge(starred: true)).
@projects = @projects.includes(:forked_from_project, :tags) includes(:forked_from_project, :tags).page(params[:page])
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page])
@last_push = current_user.recent_push @last_push = current_user.recent_push
@groups = [] @groups = []
...@@ -41,14 +40,18 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -41,14 +40,18 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
private private
def load_projects(base_scope) def default_sorting
projects = base_scope.sorted_by_activity.includes(:route, namespace: :route) params[:sort] ||= 'latest_activity_desc'
@sort = params[:sort]
end
filter_projects(projects) def load_projects(finder_params)
ProjectsFinder.new(params: finder_params, current_user: current_user).
execute.includes(:route, namespace: :route)
end end
def load_events def load_events
@events = Event.in_projects(load_projects(current_user.authorized_projects)) @events = Event.in_projects(load_projects(params.merge(non_public: true)))
@events = event_filter.apply_filter(@events).with_associations @events = event_filter.apply_filter(@events).with_associations
@events = @events.limit(20).offset(params[:offset] || 0) @events = @events.limit(20).offset(params[:offset] || 0)
end end
......
...@@ -7,7 +7,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController ...@@ -7,7 +7,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
@sort = params[:sort] @sort = params[:sort]
@todos = @todos.page(params[:page]) @todos = @todos.page(params[:page])
if @todos.out_of_range? && @todos.total_pages != 0 if @todos.out_of_range? && @todos.total_pages != 0
redirect_to url_for(params.merge(page: @todos.total_pages)) redirect_to url_for(params.merge(page: @todos.total_pages, only_path: true))
end end
end end
......
class Explore::ProjectsController < Explore::ApplicationController class Explore::ProjectsController < Explore::ApplicationController
include FilterProjects include ParamsBackwardCompatibility
before_action :set_non_archived_param
def index def index
@projects = load_projects params[:sort] ||= 'latest_activity_desc'
@tags = @projects.tags_on(:tags) @sort = params[:sort]
@projects = @projects.tagged_with(params[:tag]) if params[:tag].present? @projects = load_projects.page(params[:page])
@projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
@projects = filter_projects(@projects)
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).page(params[:page])
respond_to do |format| respond_to do |format|
format.html format.html
...@@ -21,10 +19,9 @@ class Explore::ProjectsController < Explore::ApplicationController ...@@ -21,10 +19,9 @@ class Explore::ProjectsController < Explore::ApplicationController
end end
def trending def trending
@projects = load_projects(Project.trending) params[:trending] = true
@projects = filter_projects(@projects) @sort = params[:sort]
@projects = @projects.sort(@sort = params[:sort]) @projects = load_projects.page(params[:page])
@projects = @projects.page(params[:page])
respond_to do |format| respond_to do |format|
format.html format.html
...@@ -37,10 +34,7 @@ class Explore::ProjectsController < Explore::ApplicationController ...@@ -37,10 +34,7 @@ class Explore::ProjectsController < Explore::ApplicationController
end end
def starred def starred
@projects = load_projects @projects = load_projects.reorder('star_count DESC').page(params[:page])
@projects = filter_projects(@projects)
@projects = @projects.reorder('star_count DESC')
@projects = @projects.page(params[:page])
respond_to do |format| respond_to do |format|
format.html format.html
...@@ -52,10 +46,10 @@ class Explore::ProjectsController < Explore::ApplicationController ...@@ -52,10 +46,10 @@ class Explore::ProjectsController < Explore::ApplicationController
end end
end end
protected private
def load_projects(base_scope = nil) def load_projects
base_scope ||= ProjectsFinder.new.execute(current_user) ProjectsFinder.new(current_user: current_user, params: params).
base_scope.includes(:route, namespace: :route) execute.includes(:route, namespace: :route)
end end
end end
...@@ -27,7 +27,7 @@ class Groups::ApplicationController < ApplicationController ...@@ -27,7 +27,7 @@ class Groups::ApplicationController < ApplicationController
end end
def group_projects def group_projects
@projects ||= GroupProjectsFinder.new(group).execute(current_user) @projects ||= GroupProjectsFinder.new(group: group, current_user: current_user).execute
end end
def authorize_admin_group! def authorize_admin_group!
......
class GroupsController < Groups::ApplicationController class GroupsController < Groups::ApplicationController
include FilterProjects
include IssuesAction include IssuesAction
include MergeRequestsAction include MergeRequestsAction
include ParamsBackwardCompatibility
respond_to :html respond_to :html
...@@ -105,15 +105,16 @@ class GroupsController < Groups::ApplicationController ...@@ -105,15 +105,16 @@ class GroupsController < Groups::ApplicationController
protected protected
def setup_projects def setup_projects
set_non_archived_param
params[:sort] ||= 'latest_activity_desc'
@sort = params[:sort]
options = {} options = {}
options[:only_owned] = true if params[:shared] == '0' options[:only_owned] = true if params[:shared] == '0'
options[:only_shared] = true if params[:shared] == '1' options[:only_shared] = true if params[:shared] == '1'
@projects = GroupProjectsFinder.new(group, options).execute(current_user) @projects = GroupProjectsFinder.new(params: params, group: group, options: options, current_user: current_user).execute
@projects = @projects.includes(:namespace) @projects = @projects.includes(:namespace)
@projects = @projects.sorted_by_activity
@projects = filter_projects(@projects)
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]) if params[:name].blank? @projects = @projects.page(params[:page]) if params[:name].blank?
end end
......
...@@ -9,7 +9,7 @@ class Projects::ForksController < Projects::ApplicationController ...@@ -9,7 +9,7 @@ class Projects::ForksController < Projects::ApplicationController
def index def index
base_query = project.forks.includes(:creator) base_query = project.forks.includes(:creator)
@forks = base_query.merge(ProjectsFinder.new.execute(current_user)) @forks = base_query.merge(ProjectsFinder.new(current_user: current_user).execute)
@total_forks_count = base_query.size @total_forks_count = base_query.size
@private_forks_count = @total_forks_count - @forks.size @private_forks_count = @total_forks_count - @forks.size
@public_forks_count = @total_forks_count - @private_forks_count @public_forks_count = @total_forks_count - @private_forks_count
......
...@@ -11,10 +11,10 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -11,10 +11,10 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :redirect_to_external_issue_tracker, only: [:index, :new] before_action :redirect_to_external_issue_tracker, only: [:index, :new]
before_action :module_enabled before_action :module_enabled
before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests, before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests,
:related_branches, :can_create_branch] :related_branches, :can_create_branch, :rendered_title]
# Allow read any issue # Allow read any issue
before_action :authorize_read_issue!, only: [:show] before_action :authorize_read_issue!, only: [:show, :rendered_title]
# Allow write(create) issue # Allow write(create) issue
before_action :authorize_create_issue!, only: [:new, :create] before_action :authorize_create_issue!, only: [:new, :create]
...@@ -31,7 +31,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -31,7 +31,7 @@ class Projects::IssuesController < Projects::ApplicationController
@issuable_meta_data = issuable_meta_data(@issues, @collection_type) @issuable_meta_data = issuable_meta_data(@issues, @collection_type)
if @issues.out_of_range? && @issues.total_pages != 0 if @issues.out_of_range? && @issues.total_pages != 0
return redirect_to url_for(params.merge(page: @issues.total_pages)) return redirect_to url_for(params.merge(page: @issues.total_pages, only_path: true))
end end
if params[:label_name].present? if params[:label_name].present?
...@@ -196,6 +196,11 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -196,6 +196,11 @@ class Projects::IssuesController < Projects::ApplicationController
end end
end end
def rendered_title
Gitlab::PollingInterval.set_header(response, interval: 3_000)
render json: { title: view_context.markdown_field(@issue, :title) }
end
protected protected
def issue def issue
......
...@@ -43,7 +43,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -43,7 +43,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@issuable_meta_data = issuable_meta_data(@merge_requests, @collection_type) @issuable_meta_data = issuable_meta_data(@merge_requests, @collection_type)
if @merge_requests.out_of_range? && @merge_requests.total_pages != 0 if @merge_requests.out_of_range? && @merge_requests.total_pages != 0
return redirect_to url_for(params.merge(page: @merge_requests.total_pages)) return redirect_to url_for(params.merge(page: @merge_requests.total_pages, only_path: true))
end end
if params[:label_name].present? if params[:label_name].present?
......
...@@ -140,6 +140,6 @@ class UsersController < ApplicationController ...@@ -140,6 +140,6 @@ class UsersController < ApplicationController
end end
def projects_for_current_user def projects_for_current_user
ProjectsFinder.new.execute(current_user) ProjectsFinder.new(current_user: current_user).execute
end end
end end
class GroupProjectsFinder < UnionFinder # GroupProjectsFinder
def initialize(group, options = {}) #
# Used to filter Projects by set of params
#
# Arguments:
# current_user - which user use
# project_ids_relation: int[] - project ids to use
# group
# options:
# only_owned: boolean
# only_shared: boolean
# params:
# sort: string
# visibility_level: int
# tags: string[]
# personal: boolean
# search: string
# non_archived: boolean
#
class GroupProjectsFinder < ProjectsFinder
attr_reader :group, :options
def initialize(group:, params: {}, options: {}, current_user: nil, project_ids_relation: nil)
super(params: params, current_user: current_user, project_ids_relation: project_ids_relation)
@group = group @group = group
@options = options @options = options
end end
def execute(current_user = nil)
segments = group_projects(current_user)
find_union(segments, Project)
end
private private
def group_projects(current_user) def init_collection
only_owned = @options.fetch(:only_owned, false) only_owned = options.fetch(:only_owned, false)
only_shared = @options.fetch(:only_shared, false) only_shared = options.fetch(:only_shared, false)
projects = [] projects = []
if current_user if current_user
if @group.users.include?(current_user) if group.users.include?(current_user)
projects << @group.projects unless only_shared projects << group.projects unless only_shared
projects << @group.shared_projects unless only_owned projects << group.shared_projects unless only_owned
else else
unless only_shared unless only_shared
projects << @group.projects.visible_to_user(current_user) projects << group.projects.visible_to_user(current_user)
projects << @group.projects.public_to_user(current_user) projects << group.projects.public_to_user(current_user)
end end
unless only_owned unless only_owned
projects << @group.shared_projects.visible_to_user(current_user) projects << group.shared_projects.visible_to_user(current_user)
projects << @group.shared_projects.public_to_user(current_user) projects << group.shared_projects.public_to_user(current_user)
end end
end end
else else
projects << @group.projects.public_only unless only_shared projects << group.projects.public_only unless only_shared
projects << @group.shared_projects.public_only unless only_owned projects << group.shared_projects.public_only unless only_owned
end end
projects projects
end end
def union(items)
find_union(items, Project)
end
end end
...@@ -116,9 +116,9 @@ class IssuableFinder ...@@ -116,9 +116,9 @@ class IssuableFinder
if current_user && params[:authorized_only].presence && !current_user_related? if current_user && params[:authorized_only].presence && !current_user_related?
current_user.authorized_projects current_user.authorized_projects
elsif group elsif group
GroupProjectsFinder.new(group).execute(current_user) GroupProjectsFinder.new(group: group, current_user: current_user).execute
else else
projects_finder.execute(current_user, item_project_ids(items)) ProjectsFinder.new(current_user: current_user, project_ids_relation: item_project_ids(items)).execute
end end
@projects = projects.with_feature_available_for_user(klass, current_user).reorder(nil) @projects = projects.with_feature_available_for_user(klass, current_user).reorder(nil)
...@@ -405,8 +405,4 @@ class IssuableFinder ...@@ -405,8 +405,4 @@ class IssuableFinder
def current_user_related? def current_user_related?
params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me' params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
end end
def projects_finder
@projects_finder ||= ProjectsFinder.new
end
end end
...@@ -83,7 +83,7 @@ class LabelsFinder < UnionFinder ...@@ -83,7 +83,7 @@ class LabelsFinder < UnionFinder
def projects def projects
return @projects if defined?(@projects) return @projects if defined?(@projects)
@projects = skip_authorization ? Project.all : ProjectsFinder.new.execute(current_user) @projects = skip_authorization ? Project.all : ProjectsFinder.new(current_user: current_user).execute
@projects = @projects.in_namespace(params[:group_id]) if group? @projects = @projects.in_namespace(params[:group_id]) if group?
@projects = @projects.where(id: params[:project_ids]) if projects? @projects = @projects.where(id: params[:project_ids]) if projects?
@projects = @projects.reorder(nil) @projects = @projects.reorder(nil)
......
# ProjectsFinder
#
# Used to filter Projects by set of params
#
# Arguments:
# current_user - which user use
# project_ids_relation: int[] - project ids to use
# params:
# trending: boolean
# non_public: boolean
# starred: boolean
# sort: string
# visibility_level: int
# tags: string[]
# personal: boolean
# search: string
# non_archived: boolean
#
class ProjectsFinder < UnionFinder class ProjectsFinder < UnionFinder
def execute(current_user = nil, project_ids_relation = nil) attr_accessor :params
segments = all_projects(current_user) attr_reader :current_user, :project_ids_relation
segments.map! { |s| s.where(id: project_ids_relation) } if project_ids_relation
find_union(segments, Project).with_route def initialize(params: {}, current_user: nil, project_ids_relation: nil)
@params = params
@current_user = current_user
@project_ids_relation = project_ids_relation
end
def execute
items = init_collection
items = by_ids(items)
items = union(items)
items = by_personal(items)
items = by_visibilty_level(items)
items = by_tags(items)
items = by_search(items)
items = by_archived(items)
sort(items)
end end
private private
def all_projects(current_user) def init_collection
projects = [] projects = []
projects << current_user.authorized_projects if current_user if params[:trending].present?
projects << Project.unscoped.public_to_user(current_user) projects << Project.trending
elsif params[:starred].present? && current_user
projects << current_user.viewable_starred_projects
else
projects << current_user.authorized_projects if current_user
projects << Project.unscoped.public_to_user(current_user) unless params[:non_public].present?
end
projects projects
end end
def by_ids(items)
project_ids_relation ? items.map { |item| item.where(id: project_ids_relation) } : items
end
def union(items)
find_union(items, Project).with_route
end
def by_personal(items)
(params[:personal].present? && current_user) ? items.personal(current_user) : items
end
def by_visibilty_level(items)
params[:visibility_level].present? ? items.where(visibility_level: params[:visibility_level]) : items
end
def by_tags(items)
params[:tag].present? ? items.tagged_with(params[:tag]) : items
end
def by_search(items)
params[:search] ||= params[:name]
params[:search].present? ? items.search(params[:search]) : items
end
def sort(items)
params[:sort].present? ? items.sort(params[:sort]) : items
end
def by_archived(projects)
# Back-compatibility with the places where `params[:archived]` can be set explicitly to `false`
params[:non_archived] = !Gitlab::Utils.to_boolean(params[:archived]) if params.key?(:archived)
params[:non_archived] ? projects.non_archived : projects
end
end end
...@@ -95,7 +95,7 @@ class TodosFinder ...@@ -95,7 +95,7 @@ class TodosFinder
def projects(items) def projects(items)
item_project_ids = items.reorder(nil).select(:project_id) item_project_ids = items.reorder(nil).select(:project_id)
ProjectsFinder.new.execute(current_user, item_project_ids) ProjectsFinder.new(current_user: current_user, project_ids_relation: item_project_ids).execute
end end
def type? def type?
......
...@@ -407,7 +407,10 @@ module ProjectsHelper ...@@ -407,7 +407,10 @@ module ProjectsHelper
def sanitize_repo_path(project, message) def sanitize_repo_path(project, message)
return '' unless message.present? return '' unless message.present?
message.strip.gsub(project.repository_storage_path.chomp('/'), "[REPOS PATH]") exports_path = File.join(Settings.shared['path'], 'tmp/project_exports')
filtered_message = message.strip.gsub(exports_path, "[REPO EXPORT PATH]")
filtered_message.gsub(project.repository_storage_path.chomp('/'), "[REPOS PATH]")
end end
def project_feature_options def project_feature_options
......
...@@ -25,8 +25,8 @@ module SortingHelper ...@@ -25,8 +25,8 @@ module SortingHelper
def projects_sort_options_hash def projects_sort_options_hash
options = { options = {
sort_value_name => sort_title_name, sort_value_name => sort_title_name,
sort_value_recently_updated => sort_title_recently_updated, sort_value_latest_activity => sort_title_latest_activity,
sort_value_oldest_updated => sort_title_oldest_updated, sort_value_oldest_activity => sort_title_oldest_activity,
sort_value_recently_created => sort_title_recently_created, sort_value_recently_created => sort_title_recently_created,
sort_value_oldest_created => sort_title_oldest_created sort_value_oldest_created => sort_title_oldest_created
} }
...@@ -78,6 +78,14 @@ module SortingHelper ...@@ -78,6 +78,14 @@ module SortingHelper
'Last updated' 'Last updated'
end end
def sort_title_oldest_activity
'Oldest updated'
end
def sort_title_latest_activity
'Last updated'
end
def sort_title_oldest_created def sort_title_oldest_created
'Oldest created' 'Oldest created'
end end
...@@ -198,6 +206,14 @@ module SortingHelper ...@@ -198,6 +206,14 @@ module SortingHelper
'updated_desc' 'updated_desc'
end end
def sort_value_oldest_activity
'latest_activity_asc'
end
def sort_value_latest_activity
'latest_activity_desc'
end
def sort_value_oldest_created def sort_value_oldest_created
'created_asc' 'created_asc'
end end
......
...@@ -58,6 +58,10 @@ class Blob < SimpleDelegator ...@@ -58,6 +58,10 @@ class Blob < SimpleDelegator
binary? && extname.downcase.delete('.') == 'sketch' binary? && extname.downcase.delete('.') == 'sketch'
end end
def stl?
extname.downcase.delete('.') == 'stl'
end
def size_within_svg_limits? def size_within_svg_limits?
size <= MAXIMUM_SVG_SIZE size <= MAXIMUM_SVG_SIZE
end end
...@@ -81,6 +85,8 @@ class Blob < SimpleDelegator ...@@ -81,6 +85,8 @@ class Blob < SimpleDelegator
'notebook' 'notebook'
elsif sketch? elsif sketch?
'sketch' 'sketch'
elsif stl?
'stl'
elsif text? elsif text?
'text' 'text'
else else
......
...@@ -41,6 +41,8 @@ class Issue < ActiveRecord::Base ...@@ -41,6 +41,8 @@ class Issue < ActiveRecord::Base
scope :include_associations, -> { includes(:assignee, :labels, project: :namespace) } scope :include_associations, -> { includes(:assignee, :labels, project: :namespace) }
after_save :expire_etag_cache
attr_spammable :title, spam_title: true attr_spammable :title, spam_title: true
attr_spammable :description, spam_description: true attr_spammable :description, spam_description: true
...@@ -253,4 +255,13 @@ class Issue < ActiveRecord::Base ...@@ -253,4 +255,13 @@ class Issue < ActiveRecord::Base
def publicly_visible? def publicly_visible?
project.public? && !confidential? project.public? && !confidential?
end end
def expire_etag_cache
key = Gitlab::Routing.url_helpers.rendered_title_namespace_project_issue_path(
project.namespace,
project,
self
)
Gitlab::EtagCaching::Store.new.touch(key)
end
end end
...@@ -150,7 +150,7 @@ class Namespace < ActiveRecord::Base ...@@ -150,7 +150,7 @@ class Namespace < ActiveRecord::Base
end end
def any_project_has_container_registry_tags? def any_project_has_container_registry_tags?
projects.any?(&:has_container_registry_tags?) all_projects.any?(&:has_container_registry_tags?)
end end
def send_update_instructions def send_update_instructions
...@@ -214,6 +214,12 @@ class Namespace < ActiveRecord::Base ...@@ -214,6 +214,12 @@ class Namespace < ActiveRecord::Base
@old_repository_storage_paths ||= repository_storage_paths @old_repository_storage_paths ||= repository_storage_paths
end end
# Includes projects from this namespace and projects from all subgroups
# that belongs to this namespace
def all_projects
Project.inside_path(full_path)
end
private private
def repository_storage_paths def repository_storage_paths
...@@ -221,7 +227,7 @@ class Namespace < ActiveRecord::Base ...@@ -221,7 +227,7 @@ class Namespace < ActiveRecord::Base
# pending delete. Unscoping also get rids of the default order, which causes # pending delete. Unscoping also get rids of the default order, which causes
# problems with SELECT DISTINCT. # problems with SELECT DISTINCT.
Project.unscoped do Project.unscoped do
projects.select('distinct(repository_storage)').to_a.map(&:repository_storage_path) all_projects.select('distinct(repository_storage)').to_a.map(&:repository_storage_path)
end end
end end
......
...@@ -349,10 +349,15 @@ class Project < ActiveRecord::Base ...@@ -349,10 +349,15 @@ class Project < ActiveRecord::Base
end end
def sort(method) def sort(method)
if method == 'storage_size_desc' case method.to_s
when 'storage_size_desc'
# storage_size is a joined column so we need to # storage_size is a joined column so we need to
# pass a string to avoid AR adding the table name # pass a string to avoid AR adding the table name
reorder('project_statistics.storage_size DESC, projects.id DESC') reorder('project_statistics.storage_size DESC, projects.id DESC')
when 'latest_activity_desc'
reorder(last_activity_at: :desc)
when 'latest_activity_asc'
reorder(last_activity_at: :asc)
else else
order_by(method) order_by(method)
end end
......
...@@ -59,7 +59,7 @@ class Repository ...@@ -59,7 +59,7 @@ class Repository
def raw_repository def raw_repository
return nil unless path_with_namespace return nil unless path_with_namespace
@raw_repository ||= Gitlab::Git::Repository.new(path_to_repo) @raw_repository ||= initialize_raw_repository
end end
# Return absolute path to repository # Return absolute path to repository
...@@ -146,12 +146,7 @@ class Repository ...@@ -146,12 +146,7 @@ class Repository
# may cause the branch to "disappear" erroneously or have the wrong SHA. # may cause the branch to "disappear" erroneously or have the wrong SHA.
# #
# See: https://github.com/libgit2/libgit2/issues/1534 and https://gitlab.com/gitlab-org/gitlab-ce/issues/15392 # See: https://github.com/libgit2/libgit2/issues/1534 and https://gitlab.com/gitlab-org/gitlab-ce/issues/15392
raw_repo = raw_repo = fresh_repo ? initialize_raw_repository : raw_repository
if fresh_repo
Gitlab::Git::Repository.new(path_to_repo)
else
raw_repository
end
raw_repo.find_branch(name) raw_repo.find_branch(name)
end end
...@@ -505,9 +500,7 @@ class Repository ...@@ -505,9 +500,7 @@ class Repository
end end
end end
def branch_names delegate :branch_names, to: :raw_repository
branches.map(&:name)
end
cache_method :branch_names, fallback: [] cache_method :branch_names, fallback: []
delegate :tag_names, to: :raw_repository delegate :tag_names, to: :raw_repository
...@@ -1168,4 +1161,8 @@ class Repository ...@@ -1168,4 +1161,8 @@ class Repository
def repository_storage_path def repository_storage_path
@project.repository_storage_path @project.repository_storage_path
end end
def initialize_raw_repository
Gitlab::Git::Repository.new(project.repository_storage, path_with_namespace + '.git')
end
end end
...@@ -12,7 +12,7 @@ class GroupPolicy < BasePolicy ...@@ -12,7 +12,7 @@ class GroupPolicy < BasePolicy
can_read ||= globally_viewable can_read ||= globally_viewable
can_read ||= member can_read ||= member
can_read ||= @user.admin? can_read ||= @user.admin?
can_read ||= GroupProjectsFinder.new(@subject).execute(@user).any? can_read ||= GroupProjectsFinder.new(group: @subject, current_user: @user).execute.any?
can! :read_group if can_read can! :read_group if can_read
# Only group masters and group owners can create new projects # Only group masters and group owners can create new projects
...@@ -41,6 +41,6 @@ class GroupPolicy < BasePolicy ...@@ -41,6 +41,6 @@ class GroupPolicy < BasePolicy
return true if @subject.internal? && !@user.external? return true if @subject.internal? && !@user.external?
return true if @subject.users.include?(@user) return true if @subject.users.include?(@user)
GroupProjectsFinder.new(@subject).execute(@user).any? GroupProjectsFinder.new(group: @subject, current_user: @user).execute.any?
end end
end end
...@@ -11,4 +11,6 @@ class BuildActionEntity < Grape::Entity ...@@ -11,4 +11,6 @@ class BuildActionEntity < Grape::Entity
build.project, build.project,
build) build)
end end
expose :playable?, as: :playable
end end
...@@ -16,6 +16,7 @@ class BuildEntity < Grape::Entity ...@@ -16,6 +16,7 @@ class BuildEntity < Grape::Entity
path_to(:play_namespace_project_build, build) path_to(:play_namespace_project_build, build)
end end
expose :playable?, as: :playable
expose :created_at expose :created_at
expose :updated_at expose :updated_at
expose :detailed_status, as: :status, with: StatusEntity expose :detailed_status, as: :status, with: StatusEntity
......
...@@ -39,7 +39,7 @@ module MergeRequests ...@@ -39,7 +39,7 @@ module MergeRequests
private private
# Returns all origin and fork merge requests from `@project` satisfying passed arguments. # Returns all origin and fork merge requests from `@project` satisfying passed arguments.
def merge_requests_for(source_branch, mr_states: [:opened]) def merge_requests_for(source_branch, mr_states: [:opened, :reopened])
MergeRequest MergeRequest
.with_state(mr_states) .with_state(mr_states)
.where(source_branch: source_branch, source_project_id: @project.id) .where(source_branch: source_branch, source_project_id: @project.id)
......
...@@ -21,7 +21,9 @@ module MergeRequests ...@@ -21,7 +21,9 @@ module MergeRequests
delegate :target_branch, :source_branch, :source_project, :target_project, :compare_commits, :wip_title, :description, :errors, to: :merge_request delegate :target_branch, :source_branch, :source_project, :target_project, :compare_commits, :wip_title, :description, :errors, to: :merge_request
def find_source_project def find_source_project
source_project || project return source_project if source_project.present? && can?(current_user, :read_project, source_project)
project
end end
def find_target_project def find_target_project
......
...@@ -8,7 +8,7 @@ module Search ...@@ -8,7 +8,7 @@ module Search
def execute def execute
group = Group.find_by(id: params[:group_id]) if params[:group_id].present? group = Group.find_by(id: params[:group_id]) if params[:group_id].present?
projects = ProjectsFinder.new.execute(current_user) projects = ProjectsFinder.new(current_user: current_user).execute
if group if group
projects = projects.inside_path(group.full_path) projects = projects.inside_path(group.full_path)
......
- publicish_project_count = ProjectsFinder.new.execute(current_user).count - publicish_project_count = ProjectsFinder.new(current_user: current_user).execute.count
.blank-state.blank-state-welcome .blank-state.blank-state-welcome
%h2.blank-state-welcome-title %h2.blank-state-welcome-title
Welcome to GitLab Welcome to GitLab
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
%title= page_title(site_name) %title= page_title(site_name)
%meta{ name: "description", content: page_description } %meta{ name: "description", content: page_description }
= favicon_link_tag favicon = favicon_link_tag favicon, id: 'favicon'
= stylesheet_link_tag "application", media: "all" = stylesheet_link_tag "application", media: "all"
= stylesheet_link_tag "print", media: "print" = stylesheet_link_tag "print", media: "print"
......
...@@ -14,9 +14,21 @@ Commit Author: <%= commit.author.name %> ( <%= user_url(commit.author) %> ) ...@@ -14,9 +14,21 @@ Commit Author: <%= commit.author.name %> ( <%= user_url(commit.author) %> )
<% else -%> <% else -%>
Commit Author: <%= commit.author_name %> Commit Author: <%= commit.author_name %>
<% end -%> <% end -%>
<% if commit.different_committer? -%>
<% if commit.committer -%>
Committed by: <%= commit.committer.name %> ( <%= user_url(commit.committer) %> )
<% else -%>
Committed by: <%= commit.committer_name %>
<% end -%>
<% end -%>
<% if @pipeline.user -%>
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by <%= @pipeline.user.name %> ( <%= user_url(@pipeline.user) %> )
<% else -%>
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by API
<% end -%>
<% failed = @pipeline.statuses.latest.failed -%> <% failed = @pipeline.statuses.latest.failed -%>
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) had <%= failed.size %> failed <%= 'build'.pluralize(failed.size) %>. had <%= failed.size %> failed <%= 'build'.pluralize(failed.size) %>.
<% failed.each do |build| -%> <% failed.each do |build| -%>
<%= render "notify/links/#{build.to_partial_path}", pipeline: @pipeline, build: build %> <%= render "notify/links/#{build.to_partial_path}", pipeline: @pipeline, build: build %>
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
%tbody %tbody
%tr %tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" } Project %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" } Project
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;" }
- namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name - namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name
- namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner) - namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner)
%a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" } %a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" }
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
= @project.name = @project.name
%tr %tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Branch %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Branch
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody %tbody
%tr %tr
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
= @pipeline.ref = @pipeline.ref
%tr %tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Commit %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Commit
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:400;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody %tbody
%tr %tr
...@@ -52,13 +52,13 @@ ...@@ -52,13 +52,13 @@
= @merge_request.to_reference = @merge_request.to_reference
.commit{ style: "color:#5c5c5c;font-weight:300;" } .commit{ style: "color:#5c5c5c;font-weight:300;" }
= @pipeline.git_commit_message.truncate(50) = @pipeline.git_commit_message.truncate(50)
- commit = @pipeline.commit
%tr %tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Author %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Commit Author
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody %tbody
%tr %tr
- commit = @pipeline.commit
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
...@@ -68,17 +68,50 @@ ...@@ -68,17 +68,50 @@
- else - else
%span %span
= commit.author_name = commit.author_name
- if commit.different_committer?
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Committed by
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img.avatar{ height: "24", src: avatar_icon(commit.committer || commit.committer_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
- if commit.committer
%a.muted{ href: user_url(commit.committer), style: "color:#333333;text-decoration:none;" }
= commit.committer.name
- else
%span
= commit.committer_name
%tr.spacer %tr.spacer
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" }
&nbsp; &nbsp;
%tr.success-message %tr.success-message
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 5px;text-align:center;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 5px 0 5px;text-align:center;" }
- build_count = @pipeline.statuses.latest.size %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;font-weight:500;line-height:1.4;vertical-align:baseline;" }
Pipeline
%a{ href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;" }
= "\##{@pipeline.id}"
triggered by
- if @pipeline.user
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;padding-left:5px", width: "24" }
%img.avatar{ height: "24", src: avatar_icon(@pipeline.user, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;font-weight:500;line-height:1.4;vertical-align:baseline;" }
%a.muted{ href: user_url(@pipeline.user), style: "color:#333333;text-decoration:none;" }
= @pipeline.user.name
- else
%td{ style: "font-family:'Menlo','Liberation Mono','Consolas','DejaVu Sans Mono','Ubuntu Mono','Courier New','andale mono','lucida console',monospace;font-size:14px;line-height:1.4;vertical-align:baseline;padding:0 5px;" }
API
%tr
%td{ colspan: 2, style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:300;line-height:1.4;padding:15px 5px;text-align:center;" }
- job_count = @pipeline.statuses.latest.size
- stage_count = @pipeline.stages_count - stage_count = @pipeline.stages_count
Pipeline
%a{ href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;" }
= "\##{@pipeline.id}"
successfully completed successfully completed
#{build_count} #{'build'.pluralize(build_count)} #{job_count} #{'job'.pluralize(job_count)}
in in
#{stage_count} #{'stage'.pluralize(stage_count)}. #{stage_count} #{'stage'.pluralize(stage_count)}.
...@@ -14,7 +14,19 @@ Commit Author: <%= commit.author.name %> ( <%= user_url(commit.author) %> ) ...@@ -14,7 +14,19 @@ Commit Author: <%= commit.author.name %> ( <%= user_url(commit.author) %> )
<% else -%> <% else -%>
Commit Author: <%= commit.author_name %> Commit Author: <%= commit.author_name %>
<% end -%> <% end -%>
<% if commit.different_committer? -%>
<% if commit.committer -%>
Committed by: <%= commit.committer.name %> ( <%= user_url(commit.committer) %> )
<% else -%>
Committed by: <%= commit.committer_name %>
<% end -%>
<% end -%>
<% build_count = @pipeline.statuses.latest.size -%> <% build_count = @pipeline.statuses.latest.size -%>
<% stage_count = @pipeline.stages_count -%> <% stage_count = @pipeline.stages_count -%>
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) successfully completed <%= build_count %> <%= 'build'.pluralize(build_count) %> in <%= stage_count %> <%= 'stage'.pluralize(stage_count) %>. <% if @pipeline.user -%>
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by <%= @pipeline.user.name %> ( <%= user_url(@pipeline.user) %> )
<% else -%>
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by API
<% end -%>
successfully completed <%= build_count %> <%= 'build'.pluralize(build_count) %> in <%= stage_count %> <%= 'stage'.pluralize(stage_count) %>.
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('stl_viewer')
.file-content.is-stl-loading
.text-center#js-stl-viewer{ data: { endpoint: namespace_project_raw_path(@project.namespace, @project, @id) } }
= icon('spinner spin 2x', class: 'prepend-top-default append-bottom-default', 'aria-hidden' => 'true', 'aria-label' => 'Loading')
.text-center.prepend-top-default.append-bottom-default.stl-controls
.btn-group
%button.btn.btn-default.btn-sm.js-material-changer{ data: { type: 'wireframe' } }
Wireframe
%button.btn.btn-default.btn-sm.active.js-material-changer{ data: { type: 'default' } }
Solid
...@@ -49,11 +49,12 @@ ...@@ -49,11 +49,12 @@
= link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam' = link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam'
= link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit' = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit'
.issue-details.issuable-details .issue-details.issuable-details
.detail-page-description.content-block{ class: ('hide-bottom-border' unless @issue.description.present? ) } .detail-page-description.content-block{ class: ('hide-bottom-border' unless @issue.description.present? ) }
%h2.title .issue-title-data.hidden{ "data" => { "initial-title" => markdown_field(@issue, :title),
= markdown_field(@issue, :title) "endpoint" => rendered_title_namespace_project_issue_path(@project.namespace, @project, @issue),
} }
.issue-title-entrypoint
- if @issue.description.present? - if @issue.description.present?
.description{ class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : '' } .description{ class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : '' }
.wiki .wiki
...@@ -77,3 +78,5 @@ ...@@ -77,3 +78,5 @@
= render 'projects/issues/discussion' = render 'projects/issues/discussion'
= render 'shared/issuable/sidebar', issuable: @issue = render 'shared/issuable/sidebar', issuable: @issue
= page_specific_javascript_bundle_tag('issue_show')
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
merge_check_url: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", merge_check_url: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
check_enable: #{@merge_request.unchecked? ? "true" : "false"}, check_enable: #{@merge_request.unchecked? ? "true" : "false"},
ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
pipeline_status_url: "#{pipeline_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
ci_environments_status_url: "#{ci_environments_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", ci_environments_status_url: "#{ci_environments_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
gitlab_icon: "#{asset_path 'gitlab_logo.png'}", gitlab_icon: "#{asset_path 'gitlab_logo.png'}",
ci_status: "#{@merge_request.head_pipeline ? @merge_request.head_pipeline.status : ''}", ci_status: "#{@merge_request.head_pipeline ? @merge_request.head_pipeline.status : ''}",
......
- @sort ||= sort_value_recently_updated - @sort ||= sort_value_latest_activity
.dropdown .dropdown
- toggle_text = projects_sort_options_hash[@sort] - toggle_text = projects_sort_options_hash[@sort]
= dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { id: 'sort-projects-dropdown' }) = dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { id: 'sort-projects-dropdown' })
......
...@@ -2,6 +2,8 @@ class RepositoryImportWorker ...@@ -2,6 +2,8 @@ class RepositoryImportWorker
include Sidekiq::Worker include Sidekiq::Worker
include DedicatedSidekiqQueue include DedicatedSidekiqQueue
sidekiq_options status_expiration: StuckImportJobsWorker::IMPORT_EXPIRATION
attr_accessor :project, :current_user attr_accessor :project, :current_user
def perform(project_id) def perform(project_id)
...@@ -12,7 +14,7 @@ class RepositoryImportWorker ...@@ -12,7 +14,7 @@ class RepositoryImportWorker
import_url: @project.import_url, import_url: @project.import_url,
path: @project.path_with_namespace) path: @project.path_with_namespace)
project.update_column(:import_error, nil) project.update_columns(import_jid: self.jid, import_error: nil)
result = Projects::ImportService.new(project, current_user).execute result = Projects::ImportService.new(project, current_user).execute
......
class StuckImportJobsWorker
include Sidekiq::Worker
include CronjobQueue
IMPORT_EXPIRATION = 15.hours.to_i
def perform
stuck_projects.find_in_batches(batch_size: 500) do |group|
jids = group.map(&:import_jid)
# Find the jobs that aren't currently running or that exceeded the threshold.
completed_jids = Gitlab::SidekiqStatus.completed_jids(jids)
if completed_jids.any?
completed_ids = group.select { |project| completed_jids.include?(project.import_jid) }.map(&:id)
fail_batch!(completed_jids, completed_ids)
end
end
end
private
def stuck_projects
Project.select('id, import_jid').with_import_status(:started).where.not(import_jid: nil)
end
def fail_batch!(completed_jids, completed_ids)
Project.where(id: completed_ids).update_all(import_status: 'failed', import_error: error_message)
Rails.logger.info("Marked stuck import jobs as failed. JIDs: #{completed_jids.join(', ')}")
end
def error_message
"Import timed out. Import took longer than #{IMPORT_EXPIRATION} seconds"
end
end
---
title: Show CI status as Favicon on Pipelines, Job and MR pages
merge_request: 10144
author:
---
title: ProjectsFinder should handle more options
merge_request: 9682
author: Jacopo Beschi @jacopo-beschi
---
title: Don’t show source project name when user does not have access
merge_request:
author:
---
title: Enable creation of deploy keys with write access via the API
merge_request:
author:
---
title: Remove the class attribute from the whitelist for HTML generated from Markdown.
merge_request:
author:
---
title: Add search optional param and docs for V4
merge_request:
author:
---
title: Include reopened MRs when searching for opened ones
merge_request: 10407
author:
---
title: Fixes HTML structure that was preventing the tooltip to disappear when hovering
out of the button.
merge_request:
author:
---
title: Add remove_concurrent_index to database helper
merge_request: 10441
author: blackst0ne
---
title: Fix subgroup repository disappearance if group was moved
merge_request: 10414
author:
---
title: Fix path disclosure in project import/export
merge_request:
author:
---
title: Periodically mark projects that are stuck in importing as failed
merge_request:
author:
---
title: Incorporate Gitaly client for refs service
merge_request: 9291
author:
---
title: Fix for open redirect vulnerability using continue[to] in URL when requesting project import status.
merge_request:
author:
---
title: Fix for open redirect vulnerabilities in todos, issues, and MR controllers.
merge_request:
author:
---
title: Clearly show who triggered the pipeline in email
merge_request: 10283
author:
---
title: Disable pipeline and environment actions that are not playable
merge_request: 10052
author:
...@@ -349,6 +349,9 @@ Settings.cron_jobs['trending_projects_worker']['job_class'] = 'TrendingProjectsW ...@@ -349,6 +349,9 @@ Settings.cron_jobs['trending_projects_worker']['job_class'] = 'TrendingProjectsW
Settings.cron_jobs['remove_unreferenced_lfs_objects_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['remove_unreferenced_lfs_objects_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['remove_unreferenced_lfs_objects_worker']['cron'] ||= '20 0 * * *' Settings.cron_jobs['remove_unreferenced_lfs_objects_worker']['cron'] ||= '20 0 * * *'
Settings.cron_jobs['remove_unreferenced_lfs_objects_worker']['job_class'] = 'RemoveUnreferencedLfsObjectsWorker' Settings.cron_jobs['remove_unreferenced_lfs_objects_worker']['job_class'] = 'RemoveUnreferencedLfsObjectsWorker'
Settings.cron_jobs['stuck_import_jobs_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['stuck_import_jobs_worker']['cron'] ||= '15 * * * *'
Settings.cron_jobs['stuck_import_jobs_worker']['job_class'] = 'StuckImportJobsWorker'
# #
# GitLab Shell # GitLab Shell
......
...@@ -2,17 +2,5 @@ require 'uri' ...@@ -2,17 +2,5 @@ require 'uri'
# Make sure we initialize our Gitaly channels before Sidekiq starts multi-threaded execution. # Make sure we initialize our Gitaly channels before Sidekiq starts multi-threaded execution.
if Gitlab.config.gitaly.enabled || Rails.env.test? if Gitlab.config.gitaly.enabled || Rails.env.test?
Gitlab.config.repositories.storages.each do |name, params| Gitlab::GitalyClient.configure_channels
address = params['gitaly_address']
unless address.present?
raise "storage #{name.inspect} is missing a gitaly_address"
end
unless URI(address).scheme.in?(%w(tcp unix))
raise "Unsupported Gitaly address: #{address.inspect}"
end
Gitlab::GitalyClient.configure_channel(name, address)
end
end end
...@@ -250,6 +250,7 @@ constraints(ProjectUrlConstrainer.new) do ...@@ -250,6 +250,7 @@ constraints(ProjectUrlConstrainer.new) do
get :referenced_merge_requests get :referenced_merge_requests
get :related_branches get :related_branches
get :can_create_branch get :can_create_branch
get :rendered_title
end end
collection do collection do
post :bulk_update post :bulk_update
......
...@@ -42,10 +42,12 @@ var config = { ...@@ -42,10 +42,12 @@ var config = {
profile: './profile/profile_bundle.js', profile: './profile/profile_bundle.js',
protected_branches: './protected_branches/protected_branches_bundle.js', protected_branches: './protected_branches/protected_branches_bundle.js',
snippet: './snippet/snippet_bundle.js', snippet: './snippet/snippet_bundle.js',
stl_viewer: './blob/stl_viewer.js',
terminal: './terminal/terminal_bundle.js', terminal: './terminal/terminal_bundle.js',
u2f: ['vendor/u2f'], u2f: ['vendor/u2f'],
users: './users/users_bundle.js', users: './users/users_bundle.js',
vue_pipelines: './vue_pipelines_index/index.js', vue_pipelines: './vue_pipelines_index/index.js',
issue_show: './issue_show/index.js',
}, },
output: { output: {
......
...@@ -8,11 +8,10 @@ class MigrateRepoSize < ActiveRecord::Migration ...@@ -8,11 +8,10 @@ class MigrateRepoSize < ActiveRecord::Migration
project_data.each do |project| project_data.each do |project|
id = project['id'] id = project['id']
namespace_path = project['namespace_path'] || '' namespace_path = project['namespace_path'] || ''
repos_path = Gitlab.config.gitlab_shell['repos_path'] || Gitlab.config.repositories.storages.default['path'] path = File.join(namespace_path, project['project_path'] + '.git')
path = File.join(repos_path, namespace_path, project['project_path'] + '.git')
begin begin
repo = Gitlab::Git::Repository.new(path) repo = Gitlab::Git::Repository.new('default', path)
if repo.empty? if repo.empty?
print '-' print '-'
else else
......
# rubocop:disable RemoveIndex
class AddIndexOnRequestedAtToMembers < ActiveRecord::Migration class AddIndexOnRequestedAtToMembers < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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