Commit 9ba8685e authored by Clement Ho's avatar Clement Ho

Merge branch 'master' into 'docs-specific-review-examples'

# Conflicts:
#   doc/development/code_review.md
parents c4e7875d 0edf2b0d
...@@ -96,6 +96,7 @@ stages: ...@@ -96,6 +96,7 @@ stages:
- export KNAPSACK_GENERATE_REPORT=true - export KNAPSACK_GENERATE_REPORT=true
- export CACHE_CLASSES=true - export CACHE_CLASSES=true
- cp ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH} - cp ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH}
- scripts/gitaly-test-spawn
- knapsack rspec "--color --format documentation" - knapsack rspec "--color --format documentation"
artifacts: artifacts:
expire_in: 31d expire_in: 31d
...@@ -221,6 +222,7 @@ setup-test-env: ...@@ -221,6 +222,7 @@ setup-test-env:
- bundle exec rake gettext:po_to_json - bundle exec rake gettext:po_to_json
- bundle exec rake gitlab:assets:compile - bundle exec rake gitlab:assets:compile
- bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init' - bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
- scripts/gitaly-test-build # Do not use 'bundle exec' here
artifacts: artifacts:
expire_in: 7d expire_in: 7d
paths: paths:
...@@ -486,6 +488,7 @@ karma: ...@@ -486,6 +488,7 @@ karma:
BABEL_ENV: "coverage" BABEL_ENV: "coverage"
CHROME_LOG_FILE: "chrome_debug.log" CHROME_LOG_FILE: "chrome_debug.log"
script: script:
- scripts/gitaly-test-spawn
- bundle exec rake gettext:po_to_json - bundle exec rake gettext:po_to_json
- bundle exec rake karma - bundle exec rake karma
coverage: '/^Statements *: (\d+\.\d+%)/' coverage: '/^Statements *: (\d+\.\d+%)/'
......
This diff is collapsed.
This diff is collapsed.
...@@ -2,6 +2,16 @@ ...@@ -2,6 +2,16 @@
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.4.3 (2017-07-31)
- Fix Prometheus client PID reuse bug. !13130
- Improve deploy environment chatops slash command. !13150
- Fix asynchronous javascript paths when GitLab is installed under a relative URL. !13165
- Fix LDAP authentication to Git repository or container registry.
- Fixed new navigation breadcrumb title on help pages.
- Ensure filesystem metrics test files are deleted.
- Properly affixes nav bar in job view in microsoft edge.
## 9.4.2 (2017-07-28) ## 9.4.2 (2017-07-28)
- Fix job merge request link to a forked source project. !12965 - Fix job merge request link to a forked source project. !12965
......
...@@ -339,8 +339,8 @@ group :development, :test do ...@@ -339,8 +339,8 @@ group :development, :test do
gem 'spring-commands-rspec', '~> 1.0.4' gem 'spring-commands-rspec', '~> 1.0.4'
gem 'spring-commands-spinach', '~> 1.1.0' gem 'spring-commands-spinach', '~> 1.1.0'
gem 'rubocop', '~> 0.47.1', require: false gem 'rubocop', '~> 0.49.1', require: false
gem 'rubocop-rspec', '~> 1.15.0', require: false gem 'rubocop-rspec', '~> 1.15.1', require: false
gem 'scss_lint', '~> 0.54.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
......
...@@ -323,7 +323,7 @@ GEM ...@@ -323,7 +323,7 @@ GEM
multi_json (~> 1.10) multi_json (~> 1.10)
retriable (~> 1.4) retriable (~> 1.4)
signet (~> 0.6) signet (~> 0.6)
google-protobuf (3.2.0.2) google-protobuf (3.3.0)
googleauth (0.5.1) googleauth (0.5.1)
faraday (~> 0.9) faraday (~> 0.9)
jwt (~> 1.4) jwt (~> 1.4)
...@@ -545,6 +545,7 @@ GEM ...@@ -545,6 +545,7 @@ GEM
rubypants (~> 0.2) rubypants (~> 0.2)
orm_adapter (0.5.0) orm_adapter (0.5.0)
os (0.9.6) os (0.9.6)
parallel (1.11.2)
paranoia (2.3.1) paranoia (2.3.1)
activerecord (>= 4.0, < 5.2) activerecord (>= 4.0, < 5.2)
parser (2.4.0.0) parser (2.4.0.0)
...@@ -730,13 +731,14 @@ GEM ...@@ -730,13 +731,14 @@ GEM
pg pg
rails rails
sqlite3 sqlite3
rubocop (0.47.1) rubocop (0.49.1)
parallel (~> 1.10)
parser (>= 2.3.3.1, < 3.0) parser (>= 2.3.3.1, < 3.0)
powerpack (~> 0.1) powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0) rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1) unicode-display_width (~> 1.0, >= 1.0.1)
rubocop-rspec (1.15.0) rubocop-rspec (1.15.1)
rubocop (>= 0.42.0) rubocop (>= 0.42.0)
ruby-fogbugz (0.2.1) ruby-fogbugz (0.2.1)
crack (~> 0.4) crack (~> 0.4)
...@@ -868,7 +870,7 @@ GEM ...@@ -868,7 +870,7 @@ GEM
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.2) unf_ext (0.0.7.2)
unicode-display_width (1.1.3) unicode-display_width (1.3.0)
unicorn (5.1.0) unicorn (5.1.0)
kgio (~> 2.6) kgio (~> 2.6)
raindrops (~> 0.7) raindrops (~> 0.7)
...@@ -1078,8 +1080,8 @@ DEPENDENCIES ...@@ -1078,8 +1080,8 @@ DEPENDENCIES
rspec-retry (~> 0.4.5) rspec-retry (~> 0.4.5)
rspec-set (~> 0.1.3) rspec-set (~> 0.1.3)
rspec_profiling (~> 0.0.5) rspec_profiling (~> 0.0.5)
rubocop (~> 0.47.1) rubocop (~> 0.49.1)
rubocop-rspec (~> 1.15.0) rubocop-rspec (~> 1.15.1)
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.16.2) ruby-prof (~> 0.16.2)
ruby_parser (~> 3.8) ruby_parser (~> 3.8)
......
...@@ -128,7 +128,7 @@ information, see ...@@ -128,7 +128,7 @@ information, see
### After the 7th ### After the 7th
Once the stable branch is frozen, only fixes for regressions (bugs introduced in that same release) Once the stable branch is frozen, only fixes for [regressions](#regressions)
and security issues will be cherry-picked into the stable branch. and security issues will be cherry-picked into the stable branch.
Any merge requests cherry-picked into the stable branch for a previous release will also be picked into the latest stable branch. Any merge requests cherry-picked into the stable branch for a previous release will also be picked into the latest stable branch.
These fixes will be shipped in the next RC for that release if it is before the 22nd. These fixes will be shipped in the next RC for that release if it is before the 22nd.
...@@ -158,6 +158,24 @@ release should have the correct milestone assigned _and_ have the label ...@@ -158,6 +158,24 @@ release should have the correct milestone assigned _and_ have the label
Merge requests without a milestone and this label will Merge requests without a milestone and this label will
not be merged into any stable branches. not be merged into any stable branches.
### Regressions
A regression for a particular monthly release is a bug that exists in that
release, but wasn't present in the release before. This includes bugs in
features that were only added in that monthly release. Every regression **must**
have the milestone of the release it was introduced in - if a regression doesn't
have a milestone, it might be 'just' a bug!
For instance, if 10.5.0 adds a feature, and that feature doesn't work correctly,
then this is a regression in 10.5. If 10.5.1 then fixes that, but 10.5.3 somehow
reintroduces the bug, then this bug is still a regression in 10.5.
Because GitLab.com runs release candidates of new releases, a regression can be
reported in a release before its 'official' release date on the 22nd of the
month. When we say 'the most recent monthly release', this can refer to either
the version currently running on GitLab.com, or the most recent version
available in the package repositories.
## Release retrospective and kickoff ## Release retrospective and kickoff
### Retrospective ### Retrospective
......
...@@ -8,6 +8,7 @@ import BlobFileDropzone from '../blob/blob_file_dropzone'; ...@@ -8,6 +8,7 @@ import BlobFileDropzone from '../blob/blob_file_dropzone';
$(() => { $(() => {
const editBlobForm = $('.js-edit-blob-form'); const editBlobForm = $('.js-edit-blob-form');
const uploadBlobForm = $('.js-upload-blob-form'); const uploadBlobForm = $('.js-upload-blob-form');
const deleteBlobForm = $('.js-delete-blob-form');
if (editBlobForm.length) { if (editBlobForm.length) {
const urlRoot = editBlobForm.data('relative-url-root'); const urlRoot = editBlobForm.data('relative-url-root');
...@@ -30,4 +31,8 @@ $(() => { ...@@ -30,4 +31,8 @@ $(() => {
'.btn-upload-file', '.btn-upload-file',
); );
} }
if (deleteBlobForm.length) {
new NewCommitForm(deleteBlobForm);
}
}); });
...@@ -64,7 +64,7 @@ window.Build = (function () { ...@@ -64,7 +64,7 @@ window.Build = (function () {
$(window) $(window)
.off('scroll') .off('scroll')
.on('scroll', () => { .on('scroll', () => {
const contentHeight = this.$buildTraceOutput.prop('scrollHeight'); const contentHeight = this.$buildTraceOutput.height();
if (contentHeight > this.windowSize) { if (contentHeight > this.windowSize) {
// means the user did not scroll, the content was updated. // means the user did not scroll, the content was updated.
this.windowSize = contentHeight; this.windowSize = contentHeight;
...@@ -105,16 +105,17 @@ window.Build = (function () { ...@@ -105,16 +105,17 @@ window.Build = (function () {
}; };
Build.prototype.canScroll = function () { Build.prototype.canScroll = function () {
return document.body.scrollHeight > window.innerHeight; return $(document).height() > $(window).height();
}; };
Build.prototype.toggleScroll = function () { Build.prototype.toggleScroll = function () {
const currentPosition = document.body.scrollTop; const currentPosition = $(document).scrollTop();
const windowHeight = window.innerHeight; const scrollHeight = $(document).height();
const windowHeight = $(window).height();
if (this.canScroll()) { if (this.canScroll()) {
if (currentPosition > 0 && if (currentPosition > 0 &&
(document.body.scrollHeight - currentPosition !== windowHeight)) { (scrollHeight - currentPosition !== windowHeight)) {
// User is in the middle of the log // User is in the middle of the log
this.toggleDisableButton(this.$scrollTopBtn, false); this.toggleDisableButton(this.$scrollTopBtn, false);
...@@ -124,7 +125,7 @@ window.Build = (function () { ...@@ -124,7 +125,7 @@ window.Build = (function () {
this.toggleDisableButton(this.$scrollTopBtn, true); this.toggleDisableButton(this.$scrollTopBtn, true);
this.toggleDisableButton(this.$scrollBottomBtn, false); this.toggleDisableButton(this.$scrollBottomBtn, false);
} else if (document.body.scrollHeight - currentPosition === windowHeight) { } else if (scrollHeight - currentPosition === windowHeight) {
// User is at the bottom of the build log. // User is at the bottom of the build log.
this.toggleDisableButton(this.$scrollTopBtn, false); this.toggleDisableButton(this.$scrollTopBtn, false);
...@@ -137,7 +138,7 @@ window.Build = (function () { ...@@ -137,7 +138,7 @@ window.Build = (function () {
}; };
Build.prototype.scrollDown = function () { Build.prototype.scrollDown = function () {
document.body.scrollTop = document.body.scrollHeight; $(document).scrollTop($(document).height());
}; };
Build.prototype.scrollToBottom = function () { Build.prototype.scrollToBottom = function () {
...@@ -147,7 +148,7 @@ window.Build = (function () { ...@@ -147,7 +148,7 @@ window.Build = (function () {
}; };
Build.prototype.scrollToTop = function () { Build.prototype.scrollToTop = function () {
document.body.scrollTop = 0; $(document).scrollTop(0);
this.hasBeenScrolled = true; this.hasBeenScrolled = true;
this.toggleScroll(); this.toggleScroll();
}; };
...@@ -178,7 +179,7 @@ window.Build = (function () { ...@@ -178,7 +179,7 @@ window.Build = (function () {
this.state = log.state; this.state = log.state;
} }
this.windowSize = this.$buildTraceOutput.prop('scrollHeight'); this.windowSize = this.$buildTraceOutput.height();
if (log.append) { if (log.append) {
this.$buildTraceOutput.append(log.html); this.$buildTraceOutput.append(log.html);
......
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
/* global LabelsSelect */ /* global LabelsSelect */
/* global MilestoneSelect */ /* global MilestoneSelect */
/* global Commit */ /* global Commit */
/* global CommitsList */
/* global NewBranchForm */
/* global NotificationsForm */ /* global NotificationsForm */
/* global NotificationsDropdown */ /* global NotificationsDropdown */
/* global GroupAvatar */ /* global GroupAvatar */
...@@ -18,15 +20,20 @@ ...@@ -18,15 +20,20 @@
/* global Search */ /* global Search */
/* global Admin */ /* global Admin */
/* global NamespaceSelects */ /* global NamespaceSelects */
/* global NewCommitForm */
/* global NewBranchForm */
/* global Project */ /* global Project */
/* global ProjectAvatar */ /* global ProjectAvatar */
/* global MergeRequest */ /* global MergeRequest */
/* global Compare */ /* global Compare */
/* global CompareAutocomplete */ /* global CompareAutocomplete */
/* global ProjectFindFile */
/* global ProjectNew */ /* global ProjectNew */
/* global ProjectShow */ /* global ProjectShow */
/* global ProjectImport */
/* global Labels */ /* global Labels */
/* global Shortcuts */ /* global Shortcuts */
/* global ShortcutsFindFile */
/* global Sidebar */ /* global Sidebar */
/* global ShortcutsWiki */ /* global ShortcutsWiki */
...@@ -194,7 +201,6 @@ import GpgBadges from './gpg_badges'; ...@@ -194,7 +201,6 @@ import GpgBadges from './gpg_badges';
break; break;
case 'explore:groups:index': case 'explore:groups:index':
new GroupsList(); new GroupsList();
const landingElement = document.querySelector('.js-explore-groups-landing'); const landingElement = document.querySelector('.js-explore-groups-landing');
if (!landingElement) break; if (!landingElement) break;
const exploreGroupsLanding = new Landing( const exploreGroupsLanding = new Landing(
...@@ -217,6 +223,10 @@ import GpgBadges from './gpg_badges'; ...@@ -217,6 +223,10 @@ import GpgBadges from './gpg_badges';
case 'projects:compare:show': case 'projects:compare:show':
new gl.Diff(); new gl.Diff();
break; break;
case 'projects:branches:new':
case 'projects:branches:create':
new NewBranchForm($('.js-create-branch-form'), JSON.parse(document.getElementById('availableRefs').innerHTML));
break;
case 'projects:branches:index': case 'projects:branches:index':
gl.AjaxLoadingSpinner.init(); gl.AjaxLoadingSpinner.init();
new DeleteModal(); new DeleteModal();
...@@ -258,7 +268,7 @@ import GpgBadges from './gpg_badges'; ...@@ -258,7 +268,7 @@ import GpgBadges from './gpg_badges';
case 'projects:tags:new': case 'projects:tags:new':
new ZenMode(); new ZenMode();
new gl.GLForm($('.tag-form'), true); new gl.GLForm($('.tag-form'), true);
new RefSelectDropdown($('.js-branch-select'), window.gl.availableRefs); new RefSelectDropdown($('.js-branch-select'));
break; break;
case 'projects:snippets:show': case 'projects:snippets:show':
initNotes(); initNotes();
...@@ -304,18 +314,23 @@ import GpgBadges from './gpg_badges'; ...@@ -304,18 +314,23 @@ import GpgBadges from './gpg_badges';
container: '.js-commit-pipeline-graph', container: '.js-commit-pipeline-graph',
}).bindEvents(); }).bindEvents();
initNotes(); initNotes();
$('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
break; break;
case 'projects:commit:pipelines': case 'projects:commit:pipelines':
new MiniPipelineGraph({ new MiniPipelineGraph({
container: '.js-commit-pipeline-graph', container: '.js-commit-pipeline-graph',
}).bindEvents(); }).bindEvents();
$('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
break; break;
case 'projects:commits:show': case 'projects:activity':
new gl.Activities();
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
GpgBadges.fetch();
break; break;
case 'projects:activity': case 'projects:commits:show':
CommitsList.init(document.querySelector('.js-project-commits-show').dataset.commitsLimit);
new gl.Activities();
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
GpgBadges.fetch();
break; break;
case 'projects:show': case 'projects:show':
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
...@@ -330,6 +345,12 @@ import GpgBadges from './gpg_badges'; ...@@ -330,6 +345,12 @@ import GpgBadges from './gpg_badges';
case 'projects:edit': case 'projects:edit':
setupProjectEdit(); setupProjectEdit();
break; break;
case 'projects:imports:show':
new ProjectImport();
break;
case 'projects:pipelines:new':
new NewBranchForm($('.js-new-pipeline-form'));
break;
case 'projects:pipelines:builds': case 'projects:pipelines:builds':
case 'projects:pipelines:failures': case 'projects:pipelines:failures':
case 'projects:pipelines:show': case 'projects:pipelines:show':
...@@ -383,8 +404,19 @@ import GpgBadges from './gpg_badges'; ...@@ -383,8 +404,19 @@ import GpgBadges from './gpg_badges';
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
new TreeView(); new TreeView();
new BlobViewer(); new BlobViewer();
new NewCommitForm($('.js-create-dir-form'));
$('#tree-slider').waitForImages(function() {
gl.utils.ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
});
break; break;
case 'projects:find_file:show': case 'projects:find_file:show':
const findElement = document.querySelector('.js-file-finder');
const projectFindFile = new ProjectFindFile($(".file-finder-holder"), {
url: findElement.dataset.fileFindUrl,
treeUrl: findElement.dataset.findTreeUrl,
blobUrlTemplate: findElement.dataset.blobUrlTemplate,
});
new ShortcutsFindFile(projectFindFile);
shortcut_handler = true; shortcut_handler = true;
break; break;
case 'projects:blob:show': case 'projects:blob:show':
...@@ -540,6 +572,7 @@ import GpgBadges from './gpg_badges'; ...@@ -540,6 +572,7 @@ import GpgBadges from './gpg_badges';
shortcut_handler = new ShortcutsWiki(); shortcut_handler = new ShortcutsWiki();
new ZenMode(); new ZenMode();
new gl.GLForm($('.wiki-form'), true); new gl.GLForm($('.wiki-form'), true);
new Sidebar();
break; break;
case 'snippets': case 'snippets':
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
......
...@@ -730,10 +730,10 @@ GitLabDropdown = (function() { ...@@ -730,10 +730,10 @@ GitLabDropdown = (function() {
GitLabDropdown.prototype.focusTextInput = function(triggerFocus = false) { GitLabDropdown.prototype.focusTextInput = function(triggerFocus = false) {
if (this.options.filterable) { if (this.options.filterable) {
$(':focus').blur();
this.dropdown.one('transitionend', () => { this.dropdown.one('transitionend', () => {
this.filterInput.focus(); if (this.dropdown.is('.open')) {
this.filterInput.focus();
}
}); });
if (triggerFocus) { if (triggerFocus) {
......
import Chart from 'vendor/Chart'; import Chart from 'vendor/Chart';
import ContributorsStatGraph from './stat_graph_contributors';
// export to global scope // export to global scope
window.Chart = Chart; window.Chart = Chart;
window.ContributorsStatGraph = ContributorsStatGraph;
import Chart from 'vendor/Chart';
document.addEventListener('DOMContentLoaded', () => {
const projectChartData = JSON.parse(document.getElementById('projectChartData').innerHTML);
const responsiveChart = (selector, data) => {
const options = {
scaleOverlay: true,
responsive: true,
pointHitDetectionRadius: 2,
maintainAspectRatio: false,
};
// get selector by context
const ctx = selector.get(0).getContext('2d');
// pointing parent container to make chart.js inherit its width
const container = $(selector).parent();
const generateChart = () => {
selector.attr('width', $(container).width());
if (window.innerWidth < 768) {
// Scale fonts if window width lower than 768px (iPad portrait)
options.scaleFontSize = 8;
}
return new Chart(ctx).Bar(data, options);
};
// enabling auto-resizing
$(window).resize(generateChart);
return generateChart();
};
const chartData = (keys, values) => {
const data = {
labels: keys,
datasets: [{
fillColor: 'rgba(220,220,220,0.5)',
strokeColor: 'rgba(220,220,220,1)',
barStrokeWidth: 1,
barValueSpacing: 1,
barDatasetSpacing: 1,
data: values,
}],
};
return data;
};
const hourData = chartData(projectChartData.hour.keys, projectChartData.hour.values);
responsiveChart($('#hour-chart'), hourData);
const dayData = chartData(projectChartData.weekDays.keys, projectChartData.weekDays.values);
responsiveChart($('#weekday-chart'), dayData);
const monthData = chartData(projectChartData.month.keys, projectChartData.month.values);
responsiveChart($('#month-chart'), monthData);
const data = projectChartData.languages;
const ctx = $('#languages-chart').get(0).getContext('2d');
const options = {
scaleOverlay: true,
responsive: true,
maintainAspectRatio: false,
};
new Chart(ctx).Pie(data, options);
});
import ContributorsStatGraph from './stat_graph_contributors';
document.addEventListener('DOMContentLoaded', () => {
$.ajax({
type: 'GET',
url: document.querySelector('.js-graphs-show').dataset.projectGraphPath,
dataType: 'json',
success(data) {
const graph = new ContributorsStatGraph();
graph.init(data);
$('#brush_change').change(() => {
graph.change_date_header();
graph.redraw_authors();
});
$('.stat-graph').fadeIn();
$('.loading-graph').hide();
},
});
});
import Chart from 'vendor/Chart';
document.addEventListener('DOMContentLoaded', () => {
const chartData = JSON.parse(document.getElementById('pipelinesChartsData').innerHTML);
const buildChart = (chartScope) => {
const data = {
labels: chartScope.labels,
datasets: [{
fillColor: '#7f8fa4',
strokeColor: '#7f8fa4',
pointColor: '#7f8fa4',
pointStrokeColor: '#EEE',
data: chartScope.totalValues,
},
{
fillColor: '#44aa22',
strokeColor: '#44aa22',
pointColor: '#44aa22',
pointStrokeColor: '#fff',
data: chartScope.successValues,
},
],
};
const ctx = $(`#${chartScope.scope}Chart`).get(0).getContext('2d');
const options = {
scaleOverlay: true,
responsive: true,
maintainAspectRatio: false,
};
if (window.innerWidth < 768) {
// Scale fonts if window width lower than 768px (iPad portrait)
options.scaleFontSize = 8;
}
new Chart(ctx).Line(data, options);
};
chartData.forEach(scope => buildChart(scope));
});
import Chart from 'vendor/Chart';
document.addEventListener('DOMContentLoaded', () => {
const chartData = JSON.parse(document.getElementById('pipelinesTimesChartsData').innerHTML);
const data = {
labels: chartData.labels,
datasets: [{
fillColor: 'rgba(220,220,220,0.5)',
strokeColor: 'rgba(220,220,220,1)',
barStrokeWidth: 1,
barValueSpacing: 1,
barDatasetSpacing: 1,
data: chartData.values,
}],
};
const ctx = $('#build_timesChart').get(0).getContext('2d');
const options = {
scaleOverlay: true,
responsive: true,
maintainAspectRatio: false,
};
if (window.innerWidth < 768) {
// Scale fonts if window width lower than 768px (iPad portrait)
options.scaleFontSize = 8;
}
new Chart(ctx).Bar(data, options);
});
let hasUserDefinedProjectPath = false;
const deriveProjectPathFromUrl = ($projectImportUrl, $projectPath) => {
if ($projectImportUrl.attr('disabled') || hasUserDefinedProjectPath) {
return;
}
let importUrl = $projectImportUrl.val().trim();
if (importUrl.length === 0) {
return;
}
/*
\/?: remove trailing slash
(\.git\/?)?: remove trailing .git (with optional trailing slash)
(\?.*)?: remove query string
(#.*)?: remove fragment identifier
*/
importUrl = importUrl.replace(/\/?(\.git\/?)?(\?.*)?(#.*)?$/, '');
// extract everything after the last slash
const pathMatch = /\/([^/]+)$/.exec(importUrl);
if (pathMatch) {
$projectPath.val(pathMatch[1]);
}
};
const bindEvents = () => {
const $newProjectForm = $('#new_project');
const importBtnTooltip = 'Please enter a valid project name.';
const $importBtnWrapper = $('.import_gitlab_project');
const $projectImportUrl = $('#project_import_url');
const $projectPath = $('#project_path');
if ($newProjectForm.length !== 1) {
return;
}
$('.how_to_import_link').on('click', (e) => {
e.preventDefault();
$('.how_to_import_link').next('.modal').show();
});
$('.modal-header .close').on('click', () => {
$('.modal').hide();
});
$('.btn_import_gitlab_project').on('click', () => {
const importHref = $('a.btn_import_gitlab_project').attr('href');
$('.btn_import_gitlab_project').attr('href', `${importHref}?namespace_id=${$('#project_namespace_id').val()}&path=${$projectPath.val()}`);
});
$('.btn_import_gitlab_project').attr('disabled', !$projectPath.val().trim().length);
$importBtnWrapper.attr('title', importBtnTooltip);
$newProjectForm.on('submit', () => {
$projectPath.val($projectPath.val().trim());
});
$projectPath.on('keyup', () => {
hasUserDefinedProjectPath = $projectPath.val().trim().length > 0;
if (hasUserDefinedProjectPath) {
$('.btn_import_gitlab_project').attr('disabled', false);
$importBtnWrapper.attr('title', '');
$importBtnWrapper.removeClass('has-tooltip');
} else {
$('.btn_import_gitlab_project').attr('disabled', true);
$importBtnWrapper.addClass('has-tooltip');
}
});
$projectImportUrl.disable();
$projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl, $projectPath));
$('.import_git').on('click', () => {
$projectImportUrl.attr('disabled', !$projectImportUrl.attr('disabled'));
});
};
document.addEventListener('DOMContentLoaded', bindEvents);
export default {
bindEvents,
deriveProjectPathFromUrl,
};
class RefSelectDropdown { class RefSelectDropdown {
constructor($dropdownButton, availableRefs) { constructor($dropdownButton, availableRefs) {
const availableRefsValue = availableRefs || JSON.parse(document.getElementById('availableRefs').innerHTML);
$dropdownButton.glDropdown({ $dropdownButton.glDropdown({
data: availableRefs, data: availableRefsValue,
filterable: true, filterable: true,
filterByText: true, filterByText: true,
remote: false, remote: false,
......
/* global U2FRegister */
document.addEventListener('DOMContentLoaded', () => {
const twoFactorNode = document.querySelector('.js-two-factor-auth');
const skippable = twoFactorNode.dataset.twoFactorSkippable === 'true';
if (skippable) {
const button = `<a class="btn btn-xs btn-warning pull-right" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a>`;
const flashAlert = document.querySelector('.flash-alert .container-fluid');
if (flashAlert) flashAlert.insertAdjacentHTML('beforeend', button);
}
const u2fRegister = new U2FRegister($('#js-register-u2f'), gon.u2f);
u2fRegister.start();
});
import Api from './api';
document.addEventListener('DOMContentLoaded', () => {
$('#js-project-dropdown').glDropdown({
data: (term, callback) => {
Api.projects(term, {
order_by: 'last_activity_at',
}, (data) => {
callback(data);
});
},
text: project => (project.name_with_namespace || project.name),
selectable: true,
fieldName: 'author_id',
filterable: true,
search: {
fields: ['name_with_namespace'],
},
id: data => data.id,
isSelected: data => (data.id === 2),
});
});
...@@ -88,6 +88,10 @@ ...@@ -88,6 +88,10 @@
overflow: hidden; overflow: hidden;
display: flex; display: flex;
a {
display: flex;
}
.avatar { .avatar {
border-radius: 0; border-radius: 0;
border: none; border: none;
......
...@@ -414,13 +414,16 @@ ...@@ -414,13 +414,16 @@
background-color: $dropdown-hover-color; background-color: $dropdown-hover-color;
color: $white-light; color: $white-light;
text-decoration: none; text-decoration: none;
outline: 0;
.avatar { .avatar {
border-color: $white-light; border-color: $white-light;
} }
} }
.filter-dropdown-item { .droplab-dropdown .dropdown-menu .filter-dropdown-item {
padding: 0;
.btn { .btn {
border: none; border: none;
width: 100%; width: 100%;
...@@ -455,14 +458,11 @@ ...@@ -455,14 +458,11 @@
} }
.dropdown-user { .dropdown-user {
display: -webkit-flex;
display: flex; display: flex;
} }
.dropdown-user-details { .dropdown-user-details {
display: -webkit-flex;
display: flex; display: flex;
-webkit-flex-direction: column;
flex-direction: column; flex-direction: column;
> span { > span {
......
...@@ -313,6 +313,29 @@ header { ...@@ -313,6 +313,29 @@ header {
.impersonation i { .impersonation i {
color: $red-500; color: $red-500;
} }
// TODO: fallback to global style
.dropdown-menu,
.dropdown-menu-nav {
li {
padding: 0 1px;
a {
border-radius: 0;
padding: 8px 16px;
&:hover,
&:active,
&:focus {
background-color: $gray-darker;
}
}
}
}
}
.with-performance-bar header.navbar-gitlab {
top: $performance-bar-height;
} }
.navbar-nav { .navbar-nav {
......
...@@ -120,3 +120,7 @@ of the body element here, we negate cascading side effects but allow momentum sc ...@@ -120,3 +120,7 @@ of the body element here, we negate cascading side effects but allow momentum sc
.page-with-sidebar { .page-with-sidebar {
-webkit-overflow-scrolling: auto; -webkit-overflow-scrolling: auto;
} }
.with-performance-bar .page-with-sidebar {
margin-top: $header-height + $performance-bar-height;
}
...@@ -347,6 +347,10 @@ ...@@ -347,6 +347,10 @@
} }
} }
.with-performance-bar .layout-nav {
margin-top: $header-height + $performance-bar-height;
}
.scrolling-tabs-container { .scrolling-tabs-container {
position: relative; position: relative;
...@@ -441,6 +445,22 @@ ...@@ -441,6 +445,22 @@
} }
} }
.with-performance-bar .page-with-layout-nav {
.right-sidebar {
top: ($header-height + 1) * 2 + $performance-bar-height;
}
&.page-with-sub-nav {
.right-sidebar {
top: ($header-height + 1) * 3 + $performance-bar-height;
&.affix {
top: $header-height + $performance-bar-height;
}
}
}
}
.nav-block { .nav-block {
&.activities { &.activities {
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
......
...@@ -89,6 +89,10 @@ ...@@ -89,6 +89,10 @@
} }
} }
.with-performance-bar .right-sidebar.affix {
top: $header-height + $performance-bar-height;
}
@mixin maintain-sidebar-dimensions { @mixin maintain-sidebar-dimensions {
display: block; display: block;
width: $gutter-width; width: $gutter-width;
......
...@@ -204,6 +204,7 @@ $divergence-graph-separator-bg: #ccc; ...@@ -204,6 +204,7 @@ $divergence-graph-separator-bg: #ccc;
$general-hover-transition-duration: 100ms; $general-hover-transition-duration: 100ms;
$general-hover-transition-curve: linear; $general-hover-transition-curve: linear;
$highlight-changes-color: rgb(235, 255, 232); $highlight-changes-color: rgb(235, 255, 232);
$performance-bar-height: 35px;
/* /*
......
...@@ -309,6 +309,25 @@ header.navbar-gitlab-new { ...@@ -309,6 +309,25 @@ header.navbar-gitlab-new {
outline: 0; outline: 0;
} }
} }
// TODO: fallback to global style
.dropdown-menu {
li {
padding: 0 1px;
a {
border-radius: 0;
padding: 8px 16px;
&.is-focused,
&:hover,
&:active,
&:focus {
background-color: $gray-darker;
}
}
}
}
} }
.breadcrumbs-container { .breadcrumbs-container {
...@@ -325,6 +344,7 @@ header.navbar-gitlab-new { ...@@ -325,6 +344,7 @@ header.navbar-gitlab-new {
.breadcrumbs-links { .breadcrumbs-links {
flex: 1; flex: 1;
min-width: 0;
align-self: center; align-self: center;
color: $gl-text-color-quaternary; color: $gl-text-color-quaternary;
...@@ -343,7 +363,7 @@ header.navbar-gitlab-new { ...@@ -343,7 +363,7 @@ header.navbar-gitlab-new {
} }
.title { .title {
white-space: nowrap; display: inline-block;
> a { > a {
&:last-of-type:not(:first-child) { &:last-of-type:not(:first-child) {
......
...@@ -118,7 +118,7 @@ $new-sidebar-width: 220px; ...@@ -118,7 +118,7 @@ $new-sidebar-width: 220px;
z-index: 400; z-index: 400;
width: $new-sidebar-width; width: $new-sidebar-width;
transition: left $sidebar-transition-duration; transition: left $sidebar-transition-duration;
top: 50px; top: $header-height;
bottom: 0; bottom: 0;
left: 0; left: 0;
overflow: auto; overflow: auto;
...@@ -163,6 +163,10 @@ $new-sidebar-width: 220px; ...@@ -163,6 +163,10 @@ $new-sidebar-width: 220px;
} }
} }
.with-performance-bar .nav-sidebar {
top: $header-height + $performance-bar-height;
}
.sidebar-sub-level-items { .sidebar-sub-level-items {
display: none; display: none;
padding-bottom: 8px; padding-bottom: 8px;
...@@ -260,7 +264,7 @@ $new-sidebar-width: 220px; ...@@ -260,7 +264,7 @@ $new-sidebar-width: 220px;
// Make issue boards full-height now that sub-nav is gone // Make issue boards full-height now that sub-nav is gone
.boards-list { .boards-list {
height: calc(100vh - 50px); height: calc(100vh - #{$header-height});
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
height: 475px; // Needed for PhantomJS height: 475px; // Needed for PhantomJS
...@@ -270,6 +274,10 @@ $new-sidebar-width: 220px; ...@@ -270,6 +274,10 @@ $new-sidebar-width: 220px;
} }
} }
.with-performance-bar .boards-list {
height: calc(100vh - #{$header-height} - #{$performance-bar-height});
}
// Change color of all horizontal tabs to match the new indigo color // Change color of all horizontal tabs to match the new indigo color
.nav-links li.active a { .nav-links li.active a {
......
...@@ -64,10 +64,10 @@ ...@@ -64,10 +64,10 @@
color: $gl-text-color; color: $gl-text-color;
position: sticky; position: sticky;
position: -webkit-sticky; position: -webkit-sticky;
top: 50px; top: $header-height;
&.affix { &.affix {
top: 50px; top: $header-height;
} }
// with sidebar // with sidebar
...@@ -86,6 +86,7 @@ ...@@ -86,6 +86,7 @@
position: absolute; position: absolute;
right: 0; right: 0;
left: 0; left: 0;
top: 0;
} }
.truncated-info { .truncated-info {
...@@ -171,6 +172,16 @@ ...@@ -171,6 +172,16 @@
} }
} }
.with-performance-bar .build-page {
.top-bar {
top: $header-height + $performance-bar-height;
&.affix {
top: $header-height + $performance-bar-height;
}
}
}
.build-header { .build-header {
.ci-header-container, .ci-header-container,
.header-action-buttons { .header-action-buttons {
...@@ -300,9 +311,7 @@ ...@@ -300,9 +311,7 @@
} }
.dropdown-menu { .dropdown-menu {
right: $gl-padding; margin-top: -$gl-padding;
left: $gl-padding;
width: auto;
} }
svg { svg {
......
...@@ -110,6 +110,10 @@ ...@@ -110,6 +110,10 @@
.js-ca-dropdown { .js-ca-dropdown {
top: $gl-padding-top; top: $gl-padding-top;
.dropdown-menu-align-right {
margin-top: 2px;
}
} }
.content-list { .content-list {
...@@ -442,6 +446,24 @@ ...@@ -442,6 +446,24 @@
margin-bottom: 20px; margin-bottom: 20px;
} }
} }
// TODO: fallback to global style
.dropdown-menu {
li {
padding: 0 1px;
a {
border-radius: 0;
padding: 8px 16px;
&:hover,
&:active,
&:focus {
background-color: $gray-darker;
}
}
}
}
} }
.cycle-analytics-overview { .cycle-analytics-overview {
......
...@@ -445,6 +445,14 @@ ...@@ -445,6 +445,14 @@
} }
} }
.with-performance-bar .right-sidebar {
top: $header-height + $performance-bar-height;
.issuable-sidebar {
height: calc(100% - #{$header-height} - #{$performance-bar-height});
}
}
.detail-page-description { .detail-page-description {
padding: 16px 0; padding: 16px 0;
......
...@@ -759,6 +759,10 @@ ...@@ -759,6 +759,10 @@
} }
} }
.with-performance-bar .merge-request-tabs-holder {
top: $header-height + $performance-bar-height;
}
.merge-request-tabs { .merge-request-tabs {
display: flex; display: flex;
margin-bottom: 0; margin-bottom: 0;
......
...@@ -202,6 +202,28 @@ ...@@ -202,6 +202,28 @@
} }
} }
} }
// TODO: fallback to global style
.dropdown-menu:not(.dropdown-menu-selectable) {
li {
padding: 0 1px;
&.dropdown-header {
padding: 8px 16px;
}
a {
border-radius: 0;
padding: 8px 16px;
&:hover,
&:active,
&:focus {
background-color: $gray-darker;
}
}
}
}
} }
.blob-commit-info { .blob-commit-info {
......
...@@ -3,9 +3,16 @@ ...@@ -3,9 +3,16 @@
@import "peek/views/rblineprof"; @import "peek/views/rblineprof";
#peek { #peek {
height: 35px; position: fixed;
left: 0;
top: 0;
width: 100%;
z-index: 2000;
overflow-x: hidden;
height: $performance-bar-height;
background: $black; background: $black;
line-height: 35px; line-height: $performance-bar-height;
color: $perf-bar-text; color: $perf-bar-text;
&.disabled { &.disabled {
...@@ -25,7 +32,8 @@ ...@@ -25,7 +32,8 @@
} }
.wrapper { .wrapper {
width: 1000px; width: 80%;
height: $performance-bar-height;
margin: 0 auto; margin: 0 auto;
} }
......
...@@ -43,23 +43,7 @@ class Projects::GraphsController < Projects::ApplicationController ...@@ -43,23 +43,7 @@ class Projects::GraphsController < Projects::ApplicationController
end end
def get_languages def get_languages
@languages = Linguist::Repository.new(@repository.rugged, @repository.rugged.head.target_id).languages @languages = @project.repository.languages
total = @languages.map(&:last).sum
@languages = @languages.map do |language|
name, share = language
color = Linguist::Language[name].color || "##{Digest::SHA256.hexdigest(name)[0...6]}"
{
value: (share.to_f * 100 / total).round(2),
label: name,
color: color,
highlight: color
}
end
@languages.sort! do |x, y|
y[:value] <=> x[:value]
end
end end
def fetch_graph def fetch_graph
......
...@@ -264,7 +264,11 @@ module ApplicationHelper ...@@ -264,7 +264,11 @@ module ApplicationHelper
end end
def page_class def page_class
"issue-boards-page" if current_controller?(:boards) class_names = []
class_names << 'issue-boards-page' if current_controller?(:boards)
class_names << 'with-performance-bar' if performance_bar_enabled?
class_names
end end
# Returns active css class when condition returns true # Returns active css class when condition returns true
......
...@@ -48,7 +48,11 @@ module GitlabRoutingHelper ...@@ -48,7 +48,11 @@ module GitlabRoutingHelper
end end
def milestone_path(entity, *args) def milestone_path(entity, *args)
project_milestone_path(entity.project, entity, *args) if entity.is_group_milestone?
group_milestone_path(entity.group, entity, *args)
elsif entity.is_project_milestone?
project_milestone_path(entity.project, entity, *args)
end
end end
def issue_url(entity, *args) def issue_url(entity, *args)
...@@ -63,6 +67,14 @@ module GitlabRoutingHelper ...@@ -63,6 +67,14 @@ module GitlabRoutingHelper
project_pipeline_url(pipeline.project, pipeline.id, *args) project_pipeline_url(pipeline.project, pipeline.id, *args)
end end
def milestone_url(entity, *args)
if entity.is_group_milestone?
group_milestone_url(entity.group, entity, *args)
elsif entity.is_project_milestone?
project_milestone_url(entity.project, entity, *args)
end
end
def pipeline_job_url(pipeline, build, *args) def pipeline_job_url(pipeline, build, *args)
project_job_url(pipeline.project, build.id, *args) project_job_url(pipeline.project, build.id, *args)
end end
......
...@@ -40,7 +40,7 @@ module MergeRequestsHelper ...@@ -40,7 +40,7 @@ module MergeRequestsHelper
def merge_path_description(merge_request, separator) def merge_path_description(merge_request, separator)
if merge_request.for_fork? if merge_request.for_fork?
"Project:Branches: #{@merge_request.source_project_path}:#{@merge_request.source_branch} #{separator} #{@merge_request.target_project.path_with_namespace}:#{@merge_request.target_branch}" "Project:Branches: #{@merge_request.source_project_path}:#{@merge_request.source_branch} #{separator} #{@merge_request.target_project.full_path}:#{@merge_request.target_branch}"
else else
"Branches: #{@merge_request.source_branch} #{separator} #{@merge_request.target_branch}" "Branches: #{@merge_request.source_branch} #{separator} #{@merge_request.target_branch}"
end end
......
module NavHelper module NavHelper
def page_with_sidebar_class
class_name = page_gutter_class
class_name << 'page-with-new-sidebar' if defined?(@new_sidebar) && @new_sidebar
class_name
end
def page_gutter_class def page_gutter_class
if current_path?('merge_requests#show') || if current_path?('merge_requests#show') ||
current_path?('projects/merge_requests/conflicts#show') || current_path?('projects/merge_requests/conflicts#show') ||
current_path?('issues#show') || current_path?('issues#show') ||
current_path?('milestones#show') current_path?('milestones#show')
if cookies[:collapsed_gutter] == 'true' if cookies[:collapsed_gutter] == 'true'
"page-gutter right-sidebar-collapsed" %w[page-gutter right-sidebar-collapsed]
else else
"page-gutter right-sidebar-expanded" %w[page-gutter right-sidebar-expanded]
end end
elsif current_path?('jobs#show') elsif current_path?('jobs#show')
"page-gutter build-sidebar right-sidebar-expanded" %w[page-gutter build-sidebar right-sidebar-expanded]
elsif current_path?('wikis#show') || elsif current_path?('wikis#show') ||
current_path?('wikis#edit') || current_path?('wikis#edit') ||
current_path?('wikis#update') || current_path?('wikis#update') ||
current_path?('wikis#history') || current_path?('wikis#history') ||
current_path?('wikis#git_access') current_path?('wikis#git_access')
"page-gutter wiki-sidebar right-sidebar-expanded" %w[page-gutter wiki-sidebar right-sidebar-expanded]
else
[]
end end
end end
def nav_header_class def nav_header_class
class_name = '' class_names = []
class_name << " with-horizontal-nav" if defined?(nav) && nav class_names << 'with-horizontal-nav' if defined?(nav) && nav
class_name class_names
end end
def layout_nav_class def layout_nav_class
class_name = '' return [] if show_new_nav?
class_name << " page-with-layout-nav" if defined?(nav) && nav
class_name << " page-with-sub-nav" if content_for?(:sub_nav)
class_name class_names = []
class_names << 'page-with-layout-nav' if defined?(nav) && nav
class_names << 'page-with-sub-nav' if content_for?(:sub_nav)
class_names
end end
def nav_control_class def nav_control_class
......
...@@ -398,7 +398,7 @@ module ProjectsHelper ...@@ -398,7 +398,7 @@ module ProjectsHelper
if project if project
import_path = "/Home/Stacks/import" import_path = "/Home/Stacks/import"
repo = project.path_with_namespace repo = project.full_path
branch ||= project.default_branch branch ||= project.default_branch
sha ||= project.commit.short_id sha ||= project.commit.short_id
...@@ -458,7 +458,7 @@ module ProjectsHelper ...@@ -458,7 +458,7 @@ module ProjectsHelper
def readme_cache_key def readme_cache_key
sha = @project.commit.try(:sha) || 'nil' sha = @project.commit.try(:sha) || 'nil'
[@project.path_with_namespace, sha, "readme"].join('-') [@project.full_path, sha, "readme"].join('-')
end end
def current_ref def current_ref
......
...@@ -34,6 +34,8 @@ module WebpackHelper ...@@ -34,6 +34,8 @@ module WebpackHelper
end end
def webpack_public_path def webpack_public_path
"#{webpack_public_host}/#{Rails.application.config.webpack.public_path}/" relative_path = Rails.application.config.relative_url_root
webpack_path = Rails.application.config.webpack.public_path
File.join(webpack_public_host.to_s, relative_path.to_s, webpack_path.to_s, '')
end end
end end
...@@ -165,7 +165,7 @@ class Notify < BaseMailer ...@@ -165,7 +165,7 @@ class Notify < BaseMailer
headers['X-GitLab-Project'] = @project.name headers['X-GitLab-Project'] = @project.name
headers['X-GitLab-Project-Id'] = @project.id headers['X-GitLab-Project-Id'] = @project.id
headers['X-GitLab-Project-Path'] = @project.path_with_namespace headers['X-GitLab-Project-Path'] = @project.full_path
end end
def add_unsubscription_headers_and_links def add_unsubscription_headers_and_links
......
...@@ -317,7 +317,7 @@ module Ci ...@@ -317,7 +317,7 @@ module Ci
return @config_processor if defined?(@config_processor) return @config_processor if defined?(@config_processor)
@config_processor ||= begin @config_processor ||= begin
Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace) Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.full_path)
rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
self.yaml_errors = e.message self.yaml_errors = e.message
nil nil
......
module Storage
module LegacyNamespace
extend ActiveSupport::Concern
def move_dir
if any_project_has_container_registry_tags?
raise Gitlab::UpdatePathError.new('Namespace cannot be moved, because at least one project has tags in container registry')
end
# Move the namespace directory in all storage paths used by member projects
repository_storage_paths.each do |repository_storage_path|
# Ensure old directory exists before moving it
gitlab_shell.add_namespace(repository_storage_path, full_path_was)
unless gitlab_shell.mv_namespace(repository_storage_path, full_path_was, full_path)
Rails.logger.error "Exception moving path #{repository_storage_path} from #{full_path_was} to #{full_path}"
# if we cannot move namespace directory we should rollback
# db changes in order to prevent out of sync between db and fs
raise Gitlab::UpdatePathError.new('namespace directory cannot be moved')
end
end
Gitlab::UploadsTransfer.new.rename_namespace(full_path_was, full_path)
Gitlab::PagesTransfer.new.rename_namespace(full_path_was, full_path)
remove_exports!
# If repositories moved successfully we need to
# send update instructions to users.
# However we cannot allow rollback since we moved namespace dir
# So we basically we mute exceptions in next actions
begin
send_update_instructions
true
rescue
# Returning false does not rollback after_* transaction but gives
# us information about failing some of tasks
false
end
end
# Hooks
# Save the storage paths before the projects are destroyed to use them on after destroy
def prepare_for_destroy
old_repository_storage_paths
end
private
def old_repository_storage_paths
@old_repository_storage_paths ||= repository_storage_paths
end
def repository_storage_paths
# We need to get the storage paths for all the projects, even the ones that are
# pending delete. Unscoping also get rids of the default order, which causes
# problems with SELECT DISTINCT.
Project.unscoped do
all_projects.select('distinct(repository_storage)').to_a.map(&:repository_storage_path)
end
end
def rm_dir
# Remove the namespace directory in all storages paths used by member projects
old_repository_storage_paths.each do |repository_storage_path|
# Move namespace directory into trash.
# We will remove it later async
new_path = "#{full_path}+#{id}+deleted"
if gitlab_shell.mv_namespace(repository_storage_path, full_path, new_path)
Gitlab::AppLogger.info %Q(Namespace directory "#{full_path}" moved to "#{new_path}")
# Remove namespace directroy async with delay so
# GitLab has time to remove all projects first
run_after_commit do
GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage_path, new_path)
end
end
end
remove_exports!
end
def remove_exports!
Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete))
end
def export_path
File.join(Gitlab::ImportExport.storage_path, full_path_was)
end
def full_path_was
if parent
parent.full_path + '/' + path_was
else
path_was
end
end
end
end
module Storage
module LegacyProject
extend ActiveSupport::Concern
def disk_path
full_path
end
def ensure_storage_path_exist
gitlab_shell.add_namespace(repository_storage_path, namespace.full_path)
end
def rename_repo
path_was = previous_changes['path'].first
old_path_with_namespace = File.join(namespace.full_path, path_was)
new_path_with_namespace = File.join(namespace.full_path, path)
Rails.logger.error "Attempting to rename #{old_path_with_namespace} -> #{new_path_with_namespace}"
if has_container_registry_tags?
Rails.logger.error "Project #{old_path_with_namespace} cannot be renamed because container registry tags are present!"
# we currently doesn't support renaming repository if it contains images in container registry
raise StandardError.new('Project cannot be renamed, because images are present in its container registry')
end
expire_caches_before_rename(old_path_with_namespace)
if gitlab_shell.mv_repository(repository_storage_path, old_path_with_namespace, new_path_with_namespace)
# If repository moved successfully we need to send update instructions to users.
# However we cannot allow rollback since we moved repository
# So we basically we mute exceptions in next actions
begin
gitlab_shell.mv_repository(repository_storage_path, "#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
send_move_instructions(old_path_with_namespace)
expires_full_path_cache
@old_path_with_namespace = old_path_with_namespace
SystemHooksService.new.execute_hooks_for(self, :rename)
@repository = nil
rescue => e
Rails.logger.error "Exception renaming #{old_path_with_namespace} -> #{new_path_with_namespace}: #{e}"
# Returning false does not rollback after_* transaction but gives
# us information about failing some of tasks
false
end
else
Rails.logger.error "Repository could not be renamed: #{old_path_with_namespace} -> #{new_path_with_namespace}"
# if we cannot move namespace directory we should rollback
# db changes in order to prevent out of sync between db and fs
raise StandardError.new('repository cannot be renamed')
end
Gitlab::AppLogger.info "Project was renamed: #{old_path_with_namespace} -> #{new_path_with_namespace}"
Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.full_path)
Gitlab::PagesTransfer.new.rename_project(path_was, path, namespace.full_path)
end
def create_repository(force: false)
# Forked import is handled asynchronously
return if forked? && !force
if gitlab_shell.add_repository(repository_storage_path, path_with_namespace)
repository.after_create
true
else
errors.add(:base, 'Failed to create repository via gitlab-shell')
false
end
end
end
end
module Storage
module LegacyProjectWiki
extend ActiveSupport::Concern
def disk_path
project.disk_path + '.wiki'
end
end
end
module Storage
module LegacyRepository
extend ActiveSupport::Concern
delegate :disk_path, to: :project
end
end
...@@ -630,7 +630,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -630,7 +630,7 @@ class MergeRequest < ActiveRecord::Base
def target_project_path def target_project_path
if target_project if target_project
target_project.path_with_namespace target_project.full_path
else else
"(removed)" "(removed)"
end end
...@@ -638,7 +638,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -638,7 +638,7 @@ class MergeRequest < ActiveRecord::Base
def source_project_path def source_project_path
if source_project if source_project
source_project.path_with_namespace source_project.full_path
else else
"(removed)" "(removed)"
end end
......
...@@ -8,6 +8,7 @@ class Namespace < ActiveRecord::Base ...@@ -8,6 +8,7 @@ class Namespace < ActiveRecord::Base
include Gitlab::VisibilityLevel include Gitlab::VisibilityLevel
include Routable include Routable
include AfterCommitQueue include AfterCommitQueue
include Storage::LegacyNamespace
# Prevent users from creating unreasonably deep level of nesting. # Prevent users from creating unreasonably deep level of nesting.
# The number 20 was taken based on maximum nesting level of # The number 20 was taken based on maximum nesting level of
...@@ -41,10 +42,11 @@ class Namespace < ActiveRecord::Base ...@@ -41,10 +42,11 @@ class Namespace < ActiveRecord::Base
delegate :name, to: :owner, allow_nil: true, prefix: true delegate :name, to: :owner, allow_nil: true, prefix: true
after_update :move_dir, if: :path_changed?
after_commit :refresh_access_of_projects_invited_groups, on: :update, if: -> { previous_changes.key?('share_with_group_lock') } after_commit :refresh_access_of_projects_invited_groups, on: :update, if: -> { previous_changes.key?('share_with_group_lock') }
# Save the storage paths before the projects are destroyed to use them on after destroy # Legacy Storage specific hooks
after_update :move_dir, if: :path_changed?
before_destroy(prepend: true) { prepare_for_destroy } before_destroy(prepend: true) { prepare_for_destroy }
after_destroy :rm_dir after_destroy :rm_dir
...@@ -118,43 +120,6 @@ class Namespace < ActiveRecord::Base ...@@ -118,43 +120,6 @@ class Namespace < ActiveRecord::Base
owner_name owner_name
end end
def move_dir
if any_project_has_container_registry_tags?
raise Gitlab::UpdatePathError.new('Namespace cannot be moved, because at least one project has tags in container registry')
end
# Move the namespace directory in all storages paths used by member projects
repository_storage_paths.each do |repository_storage_path|
# Ensure old directory exists before moving it
gitlab_shell.add_namespace(repository_storage_path, full_path_was)
unless gitlab_shell.mv_namespace(repository_storage_path, full_path_was, full_path)
Rails.logger.error "Exception moving path #{repository_storage_path} from #{full_path_was} to #{full_path}"
# if we cannot move namespace directory we should rollback
# db changes in order to prevent out of sync between db and fs
raise Gitlab::UpdatePathError.new('namespace directory cannot be moved')
end
end
Gitlab::UploadsTransfer.new.rename_namespace(full_path_was, full_path)
Gitlab::PagesTransfer.new.rename_namespace(full_path_was, full_path)
remove_exports!
# If repositories moved successfully we need to
# send update instructions to users.
# However we cannot allow rollback since we moved namespace dir
# So we basically we mute exceptions in next actions
begin
send_update_instructions
rescue
# Returning false does not rollback after_* transaction but gives
# us information about failing some of tasks
false
end
end
def any_project_has_container_registry_tags? def any_project_has_container_registry_tags?
all_projects.any?(&:has_container_registry_tags?) all_projects.any?(&:has_container_registry_tags?)
end end
...@@ -206,14 +171,6 @@ class Namespace < ActiveRecord::Base ...@@ -206,14 +171,6 @@ class Namespace < ActiveRecord::Base
parent_id_changed? parent_id_changed?
end end
def prepare_for_destroy
old_repository_storage_paths
end
def old_repository_storage_paths
@old_repository_storage_paths ||= repository_storage_paths
end
# Includes projects from this namespace and projects from all subgroups # Includes projects from this namespace and projects from all subgroups
# that belongs to this namespace # that belongs to this namespace
def all_projects def all_projects
...@@ -232,37 +189,6 @@ class Namespace < ActiveRecord::Base ...@@ -232,37 +189,6 @@ class Namespace < ActiveRecord::Base
private private
def repository_storage_paths
# We need to get the storage paths for all the projects, even the ones that are
# pending delete. Unscoping also get rids of the default order, which causes
# problems with SELECT DISTINCT.
Project.unscoped do
all_projects.select('distinct(repository_storage)').to_a.map(&:repository_storage_path)
end
end
def rm_dir
# Remove the namespace directory in all storages paths used by member projects
old_repository_storage_paths.each do |repository_storage_path|
# Move namespace directory into trash.
# We will remove it later async
new_path = "#{full_path}+#{id}+deleted"
if gitlab_shell.mv_namespace(repository_storage_path, full_path, new_path)
message = "Namespace directory \"#{full_path}\" moved to \"#{new_path}\""
Gitlab::AppLogger.info message
# Remove namespace directroy async with delay so
# GitLab has time to remove all projects first
run_after_commit do
GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage_path, new_path)
end
end
end
remove_exports!
end
def refresh_access_of_projects_invited_groups def refresh_access_of_projects_invited_groups
Group Group
.joins(project_group_links: :project) .joins(project_group_links: :project)
...@@ -270,22 +196,6 @@ class Namespace < ActiveRecord::Base ...@@ -270,22 +196,6 @@ class Namespace < ActiveRecord::Base
.find_each(&:refresh_members_authorized_projects) .find_each(&:refresh_members_authorized_projects)
end end
def remove_exports!
Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete))
end
def export_path
File.join(Gitlab::ImportExport.storage_path, full_path_was)
end
def full_path_was
if parent
parent.full_path + '/' + path_was
else
path_was
end
end
def nesting_level_allowed def nesting_level_allowed
if ancestors.count > Group::NUMBER_OF_ANCESTORS_ALLOWED if ancestors.count > Group::NUMBER_OF_ANCESTORS_ALLOWED
errors.add(:parent_id, "has too deep level of nesting") errors.add(:parent_id, "has too deep level of nesting")
......
class NotificationSetting < ActiveRecord::Base class NotificationSetting < ActiveRecord::Base
include IgnorableColumn
ignore_column :events
enum level: { global: 3, watch: 2, mention: 4, participating: 1, disabled: 0, custom: 5 } enum level: { global: 3, watch: 2, mention: 4, participating: 1, disabled: 0, custom: 5 }
default_value_for :level, NotificationSetting.levels[:global] default_value_for :level, NotificationSetting.levels[:global]
...@@ -41,9 +45,6 @@ class NotificationSetting < ActiveRecord::Base ...@@ -41,9 +45,6 @@ class NotificationSetting < ActiveRecord::Base
:success_pipeline :success_pipeline
].freeze ].freeze
store :events, coder: JSON
before_save :convert_events
def self.find_or_create_for(source) def self.find_or_create_for(source)
setting = find_or_initialize_by(source: source) setting = find_or_initialize_by(source: source)
...@@ -54,42 +55,17 @@ class NotificationSetting < ActiveRecord::Base ...@@ -54,42 +55,17 @@ class NotificationSetting < ActiveRecord::Base
setting setting
end end
# 1. Check if this event has a value stored in its database column.
# 2. If it does, return that value.
# 3. If it doesn't (the value is nil), return the value from the serialized
# JSON hash in `events`.
(EMAIL_EVENTS - [:failed_pipeline]).each do |event|
define_method(event) do
bool = super()
bool.nil? ? !!events[event] : bool
end
alias_method :"#{event}?", event
end
# Allow people to receive failed pipeline notifications if they already have # Allow people to receive failed pipeline notifications if they already have
# custom notifications enabled, as these are more like mentions than the other # custom notifications enabled, as these are more like mentions than the other
# custom settings. # custom settings.
def failed_pipeline def failed_pipeline
bool = super bool = super
bool = events[:failed_pipeline] if bool.nil?
bool.nil? || bool bool.nil? || bool
end end
alias_method :failed_pipeline?, :failed_pipeline alias_method :failed_pipeline?, :failed_pipeline
def event_enabled?(event) def event_enabled?(event)
respond_to?(event) && public_send(event) respond_to?(event) && !!public_send(event)
end
def convert_events
return if events_before_type_cast.nil?
EMAIL_EVENTS.each do |event|
write_attribute(event, public_send(event))
end
write_attribute(:events, nil)
end end
end end
...@@ -17,6 +17,7 @@ class Project < ActiveRecord::Base ...@@ -17,6 +17,7 @@ class Project < ActiveRecord::Base
include ProjectFeaturesCompatibility include ProjectFeaturesCompatibility
include SelectForProjectAuthorization include SelectForProjectAuthorization
include Routable include Routable
include Storage::LegacyProject
extend Gitlab::ConfigHelper extend Gitlab::ConfigHelper
...@@ -43,9 +44,8 @@ class Project < ActiveRecord::Base ...@@ -43,9 +44,8 @@ class Project < ActiveRecord::Base
default_value_for :snippets_enabled, gitlab_config_features.snippets default_value_for :snippets_enabled, gitlab_config_features.snippets
default_value_for :only_allow_merge_if_all_discussions_are_resolved, false default_value_for :only_allow_merge_if_all_discussions_are_resolved, false
after_create :ensure_dir_exist after_create :ensure_storage_path_exist
after_create :create_project_feature, unless: :project_feature after_create :create_project_feature, unless: :project_feature
after_save :ensure_dir_exist, if: :namespace_id_changed?
after_save :update_project_statistics, if: :namespace_id_changed? after_save :update_project_statistics, if: :namespace_id_changed?
# set last_activity_at to the same as created_at # set last_activity_at to the same as created_at
...@@ -67,6 +67,10 @@ class Project < ActiveRecord::Base ...@@ -67,6 +67,10 @@ class Project < ActiveRecord::Base
after_validation :check_pending_delete after_validation :check_pending_delete
# Legacy Storage specific hooks
after_save :ensure_storage_path_exist, if: :namespace_id_changed?
acts_as_taggable acts_as_taggable
attr_accessor :new_default_branch attr_accessor :new_default_branch
...@@ -375,7 +379,7 @@ class Project < ActiveRecord::Base ...@@ -375,7 +379,7 @@ class Project < ActiveRecord::Base
begin begin
Projects::HousekeepingService.new(project).execute Projects::HousekeepingService.new(project).execute
rescue Projects::HousekeepingService::LeaseTaken => e rescue Projects::HousekeepingService::LeaseTaken => e
Rails.logger.info("Could not perform housekeeping for project #{project.path_with_namespace} (#{project.id}): #{e}") Rails.logger.info("Could not perform housekeeping for project #{project.full_path} (#{project.id}): #{e}")
end end
end end
end end
...@@ -476,12 +480,12 @@ class Project < ActiveRecord::Base ...@@ -476,12 +480,12 @@ class Project < ActiveRecord::Base
end end
def repository def repository
@repository ||= Repository.new(path_with_namespace, self) @repository ||= Repository.new(full_path, self, disk_path: disk_path)
end end
def container_registry_url def container_registry_url
if Gitlab.config.registry.enabled if Gitlab.config.registry.enabled
"#{Gitlab.config.registry.host_port}/#{path_with_namespace.downcase}" "#{Gitlab.config.registry.host_port}/#{full_path.downcase}"
end end
end end
...@@ -520,16 +524,16 @@ class Project < ActiveRecord::Base ...@@ -520,16 +524,16 @@ class Project < ActiveRecord::Base
job_id = job_id =
if forked? if forked?
RepositoryForkWorker.perform_async(id, forked_from_project.repository_storage_path, RepositoryForkWorker.perform_async(id, forked_from_project.repository_storage_path,
forked_from_project.path_with_namespace, forked_from_project.full_path,
self.namespace.full_path) self.namespace.full_path)
else else
RepositoryImportWorker.perform_async(self.id) RepositoryImportWorker.perform_async(self.id)
end end
if job_id if job_id
Rails.logger.info "Import job started for #{path_with_namespace} with job ID #{job_id}" Rails.logger.info "Import job started for #{full_path} with job ID #{job_id}"
else else
Rails.logger.error "Import job failed to start for #{path_with_namespace}" Rails.logger.error "Import job failed to start for #{full_path}"
end end
end end
...@@ -690,7 +694,7 @@ class Project < ActiveRecord::Base ...@@ -690,7 +694,7 @@ class Project < ActiveRecord::Base
# `from` argument can be a Namespace or Project. # `from` argument can be a Namespace or Project.
def to_reference(from = nil, full: false) def to_reference(from = nil, full: false)
if full || cross_namespace_reference?(from) if full || cross_namespace_reference?(from)
path_with_namespace full_path
elsif cross_project_reference?(from) elsif cross_project_reference?(from)
path path
end end
...@@ -714,7 +718,7 @@ class Project < ActiveRecord::Base ...@@ -714,7 +718,7 @@ class Project < ActiveRecord::Base
author.ensure_incoming_email_token! author.ensure_incoming_email_token!
Gitlab::IncomingEmail.reply_address( Gitlab::IncomingEmail.reply_address(
"#{path_with_namespace}+#{author.incoming_email_token}") "#{full_path}+#{author.incoming_email_token}")
end end
def build_commit_note(commit) def build_commit_note(commit)
...@@ -941,7 +945,7 @@ class Project < ActiveRecord::Base ...@@ -941,7 +945,7 @@ class Project < ActiveRecord::Base
end end
def url_to_repo def url_to_repo
gitlab_shell.url_to_repo(path_with_namespace) gitlab_shell.url_to_repo(full_path)
end end
def repo_exists? def repo_exists?
...@@ -974,56 +978,6 @@ class Project < ActiveRecord::Base ...@@ -974,56 +978,6 @@ class Project < ActiveRecord::Base
!group !group
end end
def rename_repo
path_was = previous_changes['path'].first
old_path_with_namespace = File.join(namespace.full_path, path_was)
new_path_with_namespace = File.join(namespace.full_path, path)
Rails.logger.error "Attempting to rename #{old_path_with_namespace} -> #{new_path_with_namespace}"
if has_container_registry_tags?
Rails.logger.error "Project #{old_path_with_namespace} cannot be renamed because container registry tags are present!"
# we currently doesn't support renaming repository if it contains images in container registry
raise StandardError.new('Project cannot be renamed, because images are present in its container registry')
end
expire_caches_before_rename(old_path_with_namespace)
if gitlab_shell.mv_repository(repository_storage_path, old_path_with_namespace, new_path_with_namespace)
# If repository moved successfully we need to send update instructions to users.
# However we cannot allow rollback since we moved repository
# So we basically we mute exceptions in next actions
begin
gitlab_shell.mv_repository(repository_storage_path, "#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
send_move_instructions(old_path_with_namespace)
expires_full_path_cache
@old_path_with_namespace = old_path_with_namespace
SystemHooksService.new.execute_hooks_for(self, :rename)
@repository = nil
rescue => e
Rails.logger.error "Exception renaming #{old_path_with_namespace} -> #{new_path_with_namespace}: #{e}"
# Returning false does not rollback after_* transaction but gives
# us information about failing some of tasks
false
end
else
Rails.logger.error "Repository could not be renamed: #{old_path_with_namespace} -> #{new_path_with_namespace}"
# if we cannot move namespace directory we should rollback
# db changes in order to prevent out of sync between db and fs
raise StandardError.new('repository cannot be renamed')
end
Gitlab::AppLogger.info "Project was renamed: #{old_path_with_namespace} -> #{new_path_with_namespace}"
Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.full_path)
Gitlab::PagesTransfer.new.rename_project(path_was, path, namespace.full_path)
end
# Expires various caches before a project is renamed. # Expires various caches before a project is renamed.
def expire_caches_before_rename(old_path) def expire_caches_before_rename(old_path)
repo = Repository.new(old_path, self) repo = Repository.new(old_path, self)
...@@ -1048,7 +1002,7 @@ class Project < ActiveRecord::Base ...@@ -1048,7 +1002,7 @@ class Project < ActiveRecord::Base
git_http_url: http_url_to_repo, git_http_url: http_url_to_repo,
namespace: namespace.name, namespace: namespace.name,
visibility_level: visibility_level, visibility_level: visibility_level,
path_with_namespace: path_with_namespace, path_with_namespace: full_path,
default_branch: default_branch, default_branch: default_branch,
ci_config_path: ci_config_path ci_config_path: ci_config_path
} }
...@@ -1109,19 +1063,6 @@ class Project < ActiveRecord::Base ...@@ -1109,19 +1063,6 @@ class Project < ActiveRecord::Base
merge_requests.where(source_project_id: self.id) merge_requests.where(source_project_id: self.id)
end end
def create_repository(force: false)
# Forked import is handled asynchronously
return if forked? && !force
if gitlab_shell.add_repository(repository_storage_path, path_with_namespace)
repository.after_create
true
else
errors.add(:base, 'Failed to create repository via gitlab-shell')
false
end
end
def ensure_repository def ensure_repository
create_repository(force: true) unless repository_exists? create_repository(force: true) unless repository_exists?
end end
...@@ -1257,7 +1198,7 @@ class Project < ActiveRecord::Base ...@@ -1257,7 +1198,7 @@ class Project < ActiveRecord::Base
end end
def pages_path def pages_path
File.join(Settings.pages.path, path_with_namespace) File.join(Settings.pages.path, disk_path)
end end
def public_pages_path def public_pages_path
...@@ -1265,9 +1206,21 @@ class Project < ActiveRecord::Base ...@@ -1265,9 +1206,21 @@ class Project < ActiveRecord::Base
end end
def remove_private_deploy_keys def remove_private_deploy_keys
deploy_keys.where(public: false).delete_all exclude_keys_linked_to_other_projects = <<-SQL
NOT EXISTS (
SELECT 1
FROM deploy_keys_projects dkp2
WHERE dkp2.deploy_key_id = deploy_keys_projects.deploy_key_id
AND dkp2.project_id != deploy_keys_projects.project_id
)
SQL
deploy_keys.where(public: false)
.where(exclude_keys_linked_to_other_projects)
.delete_all
end end
# TODO: what to do here when not using Legacy Storage? Do we still need to rename and delay removal?
def remove_pages def remove_pages
::Projects::UpdatePagesConfigurationService.new(self).execute ::Projects::UpdatePagesConfigurationService.new(self).execute
...@@ -1315,7 +1268,7 @@ class Project < ActiveRecord::Base ...@@ -1315,7 +1268,7 @@ class Project < ActiveRecord::Base
end end
def export_path def export_path
File.join(Gitlab::ImportExport.storage_path, path_with_namespace) File.join(Gitlab::ImportExport.storage_path, disk_path)
end end
def export_project_path def export_project_path
...@@ -1327,16 +1280,12 @@ class Project < ActiveRecord::Base ...@@ -1327,16 +1280,12 @@ class Project < ActiveRecord::Base
status.zero? status.zero?
end end
def ensure_dir_exist
gitlab_shell.add_namespace(repository_storage_path, namespace.full_path)
end
def predefined_variables def predefined_variables
[ [
{ key: 'CI_PROJECT_ID', value: id.to_s, public: true }, { key: 'CI_PROJECT_ID', value: id.to_s, public: true },
{ key: 'CI_PROJECT_NAME', value: path, public: true }, { key: 'CI_PROJECT_NAME', value: path, public: true },
{ key: 'CI_PROJECT_PATH', value: path_with_namespace, public: true }, { key: 'CI_PROJECT_PATH', value: full_path, public: true },
{ key: 'CI_PROJECT_PATH_SLUG', value: path_with_namespace.parameterize, public: true }, { key: 'CI_PROJECT_PATH_SLUG', value: full_path.parameterize, public: true },
{ key: 'CI_PROJECT_NAMESPACE', value: namespace.full_path, public: true }, { key: 'CI_PROJECT_NAMESPACE', value: namespace.full_path, public: true },
{ key: 'CI_PROJECT_URL', value: web_url, public: true } { key: 'CI_PROJECT_URL', value: web_url, public: true }
] ]
...@@ -1441,6 +1390,7 @@ class Project < ActiveRecord::Base ...@@ -1441,6 +1390,7 @@ class Project < ActiveRecord::Base
alias_method :name_with_namespace, :full_name alias_method :name_with_namespace, :full_name
alias_method :human_name, :full_name alias_method :human_name, :full_name
# @deprecated cannot remove yet because it has an index with its name in elasticsearch
alias_method :path_with_namespace, :full_path alias_method :path_with_namespace, :full_path
private private
...@@ -1495,7 +1445,7 @@ class Project < ActiveRecord::Base ...@@ -1495,7 +1445,7 @@ class Project < ActiveRecord::Base
def pending_delete_twin def pending_delete_twin
return false unless path return false unless path
Project.pending_delete.find_by_full_path(path_with_namespace) Project.pending_delete.find_by_full_path(full_path)
end end
## ##
......
...@@ -35,9 +35,9 @@ class FlowdockService < Service ...@@ -35,9 +35,9 @@ class FlowdockService < Service
data[:after], data[:after],
token: token, token: token,
repo: project.repository.path_to_repo, repo: project.repository.path_to_repo,
repo_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}", repo_url: "#{Gitlab.config.gitlab.url}/#{project.full_path}",
commit_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/%s", commit_url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/commit/%s",
diff_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/compare/%s...%s" diff_url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/compare/%s...%s"
) )
end end
end end
...@@ -140,7 +140,7 @@ class JiraService < IssueTrackerService ...@@ -140,7 +140,7 @@ class JiraService < IssueTrackerService
url: resource_url(user_path(author)) url: resource_url(user_path(author))
}, },
project: { project: {
name: project.path_with_namespace, name: project.full_path,
url: resource_url(namespace_project_path(project.namespace, project)) # rubocop:disable Cop/ProjectPathHelper url: resource_url(namespace_project_path(project.namespace, project)) # rubocop:disable Cop/ProjectPathHelper
}, },
entity: { entity: {
......
class ProjectWiki class ProjectWiki
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
include Storage::LegacyProjectWiki
MARKUPS = { MARKUPS = {
'Markdown' => :markdown, 'Markdown' => :markdown,
...@@ -26,16 +27,19 @@ class ProjectWiki ...@@ -26,16 +27,19 @@ class ProjectWiki
@project.path + '.wiki' @project.path + '.wiki'
end end
def path_with_namespace def full_path
@project.path_with_namespace + ".wiki" @project.full_path + '.wiki'
end end
# @deprecated use full_path when you need it for an URL route or disk_path when you want to point to the filesystem
alias_method :path_with_namespace, :full_path
def web_url def web_url
Gitlab::Routing.url_helpers.project_wiki_url(@project, :home) Gitlab::Routing.url_helpers.project_wiki_url(@project, :home)
end end
def url_to_repo def url_to_repo
gitlab_shell.url_to_repo(path_with_namespace) gitlab_shell.url_to_repo(full_path)
end end
def ssh_url_to_repo def ssh_url_to_repo
...@@ -43,11 +47,11 @@ class ProjectWiki ...@@ -43,11 +47,11 @@ class ProjectWiki
end end
def http_url_to_repo def http_url_to_repo
"#{Gitlab.config.gitlab.url}/#{path_with_namespace}.git" "#{Gitlab.config.gitlab.url}/#{full_path}.git"
end end
def wiki_base_path def wiki_base_path
[Gitlab.config.gitlab.relative_url_root, "/", @project.path_with_namespace, "/wikis"].join('') [Gitlab.config.gitlab.relative_url_root, '/', @project.full_path, '/wikis'].join('')
end end
# Returns the Gollum::Wiki object. # Returns the Gollum::Wiki object.
...@@ -134,7 +138,7 @@ class ProjectWiki ...@@ -134,7 +138,7 @@ class ProjectWiki
end end
def repository def repository
@repository ||= Repository.new(path_with_namespace, @project) @repository ||= Repository.new(full_path, @project, disk_path: disk_path)
end end
def default_branch def default_branch
...@@ -142,7 +146,7 @@ class ProjectWiki ...@@ -142,7 +146,7 @@ class ProjectWiki
end end
def create_repo! def create_repo!
if init_repo(path_with_namespace) if init_repo(disk_path)
wiki = Gollum::Wiki.new(path_to_repo) wiki = Gollum::Wiki.new(path_to_repo)
else else
raise CouldNotCreateWikiError raise CouldNotCreateWikiError
...@@ -162,15 +166,15 @@ class ProjectWiki ...@@ -162,15 +166,15 @@ class ProjectWiki
web_url: web_url, web_url: web_url,
git_ssh_url: ssh_url_to_repo, git_ssh_url: ssh_url_to_repo,
git_http_url: http_url_to_repo, git_http_url: http_url_to_repo,
path_with_namespace: path_with_namespace, path_with_namespace: full_path,
default_branch: default_branch default_branch: default_branch
} }
end end
private private
def init_repo(path_with_namespace) def init_repo(disk_path)
gitlab_shell.add_repository(project.repository_storage_path, path_with_namespace) gitlab_shell.add_repository(project.repository_storage_path, disk_path)
end end
def commit_details(action, message = nil, title = nil) def commit_details(action, message = nil, title = nil)
...@@ -184,7 +188,7 @@ class ProjectWiki ...@@ -184,7 +188,7 @@ class ProjectWiki
end end
def path_to_repo def path_to_repo
@path_to_repo ||= File.join(project.repository_storage_path, "#{path_with_namespace}.git") @path_to_repo ||= File.join(project.repository_storage_path, "#{disk_path}.git")
end end
def update_project_activity def update_project_activity
......
...@@ -4,7 +4,7 @@ class Repository ...@@ -4,7 +4,7 @@ class Repository
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
include RepositoryMirroring include RepositoryMirroring
attr_accessor :path_with_namespace, :project attr_accessor :full_path, :disk_path, :project
delegate :ref_name_for_sha, to: :raw_repository delegate :ref_name_for_sha, to: :raw_repository
...@@ -52,13 +52,14 @@ class Repository ...@@ -52,13 +52,14 @@ class Repository
end end
end end
def initialize(path_with_namespace, project) def initialize(full_path, project, disk_path: nil)
@path_with_namespace = path_with_namespace @full_path = full_path
@disk_path = disk_path || full_path
@project = project @project = project
end end
def raw_repository def raw_repository
return nil unless path_with_namespace return nil unless full_path
@raw_repository ||= initialize_raw_repository @raw_repository ||= initialize_raw_repository
end end
...@@ -66,7 +67,7 @@ class Repository ...@@ -66,7 +67,7 @@ class Repository
# Return absolute path to repository # Return absolute path to repository
def path_to_repo def path_to_repo
@path_to_repo ||= File.expand_path( @path_to_repo ||= File.expand_path(
File.join(repository_storage_path, path_with_namespace + ".git") File.join(repository_storage_path, disk_path + '.git')
) )
end end
...@@ -469,7 +470,7 @@ class Repository ...@@ -469,7 +470,7 @@ class Repository
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/314 # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/314
def exists? def exists?
return false unless path_with_namespace return false unless full_path
Gitlab::GitalyClient.migrate(:repository_exists) do |enabled| Gitlab::GitalyClient.migrate(:repository_exists) do |enabled|
if enabled if enabled
...@@ -1005,7 +1006,7 @@ class Repository ...@@ -1005,7 +1006,7 @@ class Repository
end end
def fetch_remote(remote, forced: false, no_tags: false) def fetch_remote(remote, forced: false, no_tags: false)
gitlab_shell.fetch_remote(repository_storage_path, path_with_namespace, remote, forced: forced, no_tags: no_tags) gitlab_shell.fetch_remote(repository_storage_path, disk_path, remote, forced: forced, no_tags: no_tags)
end end
def fetch_ref(source_path, source_ref, target_ref) def fetch_ref(source_path, source_ref, target_ref)
...@@ -1104,7 +1105,8 @@ class Repository ...@@ -1104,7 +1105,8 @@ class Repository
end end
def cache def cache
@cache ||= RepositoryCache.new(path_with_namespace, @project.id) # TODO: should we use UUIDs here? We could move repositories without clearing this cache
@cache ||= RepositoryCache.new(full_path, @project.id)
end end
def tags_sorted_by_committed_date def tags_sorted_by_committed_date
...@@ -1127,7 +1129,7 @@ class Repository ...@@ -1127,7 +1129,7 @@ class Repository
end end
def repository_event(event, tags = {}) def repository_event(event, tags = {})
Gitlab::Metrics.add_event(event, { path: path_with_namespace }.merge(tags)) Gitlab::Metrics.add_event(event, { path: full_path }.merge(tags))
end end
def create_commit(params = {}) def create_commit(params = {})
...@@ -1141,6 +1143,6 @@ class Repository ...@@ -1141,6 +1143,6 @@ class Repository
end end
def initialize_raw_repository def initialize_raw_repository
Gitlab::Git::Repository.new(project.repository_storage, path_with_namespace + '.git') Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git')
end end
end end
...@@ -44,7 +44,7 @@ class GlobalPolicy < BasePolicy ...@@ -44,7 +44,7 @@ class GlobalPolicy < BasePolicy
prevent :log_in prevent :log_in
end end
rule { admin | ~restricted_public_level }.policy do rule { ~(anonymous & restricted_public_level) }.policy do
enable :read_users_list enable :read_users_list
end end
end end
...@@ -60,7 +60,7 @@ class GitOperationService ...@@ -60,7 +60,7 @@ class GitOperationService
start_branch_name = nil if start_repository.empty_repo? start_branch_name = nil if start_repository.empty_repo?
if start_branch_name && !start_repository.branch_exists?(start_branch_name) if start_branch_name && !start_repository.branch_exists?(start_branch_name)
raise ArgumentError, "Cannot find branch #{start_branch_name} in #{start_repository.path_with_namespace}" raise ArgumentError, "Cannot find branch #{start_branch_name} in #{start_repository.full_path}"
end end
update_branch_with_hooks(branch_name) do update_branch_with_hooks(branch_name) do
......
...@@ -9,7 +9,7 @@ module Projects ...@@ -9,7 +9,7 @@ module Projects
def async_execute def async_execute
project.update_attribute(:pending_delete, true) project.update_attribute(:pending_delete, true)
job_id = ProjectDestroyWorker.perform_async(project.id, current_user.id, params) job_id = ProjectDestroyWorker.perform_async(project.id, current_user.id, params)
Rails.logger.info("User #{current_user.id} scheduled destruction of project #{project.path_with_namespace} with job ID #{job_id}") Rails.logger.info("User #{current_user.id} scheduled destruction of project #{project.full_path} with job ID #{job_id}")
end end
def execute def execute
...@@ -40,7 +40,7 @@ module Projects ...@@ -40,7 +40,7 @@ module Projects
private private
def repo_path def repo_path
project.path_with_namespace project.disk_path
end end
def wiki_path def wiki_path
...@@ -127,7 +127,7 @@ module Projects ...@@ -127,7 +127,7 @@ module Projects
def flush_caches(project) def flush_caches(project)
project.repository.before_delete project.repository.before_delete
Repository.new(wiki_path, project).before_delete Repository.new(wiki_path, project, disk_path: repo_path).before_delete
end end
end end
end end
...@@ -2,7 +2,7 @@ module Projects ...@@ -2,7 +2,7 @@ module Projects
module ImportExport module ImportExport
class ExportService < BaseService class ExportService < BaseService
def execute(_options = {}) def execute(_options = {})
@shared = Gitlab::ImportExport::Shared.new(relative_path: File.join(project.path_with_namespace, 'work')) @shared = Gitlab::ImportExport::Shared.new(relative_path: File.join(project.disk_path, 'work'))
save_all save_all
end end
......
...@@ -11,7 +11,7 @@ module Projects ...@@ -11,7 +11,7 @@ module Projects
success success
rescue => e rescue => e
error("Error importing repository #{project.import_url} into #{project.path_with_namespace} - #{e.message}") error("Error importing repository #{project.import_url} into #{project.full_path} - #{e.message}")
end end
private private
...@@ -51,7 +51,7 @@ module Projects ...@@ -51,7 +51,7 @@ module Projects
end end
def clone_repository def clone_repository
gitlab_shell.import_repository(project.repository_storage_path, project.path_with_namespace, project.import_url) gitlab_shell.import_repository(project.repository_storage_path, project.disk_path, project.import_url)
end end
def fetch_repository def fetch_repository
......
...@@ -34,7 +34,7 @@ module Projects ...@@ -34,7 +34,7 @@ module Projects
private private
def transfer(project) def transfer(project)
@old_path = project.path_with_namespace @old_path = project.full_path
@old_group = project.group @old_group = project.group
@new_path = File.join(@new_namespace.try(:full_path) || '', project.path) @new_path = File.join(@new_namespace.try(:full_path) || '', project.path)
@old_namespace = project.namespace @old_namespace = project.namespace
...@@ -61,11 +61,13 @@ module Projects ...@@ -61,11 +61,13 @@ module Projects
project.send_move_instructions(@old_path) project.send_move_instructions(@old_path)
# Move main repository # Move main repository
# TODO: check storage type and NOOP when not using Legacy
unless move_repo_folder(@old_path, @new_path) unless move_repo_folder(@old_path, @new_path)
raise TransferError.new('Cannot move project') raise TransferError.new('Cannot move project')
end end
# Move wiki repo also if present # Move wiki repo also if present
# TODO: check storage type and NOOP when not using Legacy
move_repo_folder("#{@old_path}.wiki", "#{@new_path}.wiki") move_repo_folder("#{@old_path}.wiki", "#{@new_path}.wiki")
# Move missing group labels to project # Move missing group labels to project
......
...@@ -4,6 +4,9 @@ module QuickActions ...@@ -4,6 +4,9 @@ module QuickActions
attr_reader :issuable attr_reader :issuable
SHRUG = '¯\\_(ツ)_/¯'.freeze
TABLEFLIP = '(╯°□°)╯︵ ┻━┻'.freeze
# Takes a text and interprets the commands that are extracted from it. # Takes a text and interprets the commands that are extracted from it.
# Returns the content without commands, and hash of changes to be applied to a record. # Returns the content without commands, and hash of changes to be applied to a record.
def execute(content, issuable) def execute(content, issuable)
...@@ -14,6 +17,7 @@ module QuickActions ...@@ -14,6 +17,7 @@ module QuickActions
content, commands = extractor.extract_commands(content, context) content, commands = extractor.extract_commands(content, context)
extract_updates(commands, context) extract_updates(commands, context)
[content, @updates] [content, @updates]
end end
...@@ -423,6 +427,18 @@ module QuickActions ...@@ -423,6 +427,18 @@ module QuickActions
@updates[:spend_time] = { duration: :reset, user: current_user } @updates[:spend_time] = { duration: :reset, user: current_user }
end end
desc "Append the comment with #{SHRUG}"
params '<Comment>'
substitution :shrug do |comment|
"#{comment} #{SHRUG}"
end
desc "Append the comment with #{TABLEFLIP}"
params '<Comment>'
substitution :tableflip do |comment|
"#{comment} #{TABLEFLIP}"
end
# This is a dummy command, so that it appears in the autocomplete commands # This is a dummy command, so that it appears in the autocomplete commands
desc 'CC' desc 'CC'
params '@user' params '@user'
......
...@@ -24,7 +24,7 @@ class SystemHooksService ...@@ -24,7 +24,7 @@ class SystemHooksService
key: model.key, key: model.key,
id: model.id id: model.id
) )
if model.user if model.user
data[:username] = model.user.username data[:username] = model.user.username
end end
...@@ -56,7 +56,7 @@ class SystemHooksService ...@@ -56,7 +56,7 @@ class SystemHooksService
when GroupMember when GroupMember
data.merge!(group_member_data(model)) data.merge!(group_member_data(model))
end end
data data
end end
...@@ -79,7 +79,7 @@ class SystemHooksService ...@@ -79,7 +79,7 @@ class SystemHooksService
{ {
name: model.name, name: model.name,
path: model.path, path: model.path,
path_with_namespace: model.path_with_namespace, path_with_namespace: model.full_path,
project_id: model.id, project_id: model.id,
owner_name: owner.name, owner_name: owner.name,
owner_email: owner.respond_to?(:email) ? owner.email : "", owner_email: owner.respond_to?(:email) ? owner.email : "",
...@@ -93,7 +93,7 @@ class SystemHooksService ...@@ -93,7 +93,7 @@ class SystemHooksService
{ {
project_name: project.name, project_name: project.name,
project_path: project.path, project_path: project.path,
project_path_with_namespace: project.path_with_namespace, project_path_with_namespace: project.full_path,
project_id: project.id, project_id: project.id,
user_username: model.user.username, user_username: model.user.username,
user_name: model.user.name, user_name: model.user.name,
......
...@@ -30,7 +30,7 @@ class FileUploader < GitlabUploader ...@@ -30,7 +30,7 @@ class FileUploader < GitlabUploader
# #
# Returns a String without a trailing slash # Returns a String without a trailing slash
def self.dynamic_path_segment(model) def self.dynamic_path_segment(model)
File.join(CarrierWave.root, base_dir, model.path_with_namespace) File.join(CarrierWave.root, base_dir, model.full_path)
end end
attr_accessor :model attr_accessor :model
......
...@@ -70,7 +70,7 @@ ...@@ -70,7 +70,7 @@
%span.badge %span.badge
= storage_counter(project.statistics.storage_size) = storage_counter(project.statistics.storage_size)
%span.pull-right.light %span.pull-right.light
%span.monospace= project.path_with_namespace + ".git" %span.monospace= project.full_path + '.git'
.panel-footer .panel-footer
= paginate @projects, param_name: 'projects_page', theme: 'gitlab' = paginate @projects, param_name: 'projects_page', theme: 'gitlab'
...@@ -88,7 +88,7 @@ ...@@ -88,7 +88,7 @@
%span.badge %span.badge
= storage_counter(project.statistics.storage_size) = storage_counter(project.statistics.storage_size)
%span.pull-right.light %span.pull-right.light
%span.monospace= project.path_with_namespace + ".git" %span.monospace= project.full_path + '.git'
.col-md-6 .col-md-6
- if can?(current_user, :admin_group_member, @group) - if can?(current_user, :admin_group_member, @group)
......
- page_title "UI Development Kit", "Help" - page_title "UI Development Kit", "Help"
- lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed fermentum nisi sapien, non consequat lectus aliquam ultrices. Suspendisse sodales est euismod nunc condimentum, a consectetur diam ornare." - lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed fermentum nisi sapien, non consequat lectus aliquam ultrices. Suspendisse sodales est euismod nunc condimentum, a consectetur diam ornare."
- content_for :page_specific_javascripts do
= webpack_bundle_tag('ui_development_kit')
.gitlab-ui-dev-kit .gitlab-ui-dev-kit
%h1 GitLab UI development kit %h1 GitLab UI development kit
...@@ -407,29 +409,6 @@ ...@@ -407,29 +409,6 @@
.dropdown-content .dropdown-content
.dropdown-loading .dropdown-loading
= icon('spinner spin') = icon('spinner spin')
:javascript
$('#js-project-dropdown').glDropdown({
data: function (term, callback) {
Api.projects(term, { order_by: 'last_activity_at' }, function (data) {
callback(data);
});
},
text: function (project) {
return project.name_with_namespace || project.name;
},
selectable: true,
fieldName: "author_id",
filterable: true,
search: {
fields: ['name_with_namespace']
},
id: function (data) {
return data.id;
},
isSelected: function (data) {
return data.id === 2;
}
})
.example .example
%div %div
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
%td %td
= provider_project_link(provider, project.import_source) = provider_project_link(provider, project.import_source)
%td %td
= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] = link_to project.full_path, [project.namespace.becomes(Namespace), project]
%td.job-status %td.job-status
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span %span
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
job.attr("id", "project_#{@project.id}") job.attr("id", "project_#{@project.id}")
target_field = job.find(".import-target") target_field = job.find(".import-target")
target_field.empty() target_field.empty()
target_field.append('#{link_to @project.path_with_namespace, project_path(@project)}') target_field.append('#{link_to @project.full_path, project_path(@project)}')
$("table.import-jobs tbody").prepend(job) $("table.import-jobs tbody").prepend(job)
job.addClass("active").find(".import-actions").html("<i class='fa fa-spinner fa-spin'></i> started") job.addClass("active").find(".import-actions").html("<i class='fa fa-spinner fa-spin'></i> started")
- else - else
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
%td %td
= link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: '_blank', rel: 'noopener noreferrer' = link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: '_blank', rel: 'noopener noreferrer'
%td %td
= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] = link_to project.full_path, [project.namespace.becomes(Namespace), project]
%td.job-status %td.job-status
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span %span
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
%td %td
= project.import_source = project.import_source
%td %td
= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] = link_to project.full_path, [project.namespace.becomes(Namespace), project]
%td.job-status %td.job-status
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span %span
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
%td %td
= link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank" = link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank"
%td %td
= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] = link_to project.full_path, [project.namespace.becomes(Namespace), project]
%td.job-status %td.job-status
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span %span
......
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
%td %td
= link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank", rel: 'noopener noreferrer' = link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank", rel: 'noopener noreferrer'
%td %td
= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] = link_to project.full_path, [project.namespace.becomes(Namespace), project]
%td.job-status %td.job-status
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span %span
......
-# haml-lint:disable InlineJavaScript
:javascript :javascript
var _gaq = _gaq || []; var _gaq = _gaq || [];
_gaq.push(['_setAccount', '#{extra_config.google_analytics_id}']); _gaq.push(['_setAccount', '#{extra_config.google_analytics_id}']);
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
- noteable_type = @noteable.class if @noteable.present? - noteable_type = @noteable.class if @noteable.present?
- if project - if project
-# haml-lint:disable InlineJavaScript
:javascript :javascript
gl.GfmAutoComplete = gl.GfmAutoComplete || {}; gl.GfmAutoComplete = gl.GfmAutoComplete || {};
gl.GfmAutoComplete.dataSources = { gl.GfmAutoComplete.dataSources = {
......
.page-with-sidebar{ class: "#{('page-with-new-sidebar' if defined?(@new_sidebar) && @new_sidebar)} #{page_gutter_class}" } .page-with-sidebar{ class: page_with_sidebar_class }
- if show_new_nav? - if show_new_nav?
- if defined?(nav) && nav - if defined?(nav) && nav
= render "layouts/nav/#{nav}" = render "layouts/nav/#{nav}"
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
= render "layouts/nav/#{nav}" = render "layouts/nav/#{nav}"
- if content_for?(:sub_nav) - if content_for?(:sub_nav)
= yield :sub_nav = yield :sub_nav
.content-wrapper{ class: "#{(layout_nav_class unless show_new_nav?)}" } .content-wrapper{ class: layout_nav_class }
- if show_new_nav? - if show_new_nav?
.mobile-overlay .mobile-overlay
.alert-wrapper .alert-wrapper
......
<!-- Piwik --> <!-- Piwik -->
-# haml-lint:disable InlineJavaScript
:javascript :javascript
var _paq = _paq || []; var _paq = _paq || [];
_paq.push(['trackPageView']); _paq.push(['trackPageView']);
......
!!! 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}", find_file: find_file_path } } %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
= render 'peek/bar'
- if show_new_nav? - if show_new_nav?
= render "layouts/header/new" = render "layouts/header/new"
- else - else
...@@ -10,5 +11,3 @@ ...@@ -10,5 +11,3 @@
= render 'layouts/page', sidebar: sidebar, nav: nav = render 'layouts/page', sidebar: sidebar, nav: nav
= yield :scripts_body = yield :scripts_body
= render 'peek/bar'
...@@ -91,8 +91,8 @@ ...@@ -91,8 +91,8 @@
= nav_link(controller: :abuse_reports) do = nav_link(controller: :abuse_reports) do
= link_to admin_abuse_reports_path, title: "Abuse Reports" do = link_to admin_abuse_reports_path, title: "Abuse Reports" do
%span %span
Abuse Reports
%span.badge.count= number_with_delimiter(AbuseReport.count(:all)) %span.badge.count= number_with_delimiter(AbuseReport.count(:all))
Abuse Reports
- if akismet_enabled? - if akismet_enabled?
= nav_link(controller: :spam_logs) do = nav_link(controller: :spam_logs) do
......
...@@ -28,9 +28,9 @@ ...@@ -28,9 +28,9 @@
= nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do = nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do
= link_to issues_group_path(@group), title: 'Issues' do = link_to issues_group_path(@group), title: 'Issues' do
%span %span
Issues
- issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
%span.badge.count= number_with_delimiter(issues.count) %span.badge.count= number_with_delimiter(issues.count)
Issues
%ul.sidebar-sub-level-items %ul.sidebar-sub-level-items
= nav_link(path: 'groups#issues', html_options: { class: 'home' }) do = nav_link(path: 'groups#issues', html_options: { class: 'home' }) do
...@@ -51,9 +51,9 @@ ...@@ -51,9 +51,9 @@
= nav_link(path: 'groups#merge_requests') do = nav_link(path: 'groups#merge_requests') do
= link_to merge_requests_group_path(@group), title: 'Merge Requests' do = link_to merge_requests_group_path(@group), title: 'Merge Requests' do
%span %span
Merge Requests
- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute
%span.badge.count= number_with_delimiter(merge_requests.count) %span.badge.count= number_with_delimiter(merge_requests.count)
Merge Requests
= nav_link(path: 'group_members#index') do = nav_link(path: 'group_members#index') do
= link_to group_group_members_path(@group), title: 'Members' do = link_to group_group_members_path(@group), title: 'Members' do
%span %span
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
- content_for :project_javascripts do - content_for :project_javascripts do
- project = @target_project || @project - project = @target_project || @project
- if current_user - if current_user
-# haml-lint:disable InlineJavaScript
:javascript :javascript
window.uploads_path = "#{project_uploads_path(project)}"; window.uploads_path = "#{project_uploads_path(project)}";
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
- if @snippet && current_user - if @snippet && current_user
-# haml-lint:disable InlineJavaScript
:javascript :javascript
window.uploads_path = "#{upload_path('personal_snippet', id: @snippet.id)}"; window.uploads_path = "#{upload_path('personal_snippet', id: @snippet.id)}";
......
%span.current-host
= truncate(view.hostname)
- page_title "Personal Access Tokens" - page_title "Personal Access Tokens"
- @content_class = "limit-container-width" unless fluid_layout - @content_class = "limit-container-width" unless fluid_layout
= render 'profiles/head' = render 'profiles/head'
.row.prepend-top-default .row.prepend-top-default
...@@ -19,7 +20,7 @@ ...@@ -19,7 +20,7 @@
%h5.prepend-top-0 %h5.prepend-top-0
Your New Personal Access Token Your New Personal Access Token
.form-group .form-group
= text_field_tag 'created-personal-access-token', flash[:personal_access_token], readonly: true, class: "form-control", 'aria-describedby' => "created-personal-access-token-help-block" = text_field_tag 'created-personal-access-token', flash[:personal_access_token], readonly: true, class: "form-control js-select-on-focus", 'aria-describedby' => "created-personal-access-token-help-block"
= clipboard_button(text: flash[:personal_access_token], title: "Copy personal access token to clipboard", placement: "left") = clipboard_button(text: flash[:personal_access_token], title: "Copy personal access token to clipboard", placement: "left")
%span#created-personal-access-token-help-block.help-block.text-danger Make sure you save it - you won't be able to access it again. %span#created-personal-access-token-help-block.help-block.text-danger Make sure you save it - you won't be able to access it again.
...@@ -28,8 +29,3 @@ ...@@ -28,8 +29,3 @@
= render "shared/personal_access_tokens_form", path: profile_personal_access_tokens_path, impersonation: false, token: @personal_access_token, scopes: @scopes = render "shared/personal_access_tokens_form", path: profile_personal_access_tokens_path, impersonation: false, token: @personal_access_token, scopes: @scopes
= render "shared/personal_access_tokens_table", impersonation: false, active_tokens: @active_personal_access_tokens, inactive_tokens: @inactive_personal_access_tokens = render "shared/personal_access_tokens_table", impersonation: false, active_tokens: @active_personal_access_tokens, inactive_tokens: @inactive_personal_access_tokens
:javascript
$("#created-personal-access-token").click(function() {
this.select();
});
...@@ -7,97 +7,92 @@ ...@@ -7,97 +7,92 @@
= render 'profiles/head' = render 'profiles/head'
- if inject_u2f_api? - content_for :page_specific_javascripts do
- content_for :page_specific_javascripts do - if inject_u2f_api?
= page_specific_javascript_bundle_tag('u2f') = page_specific_javascript_bundle_tag('u2f')
= page_specific_javascript_bundle_tag('two_factor_auth')
.row.prepend-top-default .js-two-factor-auth{ 'data-two-factor-skippable' => "#{two_factor_skippable?}", 'data-two_factor_skip_url' => skip_profile_two_factor_auth_path }
.col-lg-4 .row.prepend-top-default
%h4.prepend-top-0 .col-lg-4
Register Two-Factor Authentication App %h4.prepend-top-0
%p Register Two-Factor Authentication App
Use an app on your mobile device to enable two-factor authentication (2FA).
.col-lg-8
- if current_user.two_factor_otp_enabled?
= icon "check inverse", base: "circle", class: "text-success", text: "You've already enabled two-factor authentication using mobile authenticator applications. You can disable it from your account settings page."
- else
%p %p
Download the Google Authenticator application from App Store or Google Play Store and scan this code. Use an app on your mobile device to enable two-factor authentication (2FA).
More information is available in the #{link_to('documentation', help_page_path('profile/two_factor_authentication'))}. .col-lg-8
.row.append-bottom-10 - if current_user.two_factor_otp_enabled?
.col-md-4 = icon "check inverse", base: "circle", class: "text-success", text: "You've already enabled two-factor authentication using mobile authenticator applications. You can disable it from your account settings page."
= raw @qr_code - else
.col-md-8 %p
.account-well Download the Google Authenticator application from App Store or Google Play Store and scan this code.
%p.prepend-top-0.append-bottom-0 More information is available in the #{link_to('documentation', help_page_path('profile/two_factor_authentication'))}.
Can't scan the code? .row.append-bottom-10
%p.prepend-top-0.append-bottom-0 .col-md-4
To add the entry manually, provide the following details to the application on your phone. = raw @qr_code
%p.prepend-top-0.append-bottom-0 .col-md-8
Account: .account-well
= @account_string %p.prepend-top-0.append-bottom-0
%p.prepend-top-0.append-bottom-0 Can't scan the code?
Key: %p.prepend-top-0.append-bottom-0
= current_user.otp_secret.scan(/.{4}/).join(' ') To add the entry manually, provide the following details to the application on your phone.
%p.two-factor-new-manual-content %p.prepend-top-0.append-bottom-0
Time based: Yes Account:
= form_tag profile_two_factor_auth_path, method: :post do |f| = @account_string
- if @error %p.prepend-top-0.append-bottom-0
.alert.alert-danger Key:
= @error = current_user.otp_secret.scan(/.{4}/).join(' ')
.form-group %p.two-factor-new-manual-content
= label_tag :pin_code, nil, class: "label-light" Time based: Yes
= text_field_tag :pin_code, nil, class: "form-control", required: true = form_tag profile_two_factor_auth_path, method: :post do |f|
.prepend-top-default - if @error
= submit_tag 'Register with two-factor app', class: 'btn btn-success' .alert.alert-danger
= @error
.form-group
= label_tag :pin_code, nil, class: "label-light"
= text_field_tag :pin_code, nil, class: "form-control", required: true
.prepend-top-default
= submit_tag 'Register with two-factor app', class: 'btn btn-success'
%hr %hr
.row.prepend-top-default .row.prepend-top-default
.col-lg-4
.col-lg-4 %h4.prepend-top-0
%h4.prepend-top-0 Register Universal Two-Factor (U2F) Device
Register Universal Two-Factor (U2F) Device %p
%p Use a hardware device to add the second factor of authentication.
Use a hardware device to add the second factor of authentication. %p
%p As U2F devices are only supported by a few browsers, we require that you set up a
As U2F devices are only supported by a few browsers, we require that you set up a two-factor authentication app before a U2F device. That way you'll always be able to
two-factor authentication app before a U2F device. That way you'll always be able to log in - even when you're using an unsupported browser.
log in - even when you're using an unsupported browser. .col-lg-8
.col-lg-8 - if @u2f_registration.errors.present?
- if @u2f_registration.errors.present? = form_errors(@u2f_registration)
= form_errors(@u2f_registration) = render "u2f/register"
= render "u2f/register"
%hr %hr
%h5 U2F Devices (#{@u2f_registrations.length}) %h5 U2F Devices (#{@u2f_registrations.length})
- if @u2f_registrations.present? - if @u2f_registrations.present?
.table-responsive .table-responsive
%table.table.table-bordered.u2f-registrations %table.table.table-bordered.u2f-registrations
%colgroup %colgroup
%col{ width: "50%" } %col{ width: "50%" }
%col{ width: "30%" } %col{ width: "30%" }
%col{ width: "20%" } %col{ width: "20%" }
%thead %thead
%tr
%th Name
%th Registered On
%th
%tbody
- @u2f_registrations.each do |registration|
%tr %tr
%td= registration.name.presence || "<no name set>" %th Name
%td= registration.created_at.to_date.to_s(:medium) %th Registered On
%td= link_to "Delete", profile_u2f_registration_path(registration), method: :delete, class: "btn btn-danger pull-right", data: { confirm: "Are you sure you want to delete this device? This action cannot be undone." } %th
%tbody
- else - @u2f_registrations.each do |registration|
.settings-message.text-center %tr
You don't have any U2F devices registered yet. %td= registration.name.presence || "<no name set>"
%td= registration.created_at.to_date.to_s(:medium)
%td= link_to "Delete", profile_u2f_registration_path(registration), method: :delete, class: "btn btn-danger pull-right", data: { confirm: "Are you sure you want to delete this device? This action cannot be undone." }
- if two_factor_skippable? - else
:javascript .settings-message.text-center
var button = "<a class='btn btn-xs btn-warning pull-right' data-method='patch' href='#{skip_profile_two_factor_auth_path}'>Configure it later</a>"; You don't have any U2F devices registered yet.
$(".flash-alert").append(button);
...@@ -8,9 +8,3 @@ ...@@ -8,9 +8,3 @@
.content_list.project-activity{ :"data-href" => activity_project_path(@project) } .content_list.project-activity{ :"data-href" => activity_project_path(@project) }
= spinner = spinner
:javascript
var activity = new gl.Activities();
$(document).on('page:restore', function (event) {
activity.reloadActivities()
})
...@@ -20,6 +20,3 @@ ...@@ -20,6 +20,3 @@
- unless can?(current_user, :push_code, @project) - unless can?(current_user, :push_code, @project)
.inline.prepend-left-10 .inline.prepend-left-10
= commit_in_fork_help = commit_in_fork_help
:javascript
new NewCommitForm($('.js-create-dir-form'))
...@@ -13,6 +13,3 @@ ...@@ -13,6 +13,3 @@
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
= button_tag 'Delete file', class: 'btn btn-remove btn-remove-file' = button_tag 'Delete file', class: 'btn btn-remove btn-remove-file'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
:javascript
new NewCommitForm($('.js-delete-blob-form'))
...@@ -28,8 +28,4 @@ ...@@ -28,8 +28,4 @@
.form-actions .form-actions
= button_tag 'Create branch', class: 'btn btn-create', tabindex: 3 = button_tag 'Create branch', class: 'btn btn-create', tabindex: 3
= link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel' = link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel'
%script#availableRefs{ type: "application/json" }= @project.repository.ref_names.to_json.html_safe
:javascript
var availableRefs = #{@project.repository.ref_names.to_json};
new NewBranchForm($('.js-create-branch-form'), availableRefs)
.page-content-header .page-content-header.js-commit-box{ 'data-commit-path' => branches_project_commit_path(@project, @commit.id) }
.header-main-content .header-main-content
= render partial: 'signature', object: @commit.signature = render partial: 'signature', object: @commit.signature
%strong %strong
...@@ -79,6 +79,3 @@ ...@@ -79,6 +79,3 @@
= render 'shared/mini_pipeline_graph', pipeline: last_pipeline, klass: 'js-commit-pipeline-graph' = render 'shared/mini_pipeline_graph', pipeline: last_pipeline, klass: 'js-commit-pipeline-graph'
in in
= time_interval_in_words last_pipeline.duration = time_interval_in_words last_pipeline.duration
:javascript
$(".commit-info.branches").load("#{branches_project_commit_path(@project, @commit.id)}");
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
- notes = commit.notes - notes = commit.notes
- note_count = notes.user.count - note_count = notes.user.count
- cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count, @path.presence, current_controller?(:commits)] - cache_key = [project.full_path, commit.id, current_application_settings, note_count, @path.presence, current_controller?(:commits)]
- cache_key.push(commit.status(ref)) if commit.status(ref) - cache_key.push(commit.status(ref)) if commit.status(ref)
= cache(cache_key, expires_in: 1.day) do = cache(cache_key, expires_in: 1.day) do
......
...@@ -11,34 +11,32 @@ ...@@ -11,34 +11,32 @@
= content_for :sub_nav do = content_for :sub_nav do
= render "head" = render "head"
%div{ class: container_class } .js-project-commits-show{ 'data-commits-limit' => @limit }
.tree-holder %div{ class: container_class }
.nav-block .tree-holder
.tree-ref-container .nav-block
.tree-ref-holder .tree-ref-container
= render 'shared/ref_switcher', destination: 'commits' .tree-ref-holder
= render 'shared/ref_switcher', destination: 'commits'
%ul.breadcrumb.repo-breadcrumb
= commits_breadcrumbs
.tree-controls.hidden-xs.hidden-sm
- if @merge_request.present?
.control
= link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'btn'
- elsif create_mr_button?(@repository.root_ref, @ref)
.control
= link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success'
%ul.breadcrumb.repo-breadcrumb
= commits_breadcrumbs
.tree-controls.hidden-xs.hidden-sm
- if @merge_request.present?
.control .control
= link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'btn' = form_tag(project_commits_path(@project, @id), method: :get, class: 'commits-search-form', data: { 'signatures-path' => namespace_project_signatures_path }) do
- elsif create_mr_button?(@repository.root_ref, @ref) = search_field_tag :search, params[:search], { placeholder: _('Filter by commit message'), id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false }
.control .control
= link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' = link_to project_commits_path(@project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do
= icon("rss")
.control %div{ id: dom_id(@project) }
= form_tag(project_commits_path(@project, @id), method: :get, class: 'commits-search-form', data: { 'signatures-path' => namespace_project_signatures_path }) do %ol#commits-list.list-unstyled.content_list
= search_field_tag :search, params[:search], { placeholder: _('Filter by commit message'), id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false } = render 'commits', project: @project, ref: @ref
.control = spinner
= link_to project_commits_path(@project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do
= icon("rss")
%div{ id: dom_id(@project) }
%ol#commits-list.list-unstyled.content_list
= render 'commits', project: @project, ref: @ref
= spinner
:javascript
CommitsList.init(#{@limit});
- page_title "Find File", @ref - page_title "Find File", @ref
= render "projects/commits/head" = render "projects/commits/head"
.file-finder-holder.tree-holder.clearfix .file-finder-holder.tree-holder.clearfix.js-file-finder{ 'data-file-find-url': "#{escape_javascript(project_files_path(@project, @ref, @options.merge(format: :json)))}", 'data-find-tree-url': escape_javascript(project_tree_path(@project, @ref)), 'data-blob-url-template': escape_javascript(project_blob_path(@project, @id || @commit.id)) }
.nav-block .nav-block
.tree-ref-holder .tree-ref-holder
= render 'shared/ref_switcher', destination: 'find_file', path: @path = render 'shared/ref_switcher', destination: 'find_file', path: @path
...@@ -17,11 +17,3 @@ ...@@ -17,11 +17,3 @@
%table.table.files-slider{ class: "table_#{@hex_path} tree-table table-striped" } %table.table.files-slider{ class: "table_#{@hex_path} tree-table table-striped" }
%tbody %tbody
= spinner nil, true = spinner nil, true
:javascript
var projectFindFile = new ProjectFindFile($(".file-finder-holder"), {
url: "#{escape_javascript(project_files_path(@project, @ref, @options.merge(format: :json)))}",
treeUrl: "#{escape_javascript(project_tree_path(@project, @ref))}",
blobUrlTemplate: "#{escape_javascript(project_blob_path(@project, @id || @commit.id))}"
});
new ShortcutsFindFile(projectFindFile);
...@@ -3,8 +3,9 @@ ...@@ -3,8 +3,9 @@
- if show_new_nav? - if show_new_nav?
- add_to_breadcrumbs("Repository", project_tree_path(@project)) - add_to_breadcrumbs("Repository", project_tree_path(@project))
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3') = webpack_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('graphs') = webpack_bundle_tag('graphs')
= webpack_bundle_tag('graphs_charts')
= render "projects/commits/head" = render "projects/commits/head"
.repo-charts{ class: container_class } .repo-charts{ class: container_class }
...@@ -75,55 +76,10 @@ ...@@ -75,55 +76,10 @@
Commits per day hour (UTC) Commits per day hour (UTC)
%canvas#hour-chart %canvas#hour-chart
:javascript %script#projectChartData{ type: "application/json" }
var responsiveChart = function (selector, data) { - projectChartData = {};
var options = { "scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2, maintainAspectRatio: false }; - projectChartData['hour'] = { 'keys' => @commits_per_time.keys, 'values' => @commits_per_time.values }
// get selector by context - projectChartData['weekDays'] = { 'keys' => @commits_per_week_days.keys, 'values' => @commits_per_week_days.values }
var ctx = selector.get(0).getContext("2d"); - projectChartData['month'] = { 'keys' => @commits_per_month.keys, 'values' => @commits_per_month.values }
// pointing parent container to make chart.js inherit its width - projectChartData['languages'] = @languages
var container = $(selector).parent(); = projectChartData.to_json.html_safe
var generateChart = function() {
selector.attr('width', $(container).width());
if (window.innerWidth < 768) {
// Scale fonts if window width lower than 768px (iPad portrait)
options.scaleFontSize = 8
}
return new Chart(ctx).Bar(data, options);
};
// enabling auto-resizing
$(window).resize(generateChart);
return generateChart();
};
var chartData = function (keys, values) {
var data = {
labels : keys,
datasets : [{
fillColor : "rgba(220,220,220,0.5)",
strokeColor : "rgba(220,220,220,1)",
barStrokeWidth: 1,
barValueSpacing: 1,
barDatasetSpacing: 1,
data : values
}]
};
return data;
};
var hourData = chartData(#{@commits_per_time.keys.to_json}, #{@commits_per_time.values.to_json});
responsiveChart($('#hour-chart'), hourData);
var dayData = chartData(#{@commits_per_week_days.keys.to_json}, #{@commits_per_week_days.values.to_json});
responsiveChart($('#weekday-chart'), dayData);
var monthData = chartData(#{@commits_per_month.keys.to_json}, #{@commits_per_month.values.to_json});
responsiveChart($('#month-chart'), monthData);
var data = #{@languages.to_json};
var ctx = $("#languages-chart").get(0).getContext("2d");
var options = {
scaleOverlay: true,
responsive: true,
maintainAspectRatio: false
}
var myPieChart = new Chart(ctx).Pie(data, options);
- @no_container = true - @no_container = true
- page_title "Contributors" - page_title "Contributors"
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3') = webpack_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('graphs') = webpack_bundle_tag('graphs')
= webpack_bundle_tag('graphs_show')
- if show_new_nav? - if show_new_nav?
- add_to_breadcrumbs("Repository", project_tree_path(@project)) - add_to_breadcrumbs("Repository", project_tree_path(@project))
= render 'projects/commits/head' = render 'projects/commits/head'
%div{ class: container_class } .js-graphs-show{ class: container_class, 'data-project-graph-path': project_graph_path(@project, current_ref, format: :json) }
.sub-header-block .sub-header-block
.tree-ref-holder .tree-ref-holder
= render 'shared/ref_switcher', destination: 'graphs' = render 'shared/ref_switcher', destination: 'graphs'
...@@ -33,24 +34,3 @@ ...@@ -33,24 +34,3 @@
#contributors-master #contributors-master
#contributors.clearfix #contributors.clearfix
%ol.contributors-list.clearfix %ol.contributors-list.clearfix
:javascript
$.ajax({
type: "GET",
url: "#{project_graph_path(@project, current_ref, format: :json)}",
dataType: "json",
success: function (data) {
var graph = new ContributorsStatGraph();
graph.init(data);
$("#brush_change").change(function(){
graph.change_date_header();
graph.redraw_authors();
});
$(".stat-graph").fadeIn();
$(".loading-graph").hide();
}
});
...@@ -10,5 +10,3 @@ ...@@ -10,5 +10,3 @@
- if @project.external_import? - if @project.external_import?
%p.monospace git clone --bare #{@project.safe_import_url} %p.monospace git clone --bare #{@project.safe_import_url}
%p Please wait while we import the repository for you. Refresh at will. %p Please wait while we import the repository for you. Refresh at will.
:javascript
new ProjectImport();
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
Suggestions: Suggestions:
%code= 'gitlab' %code= 'gitlab'
%code= @project.path # Path contains no spaces, but dashes %code= @project.path # Path contains no spaces, but dashes
%code= @project.path_with_namespace %code= @project.full_path
%p %p
Reserved: Reserved:
= link_to 'https://docs.mattermost.com/help/messaging/executing-commands.html#built-in-commands', target: '__blank' do = link_to 'https://docs.mattermost.com/help/messaging/executing-commands.html#built-in-commands', target: '__blank' do
......
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
- projects = target_projects(@project) - projects = target_projects(@project)
.merge-request-select.dropdown .merge-request-select.dropdown
= f.hidden_field :target_project_id = f.hidden_field :target_project_id
= dropdown_toggle f.object.target_project.path_with_namespace, { toggle: "dropdown", field_name: "#{f.object_name}[target_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-target-project" } = dropdown_toggle f.object.target_project.full_path, { toggle: "dropdown", field_name: "#{f.object_name}[target_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-target-project" }
.dropdown-menu.dropdown-menu-selectable.dropdown-target-project .dropdown-menu.dropdown-menu-selectable.dropdown-target-project
= dropdown_title("Select target project") = dropdown_title("Select target project")
= dropdown_filter("Search projects") = dropdown_filter("Search projects")
......
...@@ -2,4 +2,4 @@ ...@@ -2,4 +2,4 @@
- projects.each do |project| - projects.each do |project|
%li %li
%a{ href: "#", class: "#{('is-active' if selected == project.id)}", data: { id: project.id } } %a{ href: "#", class: "#{('is-active' if selected == project.id)}", data: { id: project.id } }
= project.path_with_namespace = project.full_path
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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