Commit 29fe5d4d authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'ce-to-ee-2017-07-28' into 'master'

CE upstream - Friday

Closes gitaly#408, gitaly#411, and #1827

See merge request !2554
parents 51fe933c 2ede81d2
...@@ -102,6 +102,7 @@ stages: ...@@ -102,6 +102,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
...@@ -228,6 +229,7 @@ setup-test-env: ...@@ -228,6 +229,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:
...@@ -473,6 +475,7 @@ karma: ...@@ -473,6 +475,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+%)/'
......
7.5
\ No newline at end of file
...@@ -15,7 +15,7 @@ gem 'default_value_for', '~> 3.0.0' ...@@ -15,7 +15,7 @@ gem 'default_value_for', '~> 3.0.0'
gem 'mysql2', '~> 0.4.5', group: :mysql gem 'mysql2', '~> 0.4.5', group: :mysql
gem 'pg', '~> 0.18.2', group: :postgres gem 'pg', '~> 0.18.2', group: :postgres
gem 'rugged', '~> 0.25.1.1' gem 'rugged', '~> 0.26.0'
gem 'grape-route-helpers', '~> 2.0.0' gem 'grape-route-helpers', '~> 2.0.0'
gem 'faraday', '~> 0.12' gem 'faraday', '~> 0.12'
...@@ -61,6 +61,9 @@ gem 'validates_hostname', '~> 1.0.6' ...@@ -61,6 +61,9 @@ gem 'validates_hostname', '~> 1.0.6'
# Browser detection # Browser detection
gem 'browser', '~> 2.2' gem 'browser', '~> 2.2'
# GPG
gem 'gpgme'
# LDAP Auth # LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes # GitLab fork with several improvements to original library. For full list of changes
# see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master # see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master
...@@ -298,7 +301,7 @@ group :metrics do ...@@ -298,7 +301,7 @@ group :metrics do
gem 'influxdb', '~> 0.2', require: false gem 'influxdb', '~> 0.2', require: false
# Prometheus # Prometheus
gem 'prometheus-client-mmap', '~>0.7.0.beta9' gem 'prometheus-client-mmap', '~>0.7.0.beta11'
gem 'raindrops', '~> 0.18' gem 'raindrops', '~> 0.18'
end end
...@@ -403,7 +406,7 @@ gem 'sys-filesystem', '~> 1.1.6' ...@@ -403,7 +406,7 @@ gem 'sys-filesystem', '~> 1.1.6'
gem 'net-ntp' gem 'net-ntp'
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly', '~> 0.19.0' gem 'gitaly', '~> 0.21.0'
gem 'toml-rb', '~> 0.3.15', require: false gem 'toml-rb', '~> 0.3.15', require: false
......
...@@ -293,7 +293,7 @@ GEM ...@@ -293,7 +293,7 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gherkin-ruby (0.3.2) gherkin-ruby (0.3.2)
gitaly (0.19.0) gitaly (0.21.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.0) grpc (~> 1.0)
github-linguist (4.7.6) github-linguist (4.7.6)
...@@ -357,6 +357,8 @@ GEM ...@@ -357,6 +357,8 @@ GEM
multi_json (~> 1.11) multi_json (~> 1.11)
os (~> 0.9) os (~> 0.9)
signet (~> 0.7) signet (~> 0.7)
gpgme (2.0.13)
mini_portile2 (~> 2.1)
grape (0.19.2) grape (0.19.2)
activesupport activesupport
builder builder
...@@ -624,7 +626,7 @@ GEM ...@@ -624,7 +626,7 @@ GEM
premailer-rails (1.9.7) premailer-rails (1.9.7)
actionmailer (>= 3, < 6) actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9) premailer (~> 1.7, >= 1.7.9)
prometheus-client-mmap (0.7.0.beta10) prometheus-client-mmap (0.7.0.beta11)
mmap2 (~> 2.2, >= 2.2.7) mmap2 (~> 2.2, >= 2.2.7)
pry (0.10.4) pry (0.10.4)
coderay (~> 1.1.0) coderay (~> 1.1.0)
...@@ -778,7 +780,7 @@ GEM ...@@ -778,7 +780,7 @@ GEM
rubyzip (1.2.1) rubyzip (1.2.1)
rufus-scheduler (3.4.0) rufus-scheduler (3.4.0)
et-orbi (~> 1.0) et-orbi (~> 1.0)
rugged (0.25.1.1) rugged (0.26.0)
safe_yaml (1.0.4) safe_yaml (1.0.4)
sanitize (2.1.0) sanitize (2.1.0)
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
...@@ -1008,7 +1010,7 @@ DEPENDENCIES ...@@ -1008,7 +1010,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0) gettext_i18n_rails_js (~> 1.2.0)
gitaly (~> 0.19.0) gitaly (~> 0.21.0)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
...@@ -1018,6 +1020,7 @@ DEPENDENCIES ...@@ -1018,6 +1020,7 @@ DEPENDENCIES
gollum-rugged_adapter (~> 0.4.4) gollum-rugged_adapter (~> 0.4.4)
gon (~> 6.1.0) gon (~> 6.1.0)
google-api-client (~> 0.8.6) google-api-client (~> 0.8.6)
gpgme
grape (~> 0.19.2) grape (~> 0.19.2)
grape-entity (~> 0.6.0) grape-entity (~> 0.6.0)
grape-route-helpers (~> 2.0.0) grape-route-helpers (~> 2.0.0)
...@@ -1084,7 +1087,7 @@ DEPENDENCIES ...@@ -1084,7 +1087,7 @@ DEPENDENCIES
pg (~> 0.18.2) pg (~> 0.18.2)
poltergeist (~> 1.9.0) poltergeist (~> 1.9.0)
premailer-rails (~> 1.9.7) premailer-rails (~> 1.9.7)
prometheus-client-mmap (~> 0.7.0.beta9) prometheus-client-mmap (~> 0.7.0.beta11)
pry-byebug (~> 3.4.1) pry-byebug (~> 3.4.1)
pry-rails (~> 0.3.4) pry-rails (~> 0.3.4)
rack-attack (~> 4.4.1) rack-attack (~> 4.4.1)
...@@ -1118,7 +1121,7 @@ DEPENDENCIES ...@@ -1118,7 +1121,7 @@ DEPENDENCIES
ruby-prof (~> 0.16.2) ruby-prof (~> 0.16.2)
ruby_parser (~> 3.8) ruby_parser (~> 3.8)
rufus-scheduler (~> 3.4) rufus-scheduler (~> 3.4)
rugged (~> 0.25.1.1) rugged (~> 0.26.0)
sanitize (~> 2.0) sanitize (~> 2.0)
sass-rails (~> 5.0.6) sass-rails (~> 5.0.6)
scss_lint (~> 0.54.0) scss_lint (~> 0.54.0)
......
...@@ -8,6 +8,7 @@ import 'bootstrap-sass/assets/javascripts/bootstrap/modal'; ...@@ -8,6 +8,7 @@ import 'bootstrap-sass/assets/javascripts/bootstrap/modal';
import 'bootstrap-sass/assets/javascripts/bootstrap/tab'; import 'bootstrap-sass/assets/javascripts/bootstrap/tab';
import 'bootstrap-sass/assets/javascripts/bootstrap/transition'; import 'bootstrap-sass/assets/javascripts/bootstrap/transition';
import 'bootstrap-sass/assets/javascripts/bootstrap/tooltip'; import 'bootstrap-sass/assets/javascripts/bootstrap/tooltip';
import 'bootstrap-sass/assets/javascripts/bootstrap/popover';
// custom jQuery functions // custom jQuery functions
$.fn.extend({ $.fn.extend({
......
...@@ -23,6 +23,8 @@ ...@@ -23,6 +23,8 @@
/* global NamespaceSelects */ /* global NamespaceSelects */
/* global Project */ /* global Project */
/* global ProjectAvatar */ /* global ProjectAvatar */
/* global MergeRequest */
/* global Compare */
/* global CompareAutocomplete */ /* global CompareAutocomplete */
/* global PathLocks */ /* global PathLocks */
/* global ProjectFindFile */ /* global ProjectFindFile */
...@@ -72,6 +74,7 @@ import initSettingsPanels from './settings_panels'; ...@@ -72,6 +74,7 @@ import initSettingsPanels from './settings_panels';
import initExperimentalFlags from './experimental_flags'; import initExperimentalFlags from './experimental_flags';
import OAuthRememberMe from './oauth_remember_me'; import OAuthRememberMe from './oauth_remember_me';
import PerformanceBar from './performance_bar'; import PerformanceBar from './performance_bar';
import GpgBadges from './gpg_badges';
import initNotes from './init_notes'; import initNotes from './init_notes';
import initLegacyFilters from './init_legacy_filters'; import initLegacyFilters from './init_legacy_filters';
import initIssuableSidebar from './init_issuable_sidebar'; import initIssuableSidebar from './init_issuable_sidebar';
...@@ -255,6 +258,19 @@ import AuditLogs from './audit_logs'; ...@@ -255,6 +258,19 @@ import AuditLogs from './audit_logs';
new gl.IssuableTemplateSelectors(); new gl.IssuableTemplateSelectors();
break; break;
case 'projects:merge_requests:creations:new': case 'projects:merge_requests:creations:new':
const mrNewCompareNode = document.querySelector('.js-merge-request-new-compare');
if (mrNewCompareNode) {
new Compare({
targetProjectUrl: mrNewCompareNode.dataset.targetProjectUrl,
sourceBranchUrl: mrNewCompareNode.dataset.sourceBranchUrl,
targetBranchUrl: mrNewCompareNode.dataset.targetBranchUrl,
});
} else {
const mrNewSubmitNode = document.querySelector('.js-merge-request-new-submit');
new MergeRequest({
action: mrNewSubmitNode.dataset.mrSubmitAction,
});
}
case 'projects:merge_requests:creations:diffs': case 'projects:merge_requests:creations:diffs':
case 'projects:merge_requests:edit': case 'projects:merge_requests:edit':
new gl.Diff(); new gl.Diff();
...@@ -294,6 +310,10 @@ import AuditLogs from './audit_logs'; ...@@ -294,6 +310,10 @@ import AuditLogs from './audit_logs';
new gl.Diff(); new gl.Diff();
shortcut_handler = new ShortcutsIssuable(true); shortcut_handler = new ShortcutsIssuable(true);
new ZenMode(); new ZenMode();
const mrShowNode = document.querySelector('.merge-request');
window.mergeRequest = new MergeRequest({
action: mrShowNode.dataset.mrAction,
});
initIssuableSidebar(); initIssuableSidebar();
initNotes(); initNotes();
break; break;
...@@ -325,6 +345,7 @@ import AuditLogs from './audit_logs'; ...@@ -325,6 +345,7 @@ import AuditLogs from './audit_logs';
CommitsList.init(document.querySelector('.js-project-commits-show').dataset.commitsLimit); CommitsList.init(document.querySelector('.js-project-commits-show').dataset.commitsLimit);
new gl.Activities(); new gl.Activities();
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
GpgBadges.fetch();
break; break;
case 'projects:edit': case 'projects:edit':
new UsersSelect(); new UsersSelect();
...@@ -616,6 +637,13 @@ import AuditLogs from './audit_logs'; ...@@ -616,6 +637,13 @@ import AuditLogs from './audit_logs';
case 'repository': case 'repository':
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
} }
break;
case 'users':
const action = path[1];
import(/* webpackChunkName: 'user_profile' */ './users')
.then(user => user.default(action))
.catch(() => {});
break;
} }
// If we haven't installed a custom shortcut handler, install the default one // If we haven't installed a custom shortcut handler, install the default one
if (!shortcut_handler) { if (!shortcut_handler) {
......
export default class GpgBadges {
static fetch() {
const form = $('.commits-search-form');
$.get({
url: form.data('signatures-path'),
data: form.serialize(),
}).done((response) => {
const badges = $('.js-loading-gpg-badge');
response.signatures.forEach((signature) => {
badges.filter(`[data-commit-sha="${signature.commit_sha}"]`).replaceWith(signature.html);
});
});
}
}
...@@ -3,10 +3,10 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -3,10 +3,10 @@ document.addEventListener('DOMContentLoaded', () => {
modal: true, modal: true,
show: false, show: false,
}); });
$('.how_to_merge_link').bind('click', () => { $('.how_to_merge_link').on('click', () => {
modal.show(); modal.show();
}); });
$('.modal-header .close').bind('click', () => { $('.modal-header .close').on('click', () => {
modal.hide(); modal.hide();
}); });
}); });
...@@ -102,7 +102,7 @@ export default class IntegrationSettingsForm { ...@@ -102,7 +102,7 @@ export default class IntegrationSettingsForm {
}) })
.done((res) => { .done((res) => {
if (res.error) { if (res.error) {
new Flash(res.message, null, null, { new Flash(`${res.message} ${res.service_response}`, null, null, {
title: 'Save anyway', title: 'Save anyway',
clickHandler: (e) => { clickHandler: (e) => {
e.preventDefault(); e.preventDefault();
......
...@@ -166,6 +166,8 @@ document.addEventListener('beforeunload', function () { ...@@ -166,6 +166,8 @@ document.addEventListener('beforeunload', function () {
$(document).off('scroll'); $(document).off('scroll');
// Close any open tooltips // Close any open tooltips
$('.has-tooltip, [data-toggle="tooltip"]').tooltip('destroy'); $('.has-tooltip, [data-toggle="tooltip"]').tooltip('destroy');
// Close any open popover
$('[data-toggle="popover"]').popover('destroy');
}); });
window.addEventListener('hashchange', gl.utils.handleLocationHash); window.addEventListener('hashchange', gl.utils.handleLocationHash);
...@@ -254,6 +256,11 @@ $(function () { ...@@ -254,6 +256,11 @@ $(function () {
return $(el).data('placement') || 'bottom'; return $(el).data('placement') || 'bottom';
} }
}); });
// Initialize popovers
$body.popover({
selector: '[data-toggle="popover"]',
trigger: 'focus'
});
$('.trigger-submit').on('change', function () { $('.trigger-submit').on('change', function () {
return $(this).parents('form').submit(); return $(this).parents('form').submit();
// Form submitter // Form submitter
......
import ActivityCalendar from './activity_calendar'; import Cookies from 'js-cookie';
import User from './user'; import UserTabs from './user_tabs';
// use legacy exports until embedded javascript is refactored export default function initUserProfile(action) {
window.Calendar = ActivityCalendar; // place profile avatars to top
window.gl = window.gl || {}; $('.profile-groups-avatars').tooltip({
window.gl.User = User; placement: 'top',
});
// eslint-disable-next-line no-new
new UserTabs({ parentEl: '.user-profile', action });
// hide project limit message
$('.hide-project-limit-message').on('click', (e) => {
e.preventDefault();
Cookies.set('hide_project_limit_message', 'false');
$(this).parents('.project-limit-message').remove();
});
}
/* eslint-disable class-methods-use-this */
import Cookies from 'js-cookie';
import UserTabs from './user_tabs';
export default class User {
constructor({ action }) {
this.action = action;
this.placeProfileAvatarsToTop();
this.initTabs();
this.hideProjectLimitMessage();
}
placeProfileAvatarsToTop() {
$('.profile-groups-avatars').tooltip({
placement: 'top',
});
}
initTabs() {
return new UserTabs({
parentEl: '.user-profile',
action: this.action,
});
}
hideProjectLimitMessage() {
$('.hide-project-limit-message').on('click', (e) => {
e.preventDefault();
Cookies.set('hide_project_limit_message', 'false');
$(this).parents('.project-limit-message').remove();
});
}
}
/* eslint-disable max-len, space-before-function-paren, no-underscore-dangle, consistent-return, comma-dangle, no-unused-vars, dot-notation, no-new, no-return-assign, camelcase, no-param-reassign, class-methods-use-this */ import ActivityCalendar from './activity_calendar';
/* /**
UserTabs * UserTabs
*
Handles persisting and restoring the current tab selection and lazily-loading * Handles persisting and restoring the current tab selection and lazily-loading
content on the Users#show page. * content on the Users#show page.
*
### Example Markup * ### Example Markup
*
<ul class="nav-links"> * <ul class="nav-links">
<li class="activity-tab active"> * <li class="activity-tab active">
<a data-action="activity" data-target="#activity" data-toggle="tab" href="/u/username"> * <a data-action="activity" data-target="#activity" data-toggle="tab" href="/u/username">
Activity * Activity
</a> * </a>
</li> * </li>
<li class="groups-tab"> * <li class="groups-tab">
<a data-action="groups" data-target="#groups" data-toggle="tab" href="/u/username/groups"> * <a data-action="groups" data-target="#groups" data-toggle="tab" href="/u/username/groups">
Groups * Groups
</a> * </a>
</li> * </li>
<li class="contributed-tab"> * <li class="contributed-tab">
<a data-action="contributed" data-target="#contributed" data-toggle="tab" href="/u/username/contributed"> * ...
Contributed projects * </li>
</a> * <li class="projects-tab">
</li> * ...
<li class="projects-tab"> * </li>
<a data-action="projects" data-target="#projects" data-toggle="tab" href="/u/username/projects"> * <li class="snippets-tab">
Personal projects * ...
</a> * </li>
</li> * </ul>
<li class="snippets-tab"> *
<a data-action="snippets" data-target="#snippets" data-toggle="tab" href="/u/username/snippets"> * <div class="tab-content">
</a> * <div class="tab-pane" id="activity">
</li> * Activity Content
</ul> * </div>
* <div class="tab-pane" id="groups">
<div class="tab-content"> * Groups Content
<div class="tab-pane" id="activity"> * </div>
Activity Content * <div class="tab-pane" id="contributed">
</div> * Contributed projects content
<div class="tab-pane" id="groups"> * </div>
Groups Content * <div class="tab-pane" id="projects">
</div> * Projects content
<div class="tab-pane" id="contributed"> * </div>
Contributed projects content * <div class="tab-pane" id="snippets">
</div> * Snippets content
<div class="tab-pane" id="projects"> * </div>
Projects content * </div>
</div> *
<div class="tab-pane" id="snippets"> * <div class="loading-status">
Snippets content * <div class="loading">
* Loading Animation
* </div>
* </div>
*/
const CALENDAR_TEMPLATE = `
<div class="clearfix calendar">
<div class="js-contrib-calendar"></div>
<div class="calendar-hint">
Summary of issues, merge requests, push events, and comments
</div> </div>
</div> </div>
`;
<div class="loading-status">
<div class="loading">
Loading Animation
</div>
</div>
*/
export default class UserTabs { export default class UserTabs {
constructor ({ defaultAction, action, parentEl }) { constructor({ defaultAction, action, parentEl }) {
this.loaded = {}; this.loaded = {};
this.defaultAction = defaultAction || 'activity'; this.defaultAction = defaultAction || 'activity';
this.action = action || this.defaultAction; this.action = action || this.defaultAction;
this.$parentEl = $(parentEl) || $(document); this.$parentEl = $(parentEl) || $(document);
this._location = window.location; this.windowLocation = window.location;
this.$parentEl.find('.nav-links a') this.$parentEl.find('.nav-links a')
.each((i, navLink) => { .each((i, navLink) => {
this.loaded[$(navLink).attr('data-action')] = false; this.loaded[$(navLink).attr('data-action')] = false;
...@@ -82,12 +86,10 @@ export default class UserTabs { ...@@ -82,12 +86,10 @@ export default class UserTabs {
} }
bindEvents() { bindEvents() {
this.changeProjectsPageWrapper = this.changeProjectsPage.bind(this); this.$parentEl
.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]')
this.$parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]') .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event))
.on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event)); .on('click', '.gl-pagination a', event => this.changeProjectsPage(event));
this.$parentEl.on('click', '.gl-pagination a', this.changeProjectsPageWrapper);
} }
changeProjectsPage(e) { changeProjectsPage(e) {
...@@ -122,7 +124,7 @@ export default class UserTabs { ...@@ -122,7 +124,7 @@ export default class UserTabs {
const loadableActions = ['groups', 'contributed', 'projects', 'snippets']; const loadableActions = ['groups', 'contributed', 'projects', 'snippets'];
if (loadableActions.indexOf(action) > -1) { if (loadableActions.indexOf(action) > -1) {
return this.loadTab(action, endpoint); this.loadTab(action, endpoint);
} }
} }
...@@ -131,25 +133,38 @@ export default class UserTabs { ...@@ -131,25 +133,38 @@ export default class UserTabs {
beforeSend: () => this.toggleLoading(true), beforeSend: () => this.toggleLoading(true),
complete: () => this.toggleLoading(false), complete: () => this.toggleLoading(false),
dataType: 'json', dataType: 'json',
type: 'GET',
url: endpoint, url: endpoint,
success: (data) => { success: (data) => {
const tabSelector = `div#${action}`; const tabSelector = `div#${action}`;
this.$parentEl.find(tabSelector).html(data.html); this.$parentEl.find(tabSelector).html(data.html);
this.loaded[action] = true; this.loaded[action] = true;
return gl.utils.localTimeAgo($('.js-timeago', tabSelector)); gl.utils.localTimeAgo($('.js-timeago', tabSelector));
} },
}); });
} }
loadActivities() { loadActivities() {
if (this.loaded['activity']) { if (this.loaded.activity) {
return; return;
} }
const $calendarWrap = this.$parentEl.find('.user-calendar'); const $calendarWrap = this.$parentEl.find('.user-calendar');
$calendarWrap.load($calendarWrap.data('href')); const calendarPath = $calendarWrap.data('calendarPath');
const calendarActivitiesPath = $calendarWrap.data('calendarActivitiesPath');
$.ajax({
dataType: 'json',
url: calendarPath,
success: (activityData) => {
$calendarWrap.html(CALENDAR_TEMPLATE);
// eslint-disable-next-line no-new
new ActivityCalendar('.js-contrib-calendar', activityData, calendarActivitiesPath);
},
});
// eslint-disable-next-line no-new
new gl.Activities(); new gl.Activities();
return this.loaded['activity'] = true; this.loaded.activity = true;
} }
toggleLoading(status) { toggleLoading(status) {
...@@ -158,13 +173,13 @@ export default class UserTabs { ...@@ -158,13 +173,13 @@ export default class UserTabs {
} }
setCurrentAction(source) { setCurrentAction(source) {
let new_state = source; let newState = source;
new_state = new_state.replace(/\/+$/, ''); newState = newState.replace(/\/+$/, '');
new_state += this._location.search + this._location.hash; newState += this.windowLocation.search + this.windowLocation.hash;
history.replaceState({ history.replaceState({
url: new_state url: newState,
}, document.title, new_state); }, document.title, newState);
return new_state; return newState;
} }
getCurrentAction() { getCurrentAction() {
......
...@@ -148,7 +148,6 @@ ...@@ -148,7 +148,6 @@
padding: 5px 8px; padding: 5px 8px;
color: $gl-text-color; color: $gl-text-color;
line-height: initial; line-height: initial;
text-overflow: ellipsis;
border-radius: 2px; border-radius: 2px;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
...@@ -203,11 +202,6 @@ ...@@ -203,11 +202,6 @@
border-radius: $border-radius-base; border-radius: $border-radius-base;
box-shadow: 0 2px 4px $dropdown-shadow-color; box-shadow: 0 2px 4px $dropdown-shadow-color;
@media (max-width: $screen-sm-min) {
width: 100%;
min-width: 180px;
}
&.dropdown-open-left { &.dropdown-open-left {
right: 0; right: 0;
left: auto; left: auto;
...@@ -289,6 +283,11 @@ ...@@ -289,6 +283,11 @@
padding: 5px 8px; padding: 5px 8px;
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
} }
.badge + span:not(.badge) {
// Expects up to 3 digits on the badge
margin-right: 40px;
}
} }
.droplab-dropdown { .droplab-dropdown {
...@@ -373,7 +372,6 @@ ...@@ -373,7 +372,6 @@
.dropdown-menu, .dropdown-menu,
.dropdown-menu-nav { .dropdown-menu-nav {
max-width: 280px; max-width: 280px;
width: auto;
} }
} }
......
...@@ -393,7 +393,8 @@ ...@@ -393,7 +393,8 @@
@media (max-width: $screen-xs) { @media (max-width: $screen-xs) {
.filter-dropdown-container { .filter-dropdown-container {
.dropdown-toggle, .dropdown-toggle,
.dropdown { .dropdown,
.dropdown-menu {
width: 100%; width: 100%;
} }
......
...@@ -118,3 +118,29 @@ ...@@ -118,3 +118,29 @@
@content; @content;
} }
} }
/*
* Mixin for status badges, as used for pipelines and commit signatures
*/
@mixin status-color($color-light, $color-main, $color-dark) {
color: $color-main;
border-color: $color-main;
&:not(span):hover {
background-color: $color-light;
color: $color-dark;
border-color: $color-dark;
svg {
fill: $color-dark;
}
}
svg {
fill: $color-main;
}
}
@mixin green-status-color {
@include status-color($green-50, $green-500, $green-700);
}
...@@ -287,3 +287,63 @@ ...@@ -287,3 +287,63 @@
color: $gl-text-color; color: $gl-text-color;
} }
} }
.gpg-status-box {
&.valid {
@include green-status-color;
}
&.invalid {
@include status-color($gray-dark, $gray, $common-gray-dark);
border-color: $common-gray-light;
}
}
.gpg-popover-status {
display: flex;
align-items: center;
font-weight: normal;
line-height: 1.5;
}
.gpg-popover-icon {
// same margin as .s32.avatar
margin-right: $btn-side-margin;
&.valid {
svg {
border: 1px solid $brand-success;
fill: $brand-success;
}
}
&.invalid {
svg {
border: 1px solid $common-gray-light;
fill: $common-gray-light;
}
}
svg {
width: 32px;
height: 32px;
border-radius: 50%;
vertical-align: middle;
}
}
.gpg-popover-user-link {
display: flex;
align-items: center;
margin-bottom: $gl-padding / 2;
text-decoration: none;
color: $gl-text-color;
}
.commit .gpg-popover-help-link {
display: block;
color: $link-color;
}
...@@ -211,6 +211,10 @@ ...@@ -211,6 +211,10 @@
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
} }
&.affix-top .issuable-sidebar {
height: 100%;
}
&.right-sidebar-expanded { &.right-sidebar-expanded {
width: $gutter_width; width: $gutter_width;
......
...@@ -391,3 +391,26 @@ table.u2f-registrations { ...@@ -391,3 +391,26 @@ table.u2f-registrations {
margin-bottom: 0; margin-bottom: 0;
} }
} }
.gpg-email-badge {
display: inline;
margin-right: $gl-padding / 2;
.gpg-email-badge-email {
display: inline;
margin-right: $gl-padding / 4;
}
.label-verification-status {
border-width: 1px;
border-style: solid;
&.verified {
@include green-status-color;
}
&.unverified {
@include status-color($gray-dark, $gray, $common-gray-dark);
}
}
}
...@@ -727,6 +727,7 @@ a.allowed-to-push { ...@@ -727,6 +727,7 @@ a.allowed-to-push {
background-color: transparent; background-color: transparent;
border: 0; border: 0;
text-align: left; text-align: left;
text-overflow: ellipsis;
} }
.protected-branches-list, .protected-branches-list,
......
@mixin status-color($color-light, $color-main, $color-dark) {
color: $color-main;
border-color: $color-main;
&:not(span):hover {
background-color: $color-light;
color: $color-dark;
border-color: $color-dark;
svg {
fill: $color-dark;
}
}
svg {
fill: $color-main;
}
}
.ci-status { .ci-status {
padding: 2px 7px 4px; padding: 2px 7px 4px;
border: 1px solid $gray-darker; border: 1px solid $gray-darker;
...@@ -41,7 +22,7 @@ ...@@ -41,7 +22,7 @@
} }
&.ci-success { &.ci-success {
@include status-color($green-50, $green-500, $green-700); @include green-status-color;
} }
&.ci-canceled, &.ci-canceled,
......
...@@ -78,12 +78,13 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -78,12 +78,13 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file] params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file]
params.require(:application_setting).permit( params.require(:application_setting).permit(
permitted_application_setting_attributes visible_application_setting_attributes
) )
end end
def permitted_application_setting_attributes def visible_application_setting_attributes
ApplicationSettingsHelper.visible_attributes + [ ApplicationSettingsHelper.visible_attributes + [
:domain_blacklist_file,
disabled_oauth_sign_in_sources: [], disabled_oauth_sign_in_sources: [],
import_sources: [], import_sources: [],
repository_storages: [], repository_storages: [],
......
...@@ -70,6 +70,16 @@ class ApplicationController < ActionController::Base ...@@ -70,6 +70,16 @@ class ApplicationController < ActionController::Base
protected protected
def append_info_to_payload(payload)
super
payload[:remote_ip] = request.remote_ip
if current_user.present?
payload[:user_id] = current_user.id
payload[:username] = current_user.username
end
end
# This filter handles both private tokens and personal access tokens # This filter handles both private tokens and personal access tokens
def authenticate_user_from_private_token! def authenticate_user_from_private_token!
token = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence token = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence
......
module EE module EE
module Admin module Admin
module ApplicationSettingsController module ApplicationSettingsController
def permitted_application_setting_attributes def visible_application_setting_attributes
attrs = super attrs = super
if License.feature_available?(:repository_mirrors) if License.feature_available?(:repository_mirrors)
......
class Profiles::GpgKeysController < Profiles::ApplicationController
before_action :set_gpg_key, only: [:destroy, :revoke]
def index
@gpg_keys = current_user.gpg_keys
@gpg_key = GpgKey.new
end
def create
@gpg_key = current_user.gpg_keys.new(gpg_key_params)
if @gpg_key.save
redirect_to profile_gpg_keys_path
else
@gpg_keys = current_user.gpg_keys.select(&:persisted?)
render :index
end
end
def destroy
@gpg_key.destroy
respond_to do |format|
format.html { redirect_to profile_gpg_keys_url, status: 302 }
format.js { head :ok }
end
end
def revoke
@gpg_key.revoke
respond_to do |format|
format.html { redirect_to profile_gpg_keys_url, status: 302 }
format.js { head :ok }
end
end
private
def gpg_key_params
params.require(:gpg_key).permit(:key)
end
def set_gpg_key
@gpg_key = current_user.gpg_keys.find(params[:id])
end
end
...@@ -6,18 +6,9 @@ class Projects::CommitsController < Projects::ApplicationController ...@@ -6,18 +6,9 @@ class Projects::CommitsController < Projects::ApplicationController
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :assign_ref_vars before_action :assign_ref_vars
before_action :authorize_download_code! before_action :authorize_download_code!
before_action :set_commits
def show def show
@limit, @offset = (params[:limit] || 40).to_i, (params[:offset] || 0).to_i
search = params[:search]
@commits =
if search.present?
@repository.find_commits_by_message(search, @ref, @path, @limit, @offset)
else
@repository.commits(@ref, path: @path, limit: @limit, offset: @offset)
end
@note_counts = project.notes.where(commit_id: @commits.map(&:id)) @note_counts = project.notes.where(commit_id: @commits.map(&:id))
.group(:commit_id).count .group(:commit_id).count
...@@ -37,4 +28,33 @@ class Projects::CommitsController < Projects::ApplicationController ...@@ -37,4 +28,33 @@ class Projects::CommitsController < Projects::ApplicationController
end end
end end
end end
def signatures
respond_to do |format|
format.json do
render json: {
signatures: @commits.select(&:has_signature?).map do |commit|
{
commit_sha: commit.sha,
html: view_to_html_string('projects/commit/_signature', signature: commit.signature)
}
end
}
end
end
end
private
def set_commits
@limit, @offset = (params[:limit] || 40).to_i, (params[:offset] || 0).to_i
search = params[:search]
@commits =
if search.present?
@repository.find_commits_by_message(search, @ref, @path, @limit, @offset)
else
@repository.commits(@ref, path: @path, limit: @limit, offset: @offset)
end
end
end end
...@@ -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
......
...@@ -58,6 +58,9 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -58,6 +58,9 @@ class Projects::WikisController < Projects::ApplicationController
else else
render 'edit' render 'edit'
end end
rescue WikiPage::PageChangedError
@conflict = true
render 'edit'
end end
def create def create
...@@ -125,6 +128,6 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -125,6 +128,6 @@ class Projects::WikisController < Projects::ApplicationController
end end
def wiki_params def wiki_params
params.require(:wiki).permit(:title, :content, :format, :message) params.require(:wiki).permit(:title, :content, :format, :message, :last_commit_sha)
end end
end end
...@@ -73,10 +73,7 @@ class UsersController < ApplicationController ...@@ -73,10 +73,7 @@ class UsersController < ApplicationController
end end
def calendar def calendar
calendar = contributions_calendar render json: contributions_calendar.activity_dates
@activity_dates = calendar.activity_dates
render 'calendar', layout: false
end end
def calendar_activities def calendar_activities
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
# group_id: integer # group_id: integer
# project_id: integer # project_id: integer
# milestone_title: string # milestone_title: string
# author_id: integer
# assignee_id: integer # assignee_id: integer
# search: string # search: string
# label_name: string # label_name: string
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
# group_id: integer # group_id: integer
# project_id: integer # project_id: integer
# milestone_title: string # milestone_title: string
# author_id: integer
# assignee_id: integer # assignee_id: integer
# search: string # search: string
# label_name: string # label_name: string
......
...@@ -113,6 +113,10 @@ module CommitsHelper ...@@ -113,6 +113,10 @@ module CommitsHelper
commit_action_link('cherry-pick', commit, continue_to_path, btn_class: btn_class, has_tooltip: has_tooltip) commit_action_link('cherry-pick', commit, continue_to_path, btn_class: btn_class, has_tooltip: has_tooltip)
end end
def commit_signature_badge_classes(additional_classes)
%w(btn status-box gpg-status-box) + Array(additional_classes)
end
protected protected
# Private: Returns a link to a person. If the person has a matching user and # Private: Returns a link to a person. If the person has a matching user and
......
...@@ -14,7 +14,7 @@ module Emails ...@@ -14,7 +14,7 @@ module Emails
end end
def new_ssh_key_email(key_id) def new_ssh_key_email(key_id)
@key = Key.find_by_id(key_id) @key = Key.find_by(id: key_id)
return unless @key return unless @key
...@@ -22,5 +22,15 @@ module Emails ...@@ -22,5 +22,15 @@ module Emails
@target_url = user_url(@user) @target_url = user_url(@user)
mail(to: @user.notification_email, subject: subject("SSH key was added to your account")) mail(to: @user.notification_email, subject: subject("SSH key was added to your account"))
end end
def new_gpg_key_email(gpg_key_id)
@gpg_key = GpgKey.find_by(id: gpg_key_id)
return unless @gpg_key
@current_user = @user = @gpg_key.user
@target_url = user_url(@user)
mail(to: @user.notification_email, subject: subject("GPG key was added to your account"))
end
end end
end end
...@@ -362,7 +362,9 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -362,7 +362,9 @@ class ApplicationSetting < ActiveRecord::Base
Array(read_attribute(:repository_storages)) Array(read_attribute(:repository_storages))
end end
# DEPRECATED
# repository_storage is still required in the API. Remove in 9.0 # repository_storage is still required in the API. Remove in 9.0
# Still used in API v3
def repository_storage def repository_storage
repository_storages.first repository_storages.first
end end
......
...@@ -3,4 +3,13 @@ class ChatTeam < ActiveRecord::Base ...@@ -3,4 +3,13 @@ class ChatTeam < ActiveRecord::Base
validates :namespace, uniqueness: true validates :namespace, uniqueness: true
belongs_to :namespace belongs_to :namespace
def remove_mattermost_team(current_user)
Mattermost::Team.new(current_user).destroy(team_id: team_id)
rescue Mattermost::ClientError => e
# Either the group is not found, or the user doesn't have the proper
# access on the mattermost instance. In the first case, we're done either way
# in the latter case, we can't recover by retrying, so we just log what happened
Rails.logger.error("Mattermost team deletion failed: #{e}")
end
end end
...@@ -234,6 +234,14 @@ class Commit ...@@ -234,6 +234,14 @@ class Commit
@statuses[ref] = pipelines.latest_status(ref) @statuses[ref] = pipelines.latest_status(ref)
end end
def signature
return @signature if defined?(@signature)
@signature = gpg_commit.signature
end
delegate :has_signature?, to: :gpg_commit
def revert_branch_name def revert_branch_name
"revert-#{short_id}" "revert-#{short_id}"
end end
...@@ -382,4 +390,8 @@ class Commit ...@@ -382,4 +390,8 @@ class Commit
def merged_merge_request_no_cache(user) def merged_merge_request_no_cache(user)
MergeRequestsFinder.new(user, project_id: project.id).find_by(merge_commit_sha: id) if merge_commit? MergeRequestsFinder.new(user, project_id: project.id).find_by(merge_commit_sha: id) if merge_commit?
end end
def gpg_commit
@gpg_commit ||= Gitlab::Gpg::Commit.new(self)
end
end end
class GpgKey < ActiveRecord::Base
KEY_PREFIX = '-----BEGIN PGP PUBLIC KEY BLOCK-----'.freeze
KEY_SUFFIX = '-----END PGP PUBLIC KEY BLOCK-----'.freeze
include ShaAttribute
sha_attribute :primary_keyid
sha_attribute :fingerprint
belongs_to :user
has_many :gpg_signatures
validates :user, presence: true
validates :key,
presence: true,
uniqueness: true,
format: {
with: /\A#{KEY_PREFIX}((?!#{KEY_PREFIX})(?!#{KEY_SUFFIX}).)+#{KEY_SUFFIX}\Z/m,
message: "is invalid. A valid public GPG key begins with '#{KEY_PREFIX}' and ends with '#{KEY_SUFFIX}'"
}
validates :fingerprint,
presence: true,
uniqueness: true,
# only validate when the `key` is valid, as we don't want the user to show
# the error about the fingerprint
unless: -> { errors.has_key?(:key) }
validates :primary_keyid,
presence: true,
uniqueness: true,
# only validate when the `key` is valid, as we don't want the user to show
# the error about the fingerprint
unless: -> { errors.has_key?(:key) }
before_validation :extract_fingerprint, :extract_primary_keyid
after_commit :update_invalid_gpg_signatures, on: :create
after_commit :notify_user, on: :create
def primary_keyid
super&.upcase
end
def fingerprint
super&.upcase
end
def key=(value)
super(value&.strip)
end
def user_infos
@user_infos ||= Gitlab::Gpg.user_infos_from_key(key)
end
def verified_user_infos
user_infos.select do |user_info|
user_info[:email] == user.email
end
end
def emails_with_verified_status
user_infos.map do |user_info|
[
user_info[:email],
user_info[:email] == user.email
]
end.to_h
end
def verified?
emails_with_verified_status.any? { |_email, verified| verified }
end
def update_invalid_gpg_signatures
InvalidGpgSignatureUpdateWorker.perform_async(self.id)
end
def revoke
GpgSignature.where(gpg_key: self, valid_signature: true).update_all(
gpg_key_id: nil,
valid_signature: false,
updated_at: Time.zone.now
)
destroy
end
private
def extract_fingerprint
# we can assume that the result only contains one item as the validation
# only allows one key
self.fingerprint = Gitlab::Gpg.fingerprints_from_key(key).first
end
def extract_primary_keyid
# we can assume that the result only contains one item as the validation
# only allows one key
self.primary_keyid = Gitlab::Gpg.primary_keyids_from_key(key).first
end
def notify_user
NotificationService.new.new_gpg_key(self)
end
end
class GpgSignature < ActiveRecord::Base
include ShaAttribute
sha_attribute :commit_sha
sha_attribute :gpg_key_primary_keyid
belongs_to :project
belongs_to :gpg_key
validates :commit_sha, presence: true
validates :project_id, presence: true
validates :gpg_key_primary_keyid, presence: true
def gpg_key_primary_keyid
super&.upcase
end
def commit
project.commit(commit_sha)
end
end
...@@ -236,10 +236,21 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -236,10 +236,21 @@ class MergeRequestDiff < ActiveRecord::Base
def create_merge_request_diff_files(diffs) def create_merge_request_diff_files(diffs)
rows = diffs.map.with_index do |diff, index| rows = diffs.map.with_index do |diff, index|
diff.to_hash.merge( diff_hash = diff.to_hash.merge(
binary: false,
merge_request_diff_id: self.id, merge_request_diff_id: self.id,
relative_order: index relative_order: index
) )
# Compatibility with old diffs created with Psych.
diff_hash.tap do |hash|
diff_text = hash[:diff]
if diff_text.encoding == Encoding::BINARY && !diff_text.ascii_only?
hash[:binary] = true
hash[:diff] = [diff_text].pack('m0')
end
end
end end
Gitlab::Database.bulk_insert('merge_request_diff_files', rows) Gitlab::Database.bulk_insert('merge_request_diff_files', rows)
...@@ -268,9 +279,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -268,9 +279,7 @@ class MergeRequestDiff < ActiveRecord::Base
st_diffs st_diffs
end end
elsif merge_request_diff_files.present? elsif merge_request_diff_files.present?
merge_request_diff_files merge_request_diff_files.map(&:to_hash)
.as_json(only: Gitlab::Git::Diff::SERIALIZE_KEYS)
.map(&:with_indifferent_access)
end end
end end
......
...@@ -8,4 +8,14 @@ class MergeRequestDiffFile < ActiveRecord::Base ...@@ -8,4 +8,14 @@ class MergeRequestDiffFile < ActiveRecord::Base
encode_utf8(diff) if diff.respond_to?(:encoding) encode_utf8(diff) if diff.respond_to?(:encoding)
end end
def diff
binary? ? super.unpack('m0').first : super
end
def to_hash
keys = Gitlab::Git::Diff::SERIALIZE_KEYS - [:diff]
as_json(only: keys).merge(diff: diff).with_indifferent_access
end
end end
...@@ -160,7 +160,10 @@ class JiraService < IssueTrackerService ...@@ -160,7 +160,10 @@ class JiraService < IssueTrackerService
def test(_) def test(_)
result = test_settings result = test_settings
{ success: result.present?, result: result } success = result.present?
result = @error if @error && !success
{ success: success, result: result }
end end
# JIRA does not need test data. # JIRA does not need test data.
...@@ -288,7 +291,8 @@ class JiraService < IssueTrackerService ...@@ -288,7 +291,8 @@ class JiraService < IssueTrackerService
yield yield
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED, URI::InvalidURIError, JIRA::HTTPError, OpenSSL::SSL::SSLError => e rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED, URI::InvalidURIError, JIRA::HTTPError, OpenSSL::SSL::SSLError => e
Rails.logger.info "#{self.class.name} Send message ERROR: #{client_url} - #{e.message}" @error = e.message
Rails.logger.info "#{self.class.name} Send message ERROR: #{client_url} - #{@error}"
nil nil
end end
......
...@@ -465,10 +465,6 @@ class Repository ...@@ -465,10 +465,6 @@ class Repository
nil nil
end end
def blob_by_oid(oid)
Gitlab::Git::Blob.raw(self, oid)
end
def root_ref def root_ref
if raw_repository if raw_repository
raw_repository.root_ref raw_repository.root_ref
......
...@@ -79,6 +79,7 @@ class User < ActiveRecord::Base ...@@ -79,6 +79,7 @@ class User < ActiveRecord::Base
where(type.not_eq('DeployKey').or(type.eq(nil))) where(type.not_eq('DeployKey').or(type.eq(nil)))
end, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent end, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :deploy_keys, -> { where(type: 'DeployKey') }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :deploy_keys, -> { where(type: 'DeployKey') }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :gpg_keys
has_many :emails, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :emails, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :personal_access_tokens, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :personal_access_tokens, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
...@@ -160,6 +161,7 @@ class User < ActiveRecord::Base ...@@ -160,6 +161,7 @@ class User < ActiveRecord::Base
before_save :ensure_authentication_token, :ensure_incoming_email_token before_save :ensure_authentication_token, :ensure_incoming_email_token
before_save :ensure_user_rights_and_limits, if: :external_changed? before_save :ensure_user_rights_and_limits, if: :external_changed?
after_save :ensure_namespace_correct after_save :ensure_namespace_correct
after_commit :update_invalid_gpg_signatures, on: :update, if: -> { previous_changes.key?('email') }
after_initialize :set_projects_limit after_initialize :set_projects_limit
after_destroy :post_destroy_hook after_destroy :post_destroy_hook
...@@ -531,6 +533,10 @@ class User < ActiveRecord::Base ...@@ -531,6 +533,10 @@ class User < ActiveRecord::Base
end end
end end
def update_invalid_gpg_signatures
gpg_keys.each(&:update_invalid_gpg_signatures)
end
# Returns the groups a user has access to # Returns the groups a user has access to
def authorized_groups def authorized_groups
union = Gitlab::SQL::Union union = Gitlab::SQL::Union
......
class WikiPage class WikiPage
PageChangedError = Class.new(StandardError)
include ActiveModel::Validations include ActiveModel::Validations
include ActiveModel::Conversion include ActiveModel::Conversion
include StaticModel include StaticModel
...@@ -136,6 +138,10 @@ class WikiPage ...@@ -136,6 +138,10 @@ class WikiPage
versions.first versions.first
end end
def last_commit_sha
commit&.sha
end
# Returns the Date that this latest version was # Returns the Date that this latest version was
# created on. # created on.
def created_at def created_at
...@@ -186,13 +192,18 @@ class WikiPage ...@@ -186,13 +192,18 @@ class WikiPage
# format - Optional symbol representing the content format. # format - Optional symbol representing the content format.
# See ProjectWiki::MARKUPS Hash for available formats. # See ProjectWiki::MARKUPS Hash for available formats.
# message - Optional commit message to set on the new version. # message - Optional commit message to set on the new version.
# last_commit_sha - Optional last commit sha to validate the page unchanged.
# #
# Returns the String SHA1 of the newly created page # Returns the String SHA1 of the newly created page
# or False if the save was unsuccessful. # or False if the save was unsuccessful.
def update(new_content = "", format = :markdown, message = nil) def update(new_content, format: :markdown, message: nil, last_commit_sha: nil)
@attributes[:content] = new_content @attributes[:content] = new_content
@attributes[:format] = format @attributes[:format] = format
if last_commit_sha && last_commit_sha != self.last_commit_sha
raise PageChangedError.new("You are attempting to update a page that has changed since you started editing it.")
end
save :update_page, @page, content, format, message save :update_page, @page, content, format, message
end end
......
...@@ -61,6 +61,8 @@ class GitPushService < BaseService ...@@ -61,6 +61,8 @@ class GitPushService < BaseService
update_remote_mirrors update_remote_mirrors
update_caches update_caches
update_signatures
end end
def update_gitattributes def update_gitattributes
...@@ -85,6 +87,12 @@ class GitPushService < BaseService ...@@ -85,6 +87,12 @@ class GitPushService < BaseService
ProjectCacheWorker.perform_async(@project.id, types, [:commit_count, :repository_size]) ProjectCacheWorker.perform_async(@project.id, types, [:commit_count, :repository_size])
end end
def update_signatures
@push_commits.each do |commit|
CreateGpgSignatureWorker.perform_async(commit.sha, @project.id)
end
end
# Schedules processing of commit messages. # Schedules processing of commit messages.
def process_commit_messages def process_commit_messages
default = is_default_branch? default = is_default_branch?
......
...@@ -21,6 +21,8 @@ module Groups ...@@ -21,6 +21,8 @@ module Groups
DestroyService.new(group, current_user).execute DestroyService.new(group, current_user).execute
end end
group.chat_team&.remove_mattermost_team(current_user)
group.really_destroy! group.really_destroy!
end end
end end
......
...@@ -19,6 +19,16 @@ class NotificationService ...@@ -19,6 +19,16 @@ class NotificationService
end end
end end
# Always notify the user about gpg key added
#
# This is a security email so it will be sent even if the user user disabled
# notifications
def new_gpg_key(gpg_key)
if gpg_key.user
mailer.new_gpg_key_email(gpg_key.id).deliver_later
end
end
# Always notify user about email added to profile # Always notify user about email added to profile
def new_email(email) def new_email(email)
if email.user if email.user
......
...@@ -138,7 +138,11 @@ module Projects ...@@ -138,7 +138,11 @@ module Projects
end end
def max_size def max_size
current_application_settings.max_pages_size.megabytes || MAX_SIZE max_pages_size = current_application_settings.max_pages_size.megabytes
return MAX_SIZE if max_pages_size.zero?
[max_pages_size, MAX_SIZE].min
end end
def tmp_path def tmp_path
......
module WikiPages module WikiPages
class UpdateService < WikiPages::BaseService class UpdateService < WikiPages::BaseService
def execute(page) def execute(page)
if page.update(@params[:content], @params[:format], @params[:message]) if page.update(@params[:content], format: @params[:format], message: @params[:message], last_commit_sha: @params[:last_commit_sha])
execute_hooks(page, 'update') execute_hooks(page, 'update')
end end
......
...@@ -175,7 +175,7 @@ ...@@ -175,7 +175,7 @@
.well-segment.well-centered .well-segment.well-centered
= link_to admin_groups_path do = link_to admin_groups_path do
%h3.text-center %h3.text-center
Groups Groups:
= number_with_delimiter(Group.count) = number_with_delimiter(Group.count)
%hr %hr
= link_to 'New group', new_admin_group_path, class: "btn btn-new" = link_to 'New group', new_admin_group_path, class: "btn btn-new"
......
= render 'shared/projects/list', projects: projects, stars: false, skip_namespace: false
...@@ -65,6 +65,10 @@ ...@@ -65,6 +65,10 @@
= custom_icon('key') = custom_icon('key')
%span.nav-item-name %span.nav-item-name
SSH Keys SSH Keys
= nav_link(controller: :gpg_keys) do
= link_to profile_gpg_keys_path, title: 'GPG Keys' do
%span
GPG Keys
= nav_link(controller: :preferences) do = nav_link(controller: :preferences) do
= link_to profile_preferences_path, title: 'Preferences' do = link_to profile_preferences_path, title: 'Preferences' do
.nav-icon-container .nav-icon-container
......
...@@ -43,6 +43,10 @@ ...@@ -43,6 +43,10 @@
= link_to profile_keys_path, title: 'SSH Keys' do = link_to profile_keys_path, title: 'SSH Keys' do
%span %span
SSH Keys SSH Keys
= nav_link(controller: :gpg_keys) do
= link_to profile_gpg_keys_path, title: 'GPG Keys' do
%span
GPG Keys
= nav_link(controller: :preferences) do = nav_link(controller: :preferences) do
= link_to profile_preferences_path, title: 'Preferences' do = link_to profile_preferences_path, title: 'Preferences' do
%span %span
......
%p
Hi #{@user.name}!
%p
A new GPG key was added to your account:
%p
Fingerprint:
%code= @gpg_key.fingerprint
%p
If this key was added in error, you can remove it under
= link_to "GPG Keys", profile_gpg_keys_url
Hi <%= @user.name %>!
A new GPG key was added to your account:
Fingerprint: <%= @gpg_key.fingerprint %>
If this key was added in error, you can remove it at <%= profile_gpg_keys_url %>
- css_classes = %w(label label-verification-status)
- css_classes << (verified ? 'verified': 'unverified')
- text = verified ? 'Verified' : 'Unverified'
.gpg-email-badge
.gpg-email-badge-email= email
%div{ class: css_classes }
= text
%div
= form_for [:profile, @gpg_key], html: { class: 'js-requires-input' } do |f|
= form_errors(@gpg_key)
.form-group
= f.label :key, class: 'label-light'
= f.text_area :key, class: "form-control", rows: 8, required: true, placeholder: "Don't paste the private part of the GPG key. Paste the public part which begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----'."
.prepend-top-default
= f.submit 'Add key', class: "btn btn-create"
%li.key-list-item
.pull-left.append-right-10
= icon 'key', class: "settings-list-icon hidden-xs"
.key-list-item-info
- key.emails_with_verified_status.map do |email, verified|
= render partial: 'email_with_badge', locals: { email: email, verified: verified }
.description
%code= key.fingerprint
.pull-right
%span.key-created-at
created #{time_ago_with_tooltip(key.created_at)}
= link_to profile_gpg_key_path(key), data: { confirm: 'Are you sure? Removing this GPG key does not affect already signed commits.' }, method: :delete, class: "btn btn-danger prepend-left-10" do
%span.sr-only Remove
= icon('trash')
= link_to revoke_profile_gpg_key_path(key), data: { confirm: 'Are you sure? All commits that were signed with this GPG key will be unverified.' }, method: :put, class: "btn btn-danger prepend-left-10" do
%span.sr-only Revoke
Revoke
- is_admin = local_assigns.fetch(:admin, false)
- if @gpg_keys.any?
%ul.well-list
= render partial: 'profiles/gpg_keys/key', collection: @gpg_keys, locals: { is_admin: is_admin }
- else
%p.settings-message.text-center
- if is_admin
There are no GPG keys associated with this account.
- else
There are no GPG keys with access to your account.
- page_title "GPG Keys"
= render 'profiles/head'
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
%h4.prepend-top-0
= page_title
%p
GPG keys allow you to verify signed commits.
.col-lg-9
%h5.prepend-top-0
Add a GPG key
%p.profile-settings-content
Before you can add a GPG key you need to
= link_to 'generate it.', help_page_path('workflow/gpg_signed_commits/index.md')
= render 'form'
%hr
%h5
Your GPG keys (#{@gpg_keys.count})
.append-bottom-default
= render 'key_table'
.file-content.image_file .file-content.image_file
%img{ 'data-src': blob_raw_url, alt: viewer.blob.name } = image_tag(blob_raw_url, alt: viewer.blob.name)
- if commit.has_signature?
%button{ class: commit_signature_badge_classes('js-loading-gpg-badge'), data: { toggle: 'tooltip', placement: 'auto top', title: 'GPG signature (loading...)', 'commit-sha' => commit.sha } }
%i.fa.fa-spinner.fa-spin
.page-content-header.js-commit-box{ 'data-commit-path' => branches_project_commit_path(@project, @commit.id) } .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
%strong %strong
#{ s_('CommitBoxTitle|Commit') } #{ s_('CommitBoxTitle|Commit') }
%span.commit-sha= @commit.short_id %span.commit-sha= @commit.short_id
......
- title = capture do
.gpg-popover-icon.invalid
= render 'shared/icons/icon_status_notfound_borderless.svg'
%div
This commit was signed with an <strong>unverified</strong> signature.
- locals = { signature: signature, title: title, label: 'Unverified', css_classes: ['invalid'] }
= render partial: 'projects/commit/signature_badge', locals: locals
- if signature
- if signature.valid_signature?
= render partial: 'projects/commit/valid_signature_badge', locals: { signature: signature }
- else
= render partial: 'projects/commit/invalid_signature_badge', locals: { signature: signature }
- css_classes = commit_signature_badge_classes(css_classes)
- title = capture do
.gpg-popover-status
= title
- content = capture do
.clearfix
= content
GPG Key ID:
%span.monospace= signature.gpg_key_primary_keyid
= link_to('Learn more about signing commits', help_page_path('workflow/gpg_signed_commits/index.md'), class: 'gpg-popover-help-link')
%button{ class: css_classes, data: { toggle: 'popover', html: 'true', placement: 'auto top', title: title, content: content } }
= label
- title = capture do
.gpg-popover-icon.valid
= render 'shared/icons/icon_status_success_borderless.svg'
%div
This commit was signed with a <strong>verified</strong> signature.
- content = capture do
- gpg_key = signature.gpg_key
- user = gpg_key&.user
- user_name = signature.gpg_key_user_name
- user_email = signature.gpg_key_user_email
- if user
= link_to user_path(user), class: 'gpg-popover-user-link' do
%div
= user_avatar_without_link(user: user, size: 32)
%div
%strong= gpg_key.user.name
%div @#{gpg_key.user.username}
- else
= mail_to user_email do
%div
= user_avatar_without_link(user_name: user_name, user_email: user_email, size: 32)
%div
%strong= user_name
%div= user_email
- locals = { signature: signature, title: title, content: content, label: 'Verified', css_classes: ['valid'] }
= render partial: 'projects/commit/signature_badge', locals: locals
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
- 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
%li.commit.flex-list.js-toggle-container{ id: "commit-#{commit.short_id}" } %li.commit.flex-row.js-toggle-container{ id: "commit-#{commit.short_id}" }
.avatar-cell.hidden-xs .avatar-cell.hidden-xs
= author_avatar(commit, size: 36) = author_avatar(commit, size: 36)
...@@ -40,9 +40,15 @@ ...@@ -40,9 +40,15 @@
= project.name_with_namespace = project.name_with_namespace
.commit-actions.flex-row.hidden-xs .commit-actions.hidden-xs
- if commit.status(ref) - if commit.status(ref)
= render_commit_status(commit, ref: ref) = render_commit_status(commit, ref: ref)
- if request.xhr?
= render partial: 'projects/commit/signature', object: commit.signature
- else
= render partial: 'projects/commit/ajax_signature', locals: { commit: commit }
= link_to commit.short_id, project_commit_path(project, commit), class: "commit-sha btn btn-transparent" = link_to commit.short_id, project_commit_path(project, commit), class: "commit-sha btn btn-transparent"
= clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard")) = clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"))
= link_to_browse_code(project, commit) = link_to_browse_code(project, commit)
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
%span.commits-count= n_("%d commit", "%d commits", commits.count) % commits.count %span.commits-count= n_("%d commit", "%d commits", commits.count) % commits.count
%li.commits-row{ data: { day: day } } %li.commits-row{ data: { day: day } }
%ul.content-list.commit-list %ul.content-list.commit-list.flex-list
= render partial: 'projects/commits/commit', collection: commits, locals: { project: project, ref: ref } = render partial: 'projects/commits/commit', collection: commits, locals: { project: project, ref: ref }
- if hidden > 0 - if hidden > 0
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
.image .image
%span.wrap %span.wrap
.frame{ class: (diff_file.deleted_file? ? 'deleted' : 'added') } .frame{ class: (diff_file.deleted_file? ? 'deleted' : 'added') }
%img{ 'data-src': blob_raw_path, alt: diff_file.file_path } = image_tag(blob_raw_path, alt: diff_file.file_path)
%p.image-info= number_to_human_size(blob.size) %p.image-info= number_to_human_size(blob.size)
- else - else
.image .image
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
%span.wrap %span.wrap
.frame.deleted .frame.deleted
%a{ href: project_blob_path(@project, tree_join(diff_file.old_content_sha, diff_file.old_path)) } %a{ href: project_blob_path(@project, tree_join(diff_file.old_content_sha, diff_file.old_path)) }
%img{ 'data-src': old_blob_raw_path, alt: diff_file.old_path } = image_tag(old_blob_raw_path, alt: diff_file.old_path)
%p.image-info.hide %p.image-info.hide
%span.meta-filesize= number_to_human_size(old_blob.size) %span.meta-filesize= number_to_human_size(old_blob.size)
| |
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
%span.wrap %span.wrap
.frame.added .frame.added
%a{ href: project_blob_path(@project, tree_join(diff_file.content_sha, diff_file.new_path)) } %a{ href: project_blob_path(@project, tree_join(diff_file.content_sha, diff_file.new_path)) }
%img{ 'data-src': blob_raw_path, alt: diff_file.new_path } = image_tag(blob_raw_path, alt: diff_file.new_path)
%p.image-info.hide %p.image-info.hide
%span.meta-filesize= number_to_human_size(blob.size) %span.meta-filesize= number_to_human_size(blob.size)
| |
...@@ -41,10 +41,10 @@ ...@@ -41,10 +41,10 @@
.swipe.view.hide .swipe.view.hide
.swipe-frame .swipe-frame
.frame.deleted .frame.deleted
%img{ 'data-src': old_blob_raw_path, alt: diff_file.old_path } = image_tag(old_blob_raw_path, alt: diff_file.old_path)
.swipe-wrap .swipe-wrap
.frame.added .frame.added
%img{ 'data-src': blob_raw_path, alt: diff_file.new_path } = image_tag(blob_raw_path, alt: diff_file.new_path)
%span.swipe-bar %span.swipe-bar
%span.top-handle %span.top-handle
%span.bottom-handle %span.bottom-handle
...@@ -52,9 +52,9 @@ ...@@ -52,9 +52,9 @@
.onion-skin.view.hide .onion-skin.view.hide
.onion-skin-frame .onion-skin-frame
.frame.deleted .frame.deleted
%img{ 'data-src': old_blob_raw_path, alt: diff_file.old_path } = image_tag(old_blob_raw_path, alt: diff_file.old_path)
.frame.added .frame.added
%img{ 'data-src': blob_raw_path, alt: diff_file.new_path } = image_tag(blob_raw_path, alt: diff_file.new_path)
.controls .controls
.transparent .transparent
.drag-track .drag-track
......
- @no_container = true - @no_container = true
- page_title "Labels" - page_title "Labels"
- hide_class = '' - hide_class = ''
- can_admin_label = can?(current_user, :admin_label, @project)
- if show_new_nav? && can?(current_user, :admin_label, @project) - if show_new_nav? && can?(current_user, :admin_label, @project)
- content_for :breadcrumbs_extra do - content_for :breadcrumbs_extra do
...@@ -12,15 +13,17 @@ ...@@ -12,15 +13,17 @@
%div{ class: container_class } %div{ class: container_class }
.top-area.adjust .top-area.adjust
.nav-text .nav-text
Labels can be applied to issues and merge requests. Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging. Labels can be applied to issues and merge requests.
- if can_admin_label
Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.
- if can_admin_label
.nav-controls{ class: ("visible-xs" if show_new_nav?) } .nav-controls{ class: ("visible-xs" if show_new_nav?) }
- if can?(current_user, :admin_label, @project)
= link_to new_project_label_path(@project), class: "btn btn-new" do = link_to new_project_label_path(@project), class: "btn btn-new" do
New label New label
.labels .labels
- if can?(current_user, :admin_label, @project) - if can_admin_label
-# Only show it in the first page -# Only show it in the first page
- hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1') - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
.prioritized-labels{ class: ('hide' if hide) } .prioritized-labels{ class: ('hide' if hide) }
...@@ -33,7 +36,7 @@ ...@@ -33,7 +36,7 @@
- if @labels.present? - if @labels.present?
.other-labels .other-labels
- if can?(current_user, :admin_label, @project) - if can_admin_label
%h5{ class: ('hide' if hide) } Other Labels %h5{ class: ('hide' if hide) } Other Labels
%ul.content-list.manage-labels-list.js-other-labels %ul.content-list.manage-labels-list.js-other-labels
= render partial: 'shared/label', subject: @project, collection: @labels, as: :label = render partial: 'shared/label', subject: @project, collection: @labels, as: :label
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: project_new_merge_request_path(@project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f| = form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: project_new_merge_request_path(@project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f|
.hide.alert.alert-danger.mr-compare-errors .hide.alert.alert-danger.mr-compare-errors
.merge-request-branches.row .merge-request-branches.js-merge-request-new-compare.row{ 'data-target-project-url': project_new_merge_request_update_branches_path(@source_project), 'data-source-branch-url': project_new_merge_request_branch_from_path(@source_project), 'data-target-branch-url': project_new_merge_request_branch_to_path(@source_project) }
.col-md-6 .col-md-6
.panel.panel-default.panel-new-merge-request .panel.panel-default.panel-new-merge-request
.panel-heading .panel-heading
...@@ -66,10 +66,3 @@ ...@@ -66,10 +66,3 @@
- if @merge_request.errors.any? - if @merge_request.errors.any?
= form_errors(@merge_request) = form_errors(@merge_request)
= f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn" = f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn"
:javascript
new Compare({
targetProjectUrl: "#{project_new_merge_request_update_branches_path(@source_project)}",
sourceBranchUrl: "#{project_new_merge_request_branch_from_path(@source_project)}",
targetBranchUrl: "#{project_new_merge_request_branch_to_path(@source_project)}"
});
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
= f.hidden_field :target_project_id = f.hidden_field :target_project_id
= f.hidden_field :target_branch = f.hidden_field :target_branch
.mr-compare.merge-request .mr-compare.merge-request.js-merge-request-new-submit{ 'data-mr-submit-action': "#{j params[:tab].presence || 'new'}" }
- if @commits.empty? - if @commits.empty?
.commits-empty .commits-empty
%h4 %h4
...@@ -50,8 +50,3 @@ ...@@ -50,8 +50,3 @@
.mr-loading-status .mr-loading-status
= spinner = spinner
:javascript
var merge_request = new MergeRequest({
action: "#{j params[:tab].presence || 'new'}",
});
...@@ -3,11 +3,11 @@ ...@@ -3,11 +3,11 @@
- page_description @merge_request.description - page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes - page_card_attributes @merge_request.card_attributes
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue') = webpack_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('diff_notes') = webpack_bundle_tag('diff_notes')
= webpack_bundle_tag('issuable') = webpack_bundle_tag('issuable')
.merge-request{ 'data-url' => merge_request_path(@merge_request, format: :json), 'data-project-path' => project_path(@merge_request.project) } .merge-request{ 'data-mr-action': "#{j params[:tab].presence || 'show'}", 'data-url' => merge_request_path(@merge_request, format: :json), 'data-project-path' => project_path(@merge_request.project) }
= render "projects/merge_requests/mr_title" = render "projects/merge_requests/mr_title"
.merge-request-details.issuable-details{ data: { id: @merge_request.project.id } } .merge-request-details.issuable-details{ data: { id: @merge_request.project.id } }
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
- if @merge_request.source_branch_exists? - if @merge_request.source_branch_exists?
= render "projects/merge_requests/how_to_merge" = render "projects/merge_requests/how_to_merge"
-# haml-lint:disable InlineJavaScript
:javascript :javascript
window.gl.mrWidgetData = #{serialize_issuable(@merge_request)} window.gl.mrWidgetData = #{serialize_issuable(@merge_request)}
...@@ -29,7 +30,6 @@ ...@@ -29,7 +30,6 @@
#js-vue-mr-widget.mr-widget #js-vue-mr-widget.mr-widget
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'vue_merge_request_widget' = webpack_bundle_tag 'vue_merge_request_widget'
.content-block.content-block-small.emoji-list-container .content-block.content-block-small.emoji-list-container
...@@ -96,10 +96,3 @@ ...@@ -96,10 +96,3 @@
= render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title = render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title
- if @merge_request.can_be_cherry_picked? - if @merge_request.can_be_cherry_picked?
= render "projects/commit/change", type: 'cherry-pick', commit: @merge_request.merge_commit, title: @merge_request.title = render "projects/commit/change", type: 'cherry-pick', commit: @merge_request.merge_commit, title: @merge_request.title
:javascript
$(function () {
window.mergeRequest = new MergeRequest({
action: "#{j params[:tab].presence || 'show'}",
});
});
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
%div %div
- if fogbugz_import_enabled? - if fogbugz_import_enabled?
= link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do = link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do
= icon('bug', text: 'Fogbugz') = icon('bug', text: 'FogBugz')
%div %div
- if gitea_import_enabled? - if gitea_import_enabled?
= link_to new_import_gitea_url, class: 'btn import_gitea' do = link_to new_import_gitea_url, class: 'btn import_gitea' do
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
= form_errors(@page) = form_errors(@page)
= f.hidden_field :title, value: @page.title = f.hidden_field :title, value: @page.title
- if @page.persisted?
= f.hidden_field :last_commit_sha, value: @page.last_commit_sha
.form-group .form-group
.col-sm-12= f.label :format, class: 'control-label-full-width' .col-sm-12= f.label :format, class: 'control-label-full-width'
.col-sm-12 .col-sm-12
......
- @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout - @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout
- page_title "Edit", @page.title.capitalize, "Wiki" - page_title "Edit", @page.title.capitalize, "Wiki"
- if @conflict
.alert.alert-danger
Someone edited the page the same time you did. Please check out
= link_to "the page", project_wiki_path(@project, @page), target: "_blank"
and make sure your changes will not unintentionally remove theirs.
.wiki-page-header.has-sidebar-toggle .wiki-page-header.has-sidebar-toggle
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= icon('angle-double-left') = icon('angle-double-left')
......
<svg width="22" height="22" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"><path d="M12.822 11.29c.816-.581 1.421-1.348 1.683-2.322.603-2.243-.973-4.553-3.53-4.553-1.15 0-2.085.41-2.775 1.089-.42.413-.672.835-.8 1.167a1.179 1.179 0 0 0 2.2.847c.016-.043.1-.184.252-.334.264-.259.613-.412 1.123-.412.938 0 1.47.78 1.254 1.584-.105.39-.37.726-.773 1.012a3.25 3.25 0 0 1-.945.47 1.179 1.179 0 0 0-.874 1.138v2.234a1.179 1.179 0 1 0 2.358 0V11.78a5.9 5.9 0 0 0 .827-.492z" fill-rule="nonzero"/><ellipse cx="10.825" cy="16.711" rx="1.275" ry="1.322"/></svg>
...@@ -4,5 +4,5 @@ ...@@ -4,5 +4,5 @@
= link_to user, title: user.name, class: "darken" do = link_to user, title: user.name, class: "darken" do
= image_tag avatar_icon(user, 32), class: "avatar s32" = image_tag avatar_icon(user, 32), class: "avatar s32"
%strong= truncate(user.name, length: 40) %strong= truncate(user.name, length: 40)
%br %div
%small.cgray= user.username %small.cgray= user.username
.clearfix.calendar
.js-contrib-calendar
.calendar-hint
Summary of issues, merge requests, push events, and comments
:javascript
new Calendar(
#{@activity_dates.to_json},
'#{user_calendar_activities_path}'
);
...@@ -2,9 +2,6 @@ ...@@ -2,9 +2,6 @@
- @hide_breadcrumbs = true - @hide_breadcrumbs = true
- page_title @user.name - page_title @user.name
- page_description @user.bio - page_description @user.bio
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('users')
- header_title @user.name, user_path(@user) - header_title @user.name, user_path(@user)
- @no_container = true - @no_container = true
...@@ -107,7 +104,7 @@ ...@@ -107,7 +104,7 @@
.tab-content .tab-content
#activity.tab-pane #activity.tab-pane
.row-content-block.calender-block.white.second-block.hidden-xs .row-content-block.calender-block.white.second-block.hidden-xs
.user-calendar{ data: { href: user_calendar_path } } .user-calendar{ data: { calendar_path: user_calendar_path(@user, :json), calendar_activities_path: user_calendar_activities_path } }
%h4.center.light %h4.center.light
%i.fa.fa-spinner.fa-spin %i.fa.fa-spinner.fa-spin
.user-calendar-activities .user-calendar-activities
...@@ -131,10 +128,3 @@ ...@@ -131,10 +128,3 @@
.loading-status .loading-status
= spinner = spinner
:javascript
var userProfile;
userProfile = new gl.User({
action: "#{controller.action_name}"
});
class CreateGpgSignatureWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
def perform(commit_sha, project_id)
project = Project.find_by(id: project_id)
return unless project
commit = project.commit(commit_sha)
return unless commit
commit.signature
end
end
class InvalidGpgSignatureUpdateWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
def perform(gpg_key_id)
gpg_key = GpgKey.find_by(id: gpg_key_id)
return unless gpg_key
Gitlab::Gpg::InvalidGpgSignatureUpdater.new(gpg_key).run
end
end
---
title: Alert the user if a Wiki page changed while they were editing it in order to prevent overwriting changes.
merge_request: 9707
author: Hiroyuki Sato
---
title: Display specific error message when JIRA test fails
merge_request:
author:
---
title: Add CSRF token verification to API
merge_request: 12154
author: Vitaliy @blackst0ne Klachkov
---
title: Improve CSS for global nav dropdown UI
merge_request: 12772
author: Takuya Noguchi
---
title: Remove help message about prioritized labels for non-members
merge_request: 12912
author: Takuya Noguchi
---
title: Fix creating merge request diffs when diff contains bytes that are invalid
in UTF-8
merge_request:
author:
---
title: Support custom directory in gitlab:backup:create task
merge_request: 12984
author: Markus Koller
---
title: GPG signed commits integration
merge_request: 9546
author: Alexis Reigel
---
title: Handle maximum pages artifacts size correctly
merge_request: 13072
author:
---
title: Add LDAP SSL certificate verification option
merge_request:
author:
---
title: Improve redirect route query performance
merge_request: 13062
author:
---
title: Ensure filesystem metrics test files are deleted
merge_request:
author:
---
title: Fix Prometheus client PID reuse bug
merge_request: 13130
author:
---
title: Enable gitaly_post_upload_pack by default
merge_request: 13078
author:
---
title: Fix the /projects/:id/repository/branches endpoint to handle dots in the branch
name when the project full patch contains a `/`
merge_request: 13115
author:
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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