Commit 54ab4adc authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into 19703-direct-link-pipelines

* master: (175 commits)
  Fix typo
  Always use `fixture_file_upload` helper to upload files in tests.
  Add CHANGELOG
  Fix extra spacing in all rgba methods in status file
  Improve spacing and fixes manual status color
  Add `ci-manual` status CSS with darkest gray color
  Move admin application spinach test to rspec
  Move admin deploy keys spinach test to rspec
  Fix CI/CD statuses actions' CSS on pipeline graphs
  Fix rubocop failures
  Store mattermost_url in settings
  Improve Mattermost Session specs
  Ensure the session is destroyed
  Improve session tests
  Setup mattermost session
  Fix link from doc/development/performance.md to 'Performance Monitoring'
  Fix query in Projects::ProjectMembersController to fetch members
  Improve test for sort dropdown on members page
  Fix sort dropdown alignment
  Undo changes on members search button stylesheet
  ...
parents 083e185c 16f950b4
...@@ -15,6 +15,7 @@ variables: ...@@ -15,6 +15,7 @@ variables:
USE_BUNDLE_INSTALL: "true" USE_BUNDLE_INSTALL: "true"
GIT_DEPTH: "20" GIT_DEPTH: "20"
PHANTOMJS_VERSION: "2.1.1" PHANTOMJS_VERSION: "2.1.1"
GET_SOURCES_ATTEMPTS: "3"
before_script: before_script:
- source ./scripts/prepare_build.sh - source ./scripts/prepare_build.sh
......
...@@ -292,7 +292,8 @@ Style/MultilineMethodDefinitionBraceLayout: ...@@ -292,7 +292,8 @@ Style/MultilineMethodDefinitionBraceLayout:
# Checks indentation of binary operations that span more than one line. # Checks indentation of binary operations that span more than one line.
Style/MultilineOperationIndentation: Style/MultilineOperationIndentation:
Enabled: false Enabled: true
EnforcedStyle: indented
# Avoid multi-line `? :` (the ternary operator), use if/unless instead. # Avoid multi-line `? :` (the ternary operator), use if/unless instead.
Style/MultilineTernaryOperator: Style/MultilineTernaryOperator:
......
...@@ -22,7 +22,6 @@ gem 'doorkeeper', '~> 4.2.0' ...@@ -22,7 +22,6 @@ gem 'doorkeeper', '~> 4.2.0'
gem 'omniauth', '~> 1.3.1' gem 'omniauth', '~> 1.3.1'
gem 'omniauth-auth0', '~> 1.4.1' gem 'omniauth-auth0', '~> 1.4.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6' gem 'omniauth-azure-oauth2', '~> 0.0.6'
gem 'omniauth-bitbucket', '~> 0.0.2'
gem 'omniauth-cas3', '~> 1.1.2' gem 'omniauth-cas3', '~> 1.1.2'
gem 'omniauth-facebook', '~> 4.0.0' gem 'omniauth-facebook', '~> 4.0.0'
gem 'omniauth-github', '~> 1.1.1' gem 'omniauth-github', '~> 1.1.1'
......
...@@ -432,10 +432,6 @@ GEM ...@@ -432,10 +432,6 @@ GEM
jwt (~> 1.0) jwt (~> 1.0)
omniauth (~> 1.0) omniauth (~> 1.0)
omniauth-oauth2 (~> 1.1) omniauth-oauth2 (~> 1.1)
omniauth-bitbucket (0.0.2)
multi_json (~> 1.7)
omniauth (~> 1.1)
omniauth-oauth (~> 1.0)
omniauth-cas3 (1.1.3) omniauth-cas3 (1.1.3)
addressable (~> 2.3) addressable (~> 2.3)
nokogiri (~> 1.6.6) nokogiri (~> 1.6.6)
...@@ -902,7 +898,6 @@ DEPENDENCIES ...@@ -902,7 +898,6 @@ DEPENDENCIES
omniauth (~> 1.3.1) omniauth (~> 1.3.1)
omniauth-auth0 (~> 1.4.1) omniauth-auth0 (~> 1.4.1)
omniauth-azure-oauth2 (~> 0.0.6) omniauth-azure-oauth2 (~> 0.0.6)
omniauth-bitbucket (~> 0.0.2)
omniauth-cas3 (~> 1.1.2) omniauth-cas3 (~> 1.1.2)
omniauth-facebook (~> 4.0.0) omniauth-facebook (~> 4.0.0)
omniauth-github (~> 1.1.1) omniauth-github (~> 1.1.1)
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
licensePath: "/api/:version/templates/licenses/:key", licensePath: "/api/:version/templates/licenses/:key",
gitignorePath: "/api/:version/templates/gitignores/:key", gitignorePath: "/api/:version/templates/gitignores/:key",
gitlabCiYmlPath: "/api/:version/templates/gitlab_ci_ymls/:key", gitlabCiYmlPath: "/api/:version/templates/gitlab_ci_ymls/:key",
dockerfilePath: "/api/:version/dockerfiles/:key",
issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key", issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key",
group: function(group_id, callback) { group: function(group_id, callback) {
var url = Api.buildUrl(Api.groupPath) var url = Api.buildUrl(Api.groupPath)
...@@ -120,6 +121,10 @@ ...@@ -120,6 +121,10 @@
return callback(file); return callback(file);
}); });
}, },
dockerfileYml: function(key, callback) {
var url = Api.buildUrl(Api.dockerfilePath).replace(':key', key);
$.get(url, callback);
},
issueTemplate: function(namespacePath, projectPath, key, type, callback) { issueTemplate: function(namespacePath, projectPath, key, type, callback) {
var url = Api.buildUrl(Api.issuableTemplatePath) var url = Api.buildUrl(Api.issuableTemplatePath)
.replace(':key', key) .replace(':key', key)
......
/* global Api */
/*= require blob/template_selector */
(() => {
const global = window.gl || (window.gl = {});
class BlobDockerfileSelector extends gl.TemplateSelector {
requestFile(query) {
return Api.dockerfileYml(query.name, this.requestFileSuccess.bind(this));
}
requestFileSuccess(file) {
return super.requestFileSuccess(file);
}
}
global.BlobDockerfileSelector = BlobDockerfileSelector;
})();
(() => {
const global = window.gl || (window.gl = {});
class BlobDockerfileSelectors {
constructor({ editor, $dropdowns } = {}) {
this.editor = editor;
this.$dropdowns = $dropdowns || $('.js-dockerfile-selector');
this.initSelectors();
}
initSelectors() {
const editor = this.editor;
this.$dropdowns.each((i, dropdown) => {
const $dropdown = $(dropdown);
return new gl.BlobDockerfileSelector({
editor,
pattern: /(Dockerfile)/,
data: $dropdown.data('data'),
wrapper: $dropdown.closest('.js-dockerfile-selector-wrap'),
dropdown: $dropdown,
});
});
}
}
global.BlobDockerfileSelectors = BlobDockerfileSelectors;
})();
...@@ -36,6 +36,9 @@ ...@@ -36,6 +36,9 @@
new gl.BlobCiYamlSelectors({ new gl.BlobCiYamlSelectors({
editor: this.editor editor: this.editor
}); });
new gl.BlobDockerfileSelectors({
editor: this.editor
});
} }
EditBlob.prototype.initModePanesAndLinks = function() { EditBlob.prototype.initModePanesAndLinks = function() {
......
...@@ -449,7 +449,7 @@ ...@@ -449,7 +449,7 @@
</span> </span>
</td> </td>
<td> <td class="environments-build-cell">
<a v-if="shouldRenderBuildName" <a v-if="shouldRenderBuildName"
class="build-link" class="build-link"
:href="model.last_deployment.deployable.build_path"> :href="model.last_deployment.deployable.build_path">
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
this.form.addClass('gfm-form'); this.form.addClass('gfm-form');
// remove notify commit author checkbox for non-commit notes // remove notify commit author checkbox for non-commit notes
gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button')); gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
GitLab.GfmAutoComplete.setup(this.form.find('.js-gfm-input')); gl.GfmAutoComplete.setup(this.form.find('.js-gfm-input'));
new DropzoneInput(this.form); new DropzoneInput(this.form);
autosize(this.textarea); autosize(this.textarea);
// form and textarea event listeners // form and textarea event listeners
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
this.renderWipExplanation = bind(this.renderWipExplanation, this); this.renderWipExplanation = bind(this.renderWipExplanation, this);
this.resetAutosave = bind(this.resetAutosave, this); this.resetAutosave = bind(this.resetAutosave, this);
this.handleSubmit = bind(this.handleSubmit, this); this.handleSubmit = bind(this.handleSubmit, this);
GitLab.GfmAutoComplete.setup(); gl.GfmAutoComplete.setup();
new UsersSelect(); new UsersSelect();
new ZenMode(); new ZenMode();
this.titleField = this.form.find("input[name*='[title]']"); this.titleField = this.form.find("input[name*='[title]']");
......
/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, padded-blocks, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, padded-blocks, max-len, prefer-arrow-callback */
/* global MergeRequestTabs */ /* global MergeRequestTabs */
/*= require jquery.waitforimages */ /*= require jquery.waitforimages */
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
// Prevent duplicate event bindings // Prevent duplicate event bindings
this.disableTaskList(); this.disableTaskList();
this.initMRBtnListeners(); this.initMRBtnListeners();
this.initCommitMessageListeners();
if ($("a.btn-close").length) { if ($("a.btn-close").length) {
this.initTaskList(); this.initTaskList();
} }
...@@ -108,6 +109,26 @@ ...@@ -108,6 +109,26 @@
// note so that we can re-use its form here // note so that we can re-use its form here
}; };
MergeRequest.prototype.initCommitMessageListeners = function() {
var textarea = $('textarea.js-commit-message');
$('a.js-with-description-link').on('click', function(e) {
e.preventDefault();
textarea.val(textarea.data('messageWithDescription'));
$('p.js-with-description-hint').hide();
$('p.js-without-description-hint').show();
});
$('a.js-without-description-link').on('click', function(e) {
e.preventDefault();
textarea.val(textarea.data('messageWithoutDescription'));
$('p.js-with-description-hint').show();
$('p.js-without-description-hint').hide();
});
};
return MergeRequest; return MergeRequest;
})(); })();
......
...@@ -49,3 +49,11 @@ ...@@ -49,3 +49,11 @@
fill: $gray-darkest; fill: $gray-darkest;
} }
} }
.ci-status-icon-manual {
color: $gl-text-color;
svg {
fill: $gl-text-color;
}
}
...@@ -32,6 +32,41 @@ body { ...@@ -32,6 +32,41 @@ body {
} }
} }
.alert-wrapper {
margin-bottom: $gl-padding;
.alert {
margin-bottom: 0;
}
/* Stripe the background colors so that adjacent alert-warnings are distinct from one another */
.alert-warning {
transition: background-color 0.15s, border-color 0.15s;
background-color: lighten($gl-warning, 4%);
border-color: lighten($gl-warning, 4%);
}
.alert-warning + .alert-warning {
background-color: $gl-warning;
border-color: $gl-warning;
}
.alert-warning + .alert-warning + .alert-warning {
background-color: darken($gl-warning, 4%);
border-color: darken($gl-warning, 4%);
}
.alert-warning + .alert-warning + .alert-warning + .alert-warning {
background-color: darken($gl-warning, 8%);
border-color: darken($gl-warning, 8%);
}
.alert-warning:only-of-type {
background-color: $gl-warning;
border-color: $gl-warning;
}
}
/* The following prevents side effects related to iOS Safari's implementation of -webkit-overflow-scrolling: touch, /* The following prevents side effects related to iOS Safari's implementation of -webkit-overflow-scrolling: touch,
which is applied to the body by jquery.nicescroling plugin to force hardware acceleration for momentum scrolling. Side which is applied to the body by jquery.nicescroling plugin to force hardware acceleration for momentum scrolling. Side
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
@import "bootstrap/alerts"; @import "bootstrap/alerts";
// @import "bootstrap/progress-bars"; // @import "bootstrap/progress-bars";
@import "bootstrap/list-group"; @import "bootstrap/list-group";
// @import "bootstrap/wells"; @import "bootstrap/wells";
@import "bootstrap/close"; @import "bootstrap/close";
@import "bootstrap/panels"; @import "bootstrap/panels";
......
...@@ -438,7 +438,7 @@ $jq-ui-default-color: #777; ...@@ -438,7 +438,7 @@ $jq-ui-default-color: #777;
$label-gray-bg: #f8fafc; $label-gray-bg: #f8fafc;
$label-inverse-bg: #333; $label-inverse-bg: #333;
$label-remove-border: rgba(0, 0, 0, .1); $label-remove-border: rgba(0, 0, 0, .1);
$label-border-radius: 14px; $label-border-radius: 100px;
/* /*
* Lint * Lint
...@@ -474,7 +474,6 @@ $project-option-descr-color: #54565b; ...@@ -474,7 +474,6 @@ $project-option-descr-color: #54565b;
$project-breadcrumb-color: #999; $project-breadcrumb-color: #999;
$project-private-forks-notice-odd: #2aa056; $project-private-forks-notice-odd: #2aa056;
$project-network-controls-color: #888; $project-network-controls-color: #888;
$project-limit-message-bg: #f28d35;
/* /*
* Runners * Runners
......
...@@ -75,7 +75,8 @@ ...@@ -75,7 +75,8 @@
.soft-wrap-toggle, .soft-wrap-toggle,
.license-selector, .license-selector,
.gitignore-selector, .gitignore-selector,
.gitlab-ci-yml-selector { .gitlab-ci-yml-selector,
.dockerfile-selector {
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
font-family: $regular_font; font-family: $regular_font;
...@@ -105,7 +106,8 @@ ...@@ -105,7 +106,8 @@
.gitignore-selector, .gitignore-selector,
.license-selector, .license-selector,
.gitlab-ci-yml-selector { .gitlab-ci-yml-selector,
.dockerfile-selector {
.dropdown { .dropdown {
line-height: 21px; line-height: 21px;
} }
......
...@@ -30,19 +30,25 @@ ...@@ -30,19 +30,25 @@
display: table-cell; display: table-cell;
} }
.environments-name,
.environments-commit, .environments-commit,
.environments-actions { .environments-actions {
width: 20%; width: 20%;
} }
.environments-deploy,
.environments-build,
.environments-date { .environments-date {
width: 10%; width: 10%;
} }
.environments-name { .environments-deploy,
width: 30%; .environments-build {
width: 15%;
}
.environment-name,
.environments-build-cell,
.deployment-column {
word-break: break-all;
} }
.deployment-column { .deployment-column {
......
...@@ -98,7 +98,7 @@ ...@@ -98,7 +98,7 @@
} }
.label { .label {
padding: 9px; padding: 8px 9px 9px;
font-size: 14px; font-size: 14px;
} }
} }
...@@ -201,6 +201,8 @@ ...@@ -201,6 +201,8 @@
.label-remove { .label-remove {
border-left: 1px solid $label-remove-border; border-left: 1px solid $label-remove-border;
z-index: 3; z-index: 3;
border-radius: $label-border-radius;
padding: 6px 10px 6px 9px;
} }
.btn { .btn {
......
...@@ -78,6 +78,21 @@ ...@@ -78,6 +78,21 @@
float: right; float: right;
} }
.dropdown {
width: 100%;
margin-top: 5px;
.dropdown-menu-toggle {
vertical-align: middle;
width: 100%;
}
@media (min-width: $screen-sm-min) {
margin-top: 0;
width: 155px;
}
}
.form-control { .form-control {
width: 100%; width: 100%;
padding-right: 35px; padding-right: 35px;
...@@ -85,12 +100,22 @@ ...@@ -85,12 +100,22 @@
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
width: 350px; width: 350px;
} }
&.input-short {
@media (min-width: $screen-md-min) {
width: 170px;
}
@media (min-width: $screen-lg-min) {
width: 210px;
}
}
} }
} }
.member-search-btn { .member-search-btn {
position: absolute; position: absolute;
right: 0; right: 4px;
top: 0; top: 0;
height: 35px; height: 35px;
padding-left: 10px; padding-left: 10px;
...@@ -99,4 +124,8 @@ ...@@ -99,4 +124,8 @@
background: transparent; background: transparent;
border: 0; border: 0;
outline: 0; outline: 0;
@media (min-width: $screen-sm-min) {
right: 160px;
}
} }
...@@ -262,3 +262,13 @@ table.u2f-registrations { ...@@ -262,3 +262,13 @@ table.u2f-registrations {
border-right: solid 1px transparent; border-right: solid 1px transparent;
} }
} }
.oauth-application-show {
.scope-name {
font-weight: 600;
}
.scopes-list {
padding-left: 18px;
}
}
\ No newline at end of file
...@@ -6,12 +6,6 @@ ...@@ -6,12 +6,6 @@
} }
} }
.no-ssh-key-message,
.project-limit-message {
background-color: $project-limit-message-bg;
margin-bottom: 0;
}
.new_project, .new_project,
.edit-project { .edit-project {
......
.container-fluid { .container-fluid {
.ci-status { .ci-status {
display: inline-block;
padding: 2px 7px; padding: 2px 7px;
margin-right: 10px; margin-right: 10px;
border: 1px solid $gray-darker; border: 1px solid $gray-darker;
...@@ -15,8 +16,7 @@ ...@@ -15,8 +16,7 @@
height: 13px; height: 13px;
width: 13px; width: 13px;
position: relative; position: relative;
top: 1px; top: 2px;
margin-right: 3px;
overflow: visible; overflow: visible;
} }
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
border-color: $gl-danger; border-color: $gl-danger;
&:not(span):hover { &:not(span):hover {
background-color: rgba( $gl-danger, .07); background-color: rgba($gl-danger, .07);
} }
svg { svg {
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
border-color: $gl-success; border-color: $gl-success;
&:not(span):hover { &:not(span):hover {
background-color: rgba( $gl-success, .07); background-color: rgba($gl-success, .07);
} }
svg { svg {
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
border-color: $gl-info; border-color: $gl-info;
&:not(span):hover { &:not(span):hover {
background-color: rgba( $gl-info, .07); background-color: rgba($gl-info, .07);
} }
svg { svg {
...@@ -66,7 +66,7 @@ ...@@ -66,7 +66,7 @@
border-color: $gl-gray; border-color: $gl-gray;
&:not(span):hover { &:not(span):hover {
background-color: rgba( $gl-gray, .07); background-color: rgba($gl-gray, .07);
} }
svg { svg {
...@@ -79,7 +79,7 @@ ...@@ -79,7 +79,7 @@
border-color: $gl-warning; border-color: $gl-warning;
&:not(span):hover { &:not(span):hover {
background-color: rgba( $gl-warning, .07); background-color: rgba($gl-warning, .07);
} }
svg { svg {
...@@ -92,7 +92,7 @@ ...@@ -92,7 +92,7 @@
border-color: $blue-normal; border-color: $blue-normal;
&:not(span):hover { &:not(span):hover {
background-color: rgba( $blue-normal, .07); background-color: rgba($blue-normal, .07);
} }
svg { svg {
...@@ -106,13 +106,26 @@ ...@@ -106,13 +106,26 @@
border-color: $gl-gray-light; border-color: $gl-gray-light;
&:not(span):hover { &:not(span):hover {
background-color: rgba( $gl-gray-light, .07); background-color: rgba($gl-gray-light, .07);
} }
svg { svg {
fill: $gl-gray-light; fill: $gl-gray-light;
} }
} }
&.ci-manual {
color: $gl-text-color;
border-color: $gl-text-color;
&:not(span):hover {
background-color: rgba($gl-text-color, .07);
}
svg {
fill: $gl-text-color;
}
}
} }
} }
......
class Admin::ApplicationsController < Admin::ApplicationController class Admin::ApplicationsController < Admin::ApplicationController
include OauthApplications
before_action :set_application, only: [:show, :edit, :update, :destroy] before_action :set_application, only: [:show, :edit, :update, :destroy]
before_action :load_scopes, only: [:new, :edit]
def index def index
@applications = Doorkeeper::Application.where("owner_id IS NULL") @applications = Doorkeeper::Application.where("owner_id IS NULL")
...@@ -47,6 +50,6 @@ class Admin::ApplicationsController < Admin::ApplicationController ...@@ -47,6 +50,6 @@ class Admin::ApplicationsController < Admin::ApplicationController
# Only allow a trusted parameter "white list" through. # Only allow a trusted parameter "white list" through.
def application_params def application_params
params[:doorkeeper_application].permit(:name, :redirect_uri) params[:doorkeeper_application].permit(:name, :redirect_uri, :scopes)
end end
end end
...@@ -262,7 +262,7 @@ class ApplicationController < ActionController::Base ...@@ -262,7 +262,7 @@ class ApplicationController < ActionController::Base
end end
def bitbucket_import_configured? def bitbucket_import_configured?
Gitlab::OAuth::Provider.enabled?(:bitbucket) && Gitlab::BitbucketImport.public_key.present? Gitlab::OAuth::Provider.enabled?(:bitbucket)
end end
def google_code_import_enabled? def google_code_import_enabled?
......
module OauthApplications
extend ActiveSupport::Concern
included do
before_action :prepare_scopes, only: [:create, :update]
end
def prepare_scopes
scopes = params.fetch(:doorkeeper_application, {}).fetch(:scopes, nil)
if scopes
params[:doorkeeper_application][:scopes] = scopes.join(' ')
end
end
def load_scopes
@scopes = Doorkeeper.configuration.scopes
end
end
class Groups::GroupMembersController < Groups::ApplicationController class Groups::GroupMembersController < Groups::ApplicationController
include MembershipActions include MembershipActions
include SortingHelper
# Authorize # Authorize
before_action :authorize_admin_group_member!, except: [:index, :leave, :request_access] before_action :authorize_admin_group_member!, except: [:index, :leave, :request_access]
def index def index
@sort = params[:sort].presence || sort_value_name
@project = @group.projects.find(params[:project_id]) if params[:project_id] @project = @group.projects.find(params[:project_id]) if params[:project_id]
@members = @group.group_members @members = @group.group_members
@members = @members.non_invite unless can?(current_user, :admin_group, @group) @members = @members.non_invite unless can?(current_user, :admin_group, @group)
@members = @members.search(params[:search]) if params[:search].present?
@members = @members.sort(@sort)
@members = @members.page(params[:page]).per(50)
if params[:search].present?
users = @group.users.search(params[:search]).to_a
@members = @members.where(user_id: users)
end
@members = @members.order('access_level DESC').page(params[:page]).per(50)
@requesters = AccessRequestsFinder.new(@group).execute(current_user) @requesters = AccessRequestsFinder.new(@group).execute(current_user)
@group_member = @group.group_members.new @group_member = @group.group_members.new
......
...@@ -2,50 +2,57 @@ class Import::BitbucketController < Import::BaseController ...@@ -2,50 +2,57 @@ class Import::BitbucketController < Import::BaseController
before_action :verify_bitbucket_import_enabled before_action :verify_bitbucket_import_enabled
before_action :bitbucket_auth, except: :callback before_action :bitbucket_auth, except: :callback
rescue_from OAuth::Error, with: :bitbucket_unauthorized rescue_from OAuth2::Error, with: :bitbucket_unauthorized
rescue_from Gitlab::BitbucketImport::Client::Unauthorized, with: :bitbucket_unauthorized rescue_from Bitbucket::Error::Unauthorized, with: :bitbucket_unauthorized
def callback def callback
request_token = session.delete(:oauth_request_token) response = client.auth_code.get_token(params[:code], redirect_uri: callback_import_bitbucket_url)
raise "Session expired!" if request_token.nil?
request_token.symbolize_keys! session[:bitbucket_token] = response.token
session[:bitbucket_expires_at] = response.expires_at
access_token = client.get_token(request_token, params[:oauth_verifier], callback_import_bitbucket_url) session[:bitbucket_expires_in] = response.expires_in
session[:bitbucket_refresh_token] = response.refresh_token
session[:bitbucket_access_token] = access_token.token
session[:bitbucket_access_token_secret] = access_token.secret
redirect_to status_import_bitbucket_url redirect_to status_import_bitbucket_url
end end
def status def status
@repos = client.projects bitbucket_client = Bitbucket::Client.new(credentials)
@incompatible_repos = client.incompatible_projects repos = bitbucket_client.repos
@repos, @incompatible_repos = repos.partition { |repo| repo.valid? }
@already_added_projects = current_user.created_projects.where(import_type: "bitbucket") @already_added_projects = current_user.created_projects.where(import_type: 'bitbucket')
already_added_projects_names = @already_added_projects.pluck(:import_source) already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos.to_a.reject!{ |repo| already_added_projects_names.include? "#{repo["owner"]}/#{repo["slug"]}" } @repos.to_a.reject! { |repo| already_added_projects_names.include?(repo.full_name) }
end end
def jobs def jobs
jobs = current_user.created_projects.where(import_type: "bitbucket").to_json(only: [:id, :import_status]) render json: current_user.created_projects
render json: jobs .where(import_type: 'bitbucket')
.to_json(only: [:id, :import_status])
end end
def create def create
bitbucket_client = Bitbucket::Client.new(credentials)
@repo_id = params[:repo_id].to_s @repo_id = params[:repo_id].to_s
repo = client.project(@repo_id.gsub('___', '/')) name = @repo_id.gsub('___', '/')
@project_name = repo['slug'] repo = bitbucket_client.repo(name)
@target_namespace = find_or_create_namespace(repo['owner'], client.user['user']['username']) @project_name = params[:new_name].presence || repo.name
unless Gitlab::BitbucketImport::KeyAdder.new(repo, current_user, access_params).execute repo_owner = repo.owner
render 'deploy_key' and return repo_owner = current_user.username if repo_owner == bitbucket_client.user.username
end @target_namespace = params[:new_namespace].presence || repo_owner
namespace = find_or_create_namespace(@target_namespace, current_user)
if current_user.can?(:create_projects, @target_namespace) if current_user.can?(:create_projects, namespace)
@project = Gitlab::BitbucketImport::ProjectCreator.new(repo, @target_namespace, current_user, access_params).execute # The token in a session can be expired, we need to get most recent one because
# Bitbucket::Connection class refreshes it.
session[:bitbucket_token] = bitbucket_client.connection.token
@project = Gitlab::BitbucketImport::ProjectCreator.new(repo, @project_name, namespace, current_user, credentials).execute
else else
render 'unauthorized' render 'unauthorized'
end end
...@@ -54,8 +61,15 @@ class Import::BitbucketController < Import::BaseController ...@@ -54,8 +61,15 @@ class Import::BitbucketController < Import::BaseController
private private
def client def client
@client ||= Gitlab::BitbucketImport::Client.new(session[:bitbucket_access_token], @client ||= OAuth2::Client.new(provider.app_id, provider.app_secret, options)
session[:bitbucket_access_token_secret]) end
def provider
Gitlab::OAuth::Provider.config_for('bitbucket')
end
def options
OmniAuth::Strategies::Bitbucket.default_options[:client_options].deep_symbolize_keys
end end
def verify_bitbucket_import_enabled def verify_bitbucket_import_enabled
...@@ -63,26 +77,23 @@ class Import::BitbucketController < Import::BaseController ...@@ -63,26 +77,23 @@ class Import::BitbucketController < Import::BaseController
end end
def bitbucket_auth def bitbucket_auth
if session[:bitbucket_access_token].blank? go_to_bitbucket_for_permissions if session[:bitbucket_token].blank?
go_to_bitbucket_for_permissions
end
end end
def go_to_bitbucket_for_permissions def go_to_bitbucket_for_permissions
request_token = client.request_token(callback_import_bitbucket_url) redirect_to client.auth_code.authorize_url(redirect_uri: callback_import_bitbucket_url)
session[:oauth_request_token] = request_token
redirect_to client.authorize_url(request_token, callback_import_bitbucket_url)
end end
def bitbucket_unauthorized def bitbucket_unauthorized
go_to_bitbucket_for_permissions go_to_bitbucket_for_permissions
end end
def access_params def credentials
{ {
bitbucket_access_token: session[:bitbucket_access_token], token: session[:bitbucket_token],
bitbucket_access_token_secret: session[:bitbucket_access_token_secret] expires_at: session[:bitbucket_expires_at],
expires_in: session[:bitbucket_expires_in],
refresh_token: session[:bitbucket_refresh_token]
} }
end end
end end
...@@ -26,7 +26,7 @@ class JwtController < ApplicationController ...@@ -26,7 +26,7 @@ class JwtController < ApplicationController
@authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, ip: request.ip) @authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, ip: request.ip)
render_unauthorized unless @authentication_result.success? && render_unauthorized unless @authentication_result.success? &&
(@authentication_result.actor.nil? || @authentication_result.actor.is_a?(User)) (@authentication_result.actor.nil? || @authentication_result.actor.is_a?(User))
end end
rescue Gitlab::Auth::MissingPersonalTokenError rescue Gitlab::Auth::MissingPersonalTokenError
render_missing_personal_token render_missing_personal_token
......
...@@ -2,10 +2,12 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController ...@@ -2,10 +2,12 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
include Gitlab::GonHelper include Gitlab::GonHelper
include PageLayoutHelper include PageLayoutHelper
include OauthApplications
before_action :verify_user_oauth_applications_enabled before_action :verify_user_oauth_applications_enabled
before_action :authenticate_user! before_action :authenticate_user!
before_action :add_gon_variables before_action :add_gon_variables
before_action :load_scopes, only: [:index, :create, :edit]
layout 'profile' layout 'profile'
......
class Profiles::PersonalAccessTokensController < Profiles::ApplicationController class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
before_action :load_personal_access_tokens, only: :index
def index def index
@personal_access_token = current_user.personal_access_tokens.build set_index_vars
end end
def create def create
...@@ -12,7 +10,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController ...@@ -12,7 +10,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
flash[:personal_access_token] = @personal_access_token.token flash[:personal_access_token] = @personal_access_token.token
redirect_to profile_personal_access_tokens_path, notice: "Your new personal access token has been created." redirect_to profile_personal_access_tokens_path, notice: "Your new personal access token has been created."
else else
load_personal_access_tokens set_index_vars
render :index render :index
end end
end end
...@@ -32,10 +30,12 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController ...@@ -32,10 +30,12 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
private private
def personal_access_token_params def personal_access_token_params
params.require(:personal_access_token).permit(:name, :expires_at) params.require(:personal_access_token).permit(:name, :expires_at, scopes: [])
end end
def load_personal_access_tokens def set_index_vars
@personal_access_token ||= current_user.personal_access_tokens.build
@scopes = Gitlab::Auth::SCOPES
@active_personal_access_tokens = current_user.personal_access_tokens.active.order(:expires_at) @active_personal_access_tokens = current_user.personal_access_tokens.active.order(:expires_at)
@inactive_personal_access_tokens = current_user.personal_access_tokens.inactive @inactive_personal_access_tokens = current_user.personal_access_tokens.inactive
end end
......
class Projects::AutocompleteSourcesController < Projects::ApplicationController
before_action :load_autocomplete_service, except: [:emojis, :members]
def emojis
render json: Gitlab::AwardEmoji.urls
end
def members
render json: ::Projects::ParticipantsService.new(@project, current_user).execute(noteable)
end
def issues
render json: @autocomplete_service.issues
end
def merge_requests
render json: @autocomplete_service.merge_requests
end
def labels
render json: @autocomplete_service.labels
end
def milestones
render json: @autocomplete_service.milestones
end
def commands
render json: @autocomplete_service.commands(noteable, params[:type])
end
private
def load_autocomplete_service
@autocomplete_service = ::Projects::AutocompleteService.new(@project, current_user)
end
def noteable
case params[:type]
when 'Issue'
IssuesFinder.new(current_user, project_id: @project.id).execute.find_by(iid: params[:type_id])
when 'MergeRequest'
MergeRequestsFinder.new(current_user, project_id: @project.id).execute.find_by(iid: params[:type_id])
when 'Commit'
@project.commit(params[:type_id])
end
end
end
class Projects::ProjectMembersController < Projects::ApplicationController class Projects::ProjectMembersController < Projects::ApplicationController
include MembershipActions include MembershipActions
include SortingHelper
# Authorize # Authorize
before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access] before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access]
def index def index
@sort = params[:sort].presence || sort_value_name
@group_links = @project.project_group_links @group_links = @project.project_group_links
@project_members = @project.project_members @project_members = @project.project_members
...@@ -35,12 +37,13 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -35,12 +37,13 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@group_links = @project.project_group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id)) @group_links = @project.project_group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id))
end end
wheres = ["id IN (#{@project_members.select(:id).to_sql})"] wheres = ["members.id IN (#{@project_members.select(:id).to_sql})"]
wheres << "id IN (#{group_members.select(:id).to_sql})" if group_members wheres << "members.id IN (#{group_members.select(:id).to_sql})" if group_members
@project_members = Member. @project_members = Member.
where(wheres.join(' OR ')). where(wheres.join(' OR ')).
order(access_level: :desc).page(params[:page]) sort(@sort).
page(params[:page])
@requesters = AccessRequestsFinder.new(@project).execute(current_user) @requesters = AccessRequestsFinder.new(@project).execute(current_user)
......
...@@ -127,39 +127,6 @@ class ProjectsController < Projects::ApplicationController ...@@ -127,39 +127,6 @@ class ProjectsController < Projects::ApplicationController
redirect_to edit_project_path(@project), alert: ex.message redirect_to edit_project_path(@project), alert: ex.message
end end
def autocomplete_sources
noteable =
case params[:type]
when 'Issue'
IssuesFinder.new(current_user, project_id: @project.id).
execute.find_by(iid: params[:type_id])
when 'MergeRequest'
MergeRequestsFinder.new(current_user, project_id: @project.id).
execute.find_by(iid: params[:type_id])
when 'Commit'
@project.commit(params[:type_id])
else
nil
end
autocomplete = ::Projects::AutocompleteService.new(@project, current_user)
participants = ::Projects::ParticipantsService.new(@project, current_user).execute(noteable)
@suggestions = {
emojis: Gitlab::AwardEmoji.urls,
issues: autocomplete.issues,
milestones: autocomplete.milestones,
mergerequests: autocomplete.merge_requests,
labels: autocomplete.labels,
members: participants,
commands: autocomplete.commands(noteable, params[:type])
}
respond_to do |format|
format.json { render json: @suggestions }
end
end
def new_issue_address def new_issue_address
return render_404 unless Gitlab::IncomingEmail.supports_issue_creation? return render_404 unless Gitlab::IncomingEmail.supports_issue_creation?
......
...@@ -114,7 +114,7 @@ class SessionsController < Devise::SessionsController ...@@ -114,7 +114,7 @@ class SessionsController < Devise::SessionsController
def valid_otp_attempt?(user) def valid_otp_attempt?(user)
user.validate_and_consume_otp!(user_params[:otp_attempt]) || user.validate_and_consume_otp!(user_params[:otp_attempt]) ||
user.invalidate_otp_backup_code!(user_params[:otp_attempt]) user.invalidate_otp_backup_code!(user_params[:otp_attempt])
end end
def log_audit_event(user, options = {}) def log_audit_event(user, options = {})
......
...@@ -191,6 +191,10 @@ module BlobHelper ...@@ -191,6 +191,10 @@ module BlobHelper
@gitlab_ci_ymls ||= Gitlab::Template::GitlabCiYmlTemplate.dropdown_names @gitlab_ci_ymls ||= Gitlab::Template::GitlabCiYmlTemplate.dropdown_names
end end
def dockerfile_names
@dockerfile_names ||= Gitlab::Template::DockerfileTemplate.dropdown_names
end
def blob_editor_paths def blob_editor_paths
{ {
'relative-url-root' => Rails.application.config.relative_url_root, 'relative-url-root' => Rails.application.config.relative_url_root,
......
...@@ -7,12 +7,12 @@ module FormHelper ...@@ -7,12 +7,12 @@ module FormHelper
content_tag(:div, class: 'alert alert-danger', id: 'error_explanation') do content_tag(:div, class: 'alert alert-danger', id: 'error_explanation') do
content_tag(:h4, headline) << content_tag(:h4, headline) <<
content_tag(:ul) do content_tag(:ul) do
model.errors.full_messages. model.errors.full_messages.
map { |msg| content_tag(:li, msg) }. map { |msg| content_tag(:li, msg) }.
join. join.
html_safe html_safe
end end
end end
end end
end end
...@@ -36,4 +36,12 @@ module MembersHelper ...@@ -36,4 +36,12 @@ module MembersHelper
"Are you sure you want to leave the " \ "Are you sure you want to leave the " \
"\"#{member_source.human_name}\" #{member_source.class.to_s.humanize(capitalize: false)}?" "\"#{member_source.human_name}\" #{member_source.class.to_s.humanize(capitalize: false)}?"
end end
def filter_group_project_member_path(options = {})
options = params.slice(:search, :sort).merge(options)
path = request.path
path << "?#{options.to_param}"
path
end
end end
...@@ -59,6 +59,10 @@ module MergeRequestsHelper ...@@ -59,6 +59,10 @@ module MergeRequestsHelper
@mr_closes_issues ||= @merge_request.closes_issues @mr_closes_issues ||= @merge_request.closes_issues
end end
def mr_issues_mentioned_but_not_closing
@mr_issues_mentioned_but_not_closing ||= @merge_request.issues_mentioned_but_not_closing
end
def mr_change_branches_path(merge_request) def mr_change_branches_path(merge_request)
new_namespace_project_merge_request_path( new_namespace_project_merge_request_path(
@project.namespace, @project, @project.namespace, @project,
......
...@@ -7,12 +7,12 @@ module NavHelper ...@@ -7,12 +7,12 @@ module NavHelper
def page_gutter_class def page_gutter_class
if current_path?('merge_requests#show') || if current_path?('merge_requests#show') ||
current_path?('merge_requests#diffs') || current_path?('merge_requests#diffs') ||
current_path?('merge_requests#commits') || current_path?('merge_requests#commits') ||
current_path?('merge_requests#builds') || current_path?('merge_requests#builds') ||
current_path?('merge_requests#conflicts') || current_path?('merge_requests#conflicts') ||
current_path?('merge_requests#pipelines') || current_path?('merge_requests#pipelines') ||
current_path?('issues#show') current_path?('issues#show')
if cookies[:collapsed_gutter] == 'true' if cookies[:collapsed_gutter] == 'true'
"page-gutter right-sidebar-collapsed" "page-gutter right-sidebar-collapsed"
else else
...@@ -21,9 +21,9 @@ module NavHelper ...@@ -21,9 +21,9 @@ module NavHelper
elsif current_path?('builds#show') elsif current_path?('builds#show')
"page-gutter build-sidebar right-sidebar-expanded" "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#history') || current_path?('wikis#history') ||
current_path?('wikis#git_access') current_path?('wikis#git_access')
"page-gutter wiki-sidebar right-sidebar-expanded" "page-gutter wiki-sidebar right-sidebar-expanded"
end end
end end
......
...@@ -25,7 +25,7 @@ module SortingHelper ...@@ -25,7 +25,7 @@ module SortingHelper
sort_value_recently_updated => sort_title_recently_updated, sort_value_recently_updated => sort_title_recently_updated,
sort_value_oldest_updated => sort_title_oldest_updated, sort_value_oldest_updated => sort_title_oldest_updated,
sort_value_recently_created => sort_title_recently_created, sort_value_recently_created => sort_title_recently_created,
sort_value_oldest_created => sort_title_oldest_created, sort_value_oldest_created => sort_title_oldest_created
} }
if current_controller?('admin/projects') if current_controller?('admin/projects')
...@@ -35,6 +35,19 @@ module SortingHelper ...@@ -35,6 +35,19 @@ module SortingHelper
options options
end end
def member_sort_options_hash
{
sort_value_access_level_asc => sort_title_access_level_asc,
sort_value_access_level_desc => sort_title_access_level_desc,
sort_value_last_joined => sort_title_last_joined,
sort_value_oldest_joined => sort_title_oldest_joined,
sort_value_name => sort_title_name_asc,
sort_value_name_desc => sort_title_name_desc,
sort_value_recently_signin => sort_title_recently_signin,
sort_value_oldest_signin => sort_title_oldest_signin
}
end
def sort_title_priority def sort_title_priority
'Priority' 'Priority'
end end
...@@ -95,6 +108,50 @@ module SortingHelper ...@@ -95,6 +108,50 @@ module SortingHelper
'Most popular' 'Most popular'
end end
def sort_title_last_joined
'Last joined'
end
def sort_title_oldest_joined
'Oldest joined'
end
def sort_title_access_level_asc
'Access level, ascending'
end
def sort_title_access_level_desc
'Access level, descending'
end
def sort_title_name_asc
'Name, ascending'
end
def sort_title_name_desc
'Name, descending'
end
def sort_value_last_joined
'last_joined'
end
def sort_value_oldest_joined
'oldest_joined'
end
def sort_value_access_level_asc
'access_level_asc'
end
def sort_value_access_level_desc
'access_level_desc'
end
def sort_value_name_desc
'name_desc'
end
def sort_value_priority def sort_value_priority
'priority' 'priority'
end end
......
...@@ -106,9 +106,9 @@ module TabHelper ...@@ -106,9 +106,9 @@ module TabHelper
def branches_tab_class def branches_tab_class
if current_controller?(:protected_branches) || if current_controller?(:protected_branches) ||
current_controller?(:branches) || current_controller?(:branches) ||
current_page?(namespace_project_repository_path(@project.namespace, current_page?(namespace_project_repository_path(@project.namespace,
@project)) @project))
'active' 'active'
end end
end end
......
...@@ -155,7 +155,7 @@ module Ci ...@@ -155,7 +155,7 @@ module Ci
end end
def has_environment? def has_environment?
self.environment.present? environment.present?
end end
def starts_environment? def starts_environment?
...@@ -221,6 +221,7 @@ module Ci ...@@ -221,6 +221,7 @@ module Ci
variables += pipeline.predefined_variables variables += pipeline.predefined_variables
variables += runner.predefined_variables if runner variables += runner.predefined_variables if runner
variables += project.container_registry_variables variables += project.container_registry_variables
variables += project.deployment_variables if has_environment?
variables += yaml_variables variables += yaml_variables
variables += user_variables variables += user_variables
variables += project.secret_variables variables += project.secret_variables
......
...@@ -57,6 +57,11 @@ class Member < ActiveRecord::Base ...@@ -57,6 +57,11 @@ class Member < ActiveRecord::Base
scope :owners, -> { active.where(access_level: OWNER) } scope :owners, -> { active.where(access_level: OWNER) }
scope :owners_and_masters, -> { active.where(access_level: [OWNER, MASTER]) } scope :owners_and_masters, -> { active.where(access_level: [OWNER, MASTER]) }
scope :order_name_asc, -> { left_join_users.reorder(Gitlab::Database.nulls_last_order('users.name', 'ASC')) }
scope :order_name_desc, -> { left_join_users.reorder(Gitlab::Database.nulls_last_order('users.name', 'DESC')) }
scope :order_recent_sign_in, -> { left_join_users.reorder(Gitlab::Database.nulls_last_order('users.last_sign_in_at', 'DESC')) }
scope :order_oldest_sign_in, -> { left_join_users.reorder(Gitlab::Database.nulls_last_order('users.last_sign_in_at', 'ASC')) }
before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? } before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? }
after_create :send_invite, if: :invite?, unless: :importing? after_create :send_invite, if: :invite?, unless: :importing?
...@@ -72,6 +77,34 @@ class Member < ActiveRecord::Base ...@@ -72,6 +77,34 @@ class Member < ActiveRecord::Base
default_value_for :notification_level, NotificationSetting.levels[:global] default_value_for :notification_level, NotificationSetting.levels[:global]
class << self class << self
def search(query)
joins(:user).merge(User.search(query))
end
def sort(method)
case method.to_s
when 'access_level_asc' then reorder(access_level: :asc)
when 'access_level_desc' then reorder(access_level: :desc)
when 'recent_sign_in' then order_recent_sign_in
when 'oldest_sign_in' then order_oldest_sign_in
when 'last_joined' then order_created_desc
when 'oldest_joined' then order_created_asc
else
order_by(method)
end
end
def left_join_users
users = User.arel_table
members = Member.arel_table
member_users = members.join(users, Arel::Nodes::OuterJoin).
on(members[:user_id].eq(users[:id])).
join_sources
joins(member_users)
end
def access_for_user_ids(user_ids) def access_for_user_ids(user_ids)
where(user_id: user_ids).has_access.pluck(:user_id, :access_level).to_h where(user_id: user_ids).has_access.pluck(:user_id, :access_level).to_h
end end
...@@ -89,8 +122,8 @@ class Member < ActiveRecord::Base ...@@ -89,8 +122,8 @@ class Member < ActiveRecord::Base
member = member =
if user.is_a?(User) if user.is_a?(User)
source.members.find_by(user_id: user.id) || source.members.find_by(user_id: user.id) ||
source.requesters.find_by(user_id: user.id) || source.requesters.find_by(user_id: user.id) ||
source.members.build(user_id: user.id) source.members.build(user_id: user.id)
else else
source.members.build(invite_email: user) source.members.build(invite_email: user)
end end
......
...@@ -568,6 +568,19 @@ class MergeRequest < ActiveRecord::Base ...@@ -568,6 +568,19 @@ class MergeRequest < ActiveRecord::Base
end end
end end
def issues_mentioned_but_not_closing(current_user = self.author)
return [] unless target_branch == project.default_branch
ext = Gitlab::ReferenceExtractor.new(project, current_user)
ext.analyze(description)
issues = ext.issues
closing_issues = Gitlab::ClosingIssueExtractor.new(project, current_user).
closed_by_message(description)
issues - closing_issues
end
def target_project_path def target_project_path
if target_project if target_project
target_project.path_with_namespace target_project.path_with_namespace
...@@ -612,13 +625,24 @@ class MergeRequest < ActiveRecord::Base ...@@ -612,13 +625,24 @@ class MergeRequest < ActiveRecord::Base
self.target_project.repository.branch_names.include?(self.target_branch) self.target_project.repository.branch_names.include?(self.target_branch)
end end
def merge_commit_message def merge_commit_message(include_description: false)
message = "Merge branch '#{source_branch}' into '#{target_branch}'\n\n" closes_issues_references = closes_issues.map do |issue|
message << "#{title}\n\n" issue.to_reference(target_project)
message << "#{description}\n\n" if description.present? end
message = [
"Merge branch '#{source_branch}' into '#{target_branch}'",
title
]
if !include_description && closes_issues_references.present?
message << "Closes #{closes_issues_references.to_sentence}"
end
message << "#{description}" if include_description && description.present?
message << "See merge request #{to_reference}" message << "See merge request #{to_reference}"
message message.join("\n\n")
end end
def reset_merge_when_build_succeeds def reset_merge_when_build_succeeds
......
...@@ -161,8 +161,8 @@ module Network ...@@ -161,8 +161,8 @@ module Network
def is_overlap?(range, overlap_space) def is_overlap?(range, overlap_space)
range.each do |i| range.each do |i|
if i != range.first && if i != range.first &&
i != range.last && i != range.last &&
@commits[i].spaces.include?(overlap_space) @commits[i].spaces.include?(overlap_space)
return true return true
end end
......
...@@ -2,6 +2,8 @@ class PersonalAccessToken < ActiveRecord::Base ...@@ -2,6 +2,8 @@ class PersonalAccessToken < ActiveRecord::Base
include TokenAuthenticatable include TokenAuthenticatable
add_authentication_token_field :token add_authentication_token_field :token
serialize :scopes, Array
belongs_to :user belongs_to :user
scope :active, -> { where(revoked: false).where("expires_at >= NOW() OR expires_at IS NULL") } scope :active, -> { where(revoked: false).where("expires_at >= NOW() OR expires_at IS NULL") }
......
...@@ -1230,6 +1230,12 @@ class Project < ActiveRecord::Base ...@@ -1230,6 +1230,12 @@ class Project < ActiveRecord::Base
end end
end end
def deployment_variables
return [] unless deployment_service
deployment_service.predefined_variables
end
def append_or_update_attribute(name, value) def append_or_update_attribute(name, value)
old_values = public_send(name.to_s) old_values = public_send(name.to_s)
......
...@@ -8,4 +8,8 @@ class DeploymentService < Service ...@@ -8,4 +8,8 @@ class DeploymentService < Service
def supported_events def supported_events
[] []
end end
def predefined_variables
[]
end
end end
...@@ -85,8 +85,8 @@ class IssueTrackerService < Service ...@@ -85,8 +85,8 @@ class IssueTrackerService < Service
def enabled_in_gitlab_config def enabled_in_gitlab_config
Gitlab.config.issues_tracker && Gitlab.config.issues_tracker &&
Gitlab.config.issues_tracker.values.any? && Gitlab.config.issues_tracker.values.any? &&
issues_tracker issues_tracker
end end
def issues_tracker def issues_tracker
......
...@@ -83,6 +83,16 @@ class KubernetesService < DeploymentService ...@@ -83,6 +83,16 @@ class KubernetesService < DeploymentService
{ success: false, result: err } { success: false, result: err }
end end
def predefined_variables
variables = [
{ key: 'KUBE_URL', value: api_url, public: true },
{ key: 'KUBE_TOKEN', value: token, public: false },
{ key: 'KUBE_NAMESPACE', value: namespace, public: true }
]
variables << { key: 'KUBE_CA_PEM', value: ca_pem, public: true } if ca_pem.present?
variables
end
private private
def build_kubeclient(api_path = '/api', api_version = 'v1') def build_kubeclient(api_path = '/api', api_version = 'v1')
......
...@@ -178,6 +178,8 @@ class User < ActiveRecord::Base ...@@ -178,6 +178,8 @@ class User < ActiveRecord::Base
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all } scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members WHERE user_id IS NOT NULL AND requested_at IS NULL)') } scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members WHERE user_id IS NOT NULL AND requested_at IS NULL)') }
scope :todo_authors, ->(user_id, state) { where(id: Todo.where(user_id: user_id, state: state).select(:author_id)) } scope :todo_authors, ->(user_id, state) { where(id: Todo.where(user_id: user_id, state: state).select(:author_id)) }
scope :order_recent_sign_in, -> { reorder(last_sign_in_at: :desc) }
scope :order_oldest_sign_in, -> { reorder(last_sign_in_at: :asc) }
def self.with_two_factor def self.with_two_factor
joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id"). joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id").
...@@ -205,8 +207,8 @@ class User < ActiveRecord::Base ...@@ -205,8 +207,8 @@ class User < ActiveRecord::Base
def sort(method) def sort(method)
case method.to_s case method.to_s
when 'recent_sign_in' then reorder(last_sign_in_at: :desc) when 'recent_sign_in' then order_recent_sign_in
when 'oldest_sign_in' then reorder(last_sign_in_at: :asc) when 'oldest_sign_in' then order_oldest_sign_in
else else
order_by(method) order_by(method)
end end
...@@ -390,7 +392,7 @@ class User < ActiveRecord::Base ...@@ -390,7 +392,7 @@ class User < ActiveRecord::Base
def namespace_uniq def namespace_uniq
# Return early if username already failed the first uniqueness validation # Return early if username already failed the first uniqueness validation
return if errors.key?(:username) && return if errors.key?(:username) &&
errors[:username].include?('has already been taken') errors[:username].include?('has already been taken')
existing_namespace = Namespace.by_path(username) existing_namespace = Namespace.by_path(username)
if existing_namespace && existing_namespace != namespace if existing_namespace && existing_namespace != namespace
......
...@@ -12,7 +12,7 @@ class NotePolicy < BasePolicy ...@@ -12,7 +12,7 @@ class NotePolicy < BasePolicy
end end
if @subject.for_merge_request? && if @subject.for_merge_request? &&
@subject.noteable.author == @user @subject.noteable.author == @user
can! :resolve_note can! :resolve_note
end end
end end
......
...@@ -3,7 +3,7 @@ class ProjectPolicy < BasePolicy ...@@ -3,7 +3,7 @@ class ProjectPolicy < BasePolicy
team_access!(user) team_access!(user)
owner = project.owner == user || owner = project.owner == user ||
(project.group && project.group.has_owner?(user)) (project.group && project.group.has_owner?(user))
owner_access! if user.admin? || owner owner_access! if user.admin? || owner
team_member_owner_access! if owner team_member_owner_access! if owner
...@@ -13,7 +13,7 @@ class ProjectPolicy < BasePolicy ...@@ -13,7 +13,7 @@ class ProjectPolicy < BasePolicy
public_access! public_access!
if project.request_access_enabled && if project.request_access_enabled &&
!(owner || user.admin? || project.team.member?(user) || project_group_member?(user)) !(owner || user.admin? || project.team.member?(user) || project_group_member?(user))
can! :request_access can! :request_access
end end
end end
...@@ -244,10 +244,10 @@ class ProjectPolicy < BasePolicy ...@@ -244,10 +244,10 @@ class ProjectPolicy < BasePolicy
def project_group_member?(user) def project_group_member?(user)
project.group && project.group &&
( (
project.group.members.exists?(user_id: user.id) || project.group.members.exists?(user_id: user.id) ||
project.group.requesters.exists?(user_id: user.id) project.group.requesters.exists?(user_id: user.id)
) )
end end
def named_abilities(name) def named_abilities(name)
......
AccessTokenValidationService = Struct.new(:token) do
# Results:
VALID = :valid
EXPIRED = :expired
REVOKED = :revoked
INSUFFICIENT_SCOPE = :insufficient_scope
def validate(scopes: [])
if token.expired?
return EXPIRED
elsif token.revoked?
return REVOKED
elsif !self.include_any_scope?(scopes)
return INSUFFICIENT_SCOPE
else
return VALID
end
end
# True if the token's scope contains any of the passed scopes.
def include_any_scope?(scopes)
if scopes.blank?
true
else
# Check whether the token is allowed access to any of the required scopes.
Set.new(scopes).intersection(Set.new(token.scopes)).present?
end
end
end
...@@ -5,7 +5,7 @@ module Groups ...@@ -5,7 +5,7 @@ module Groups
new_visibility = params[:visibility_level] new_visibility = params[:visibility_level]
if new_visibility && new_visibility.to_i != group.visibility_level if new_visibility && new_visibility.to_i != group.visibility_level
unless can?(current_user, :change_visibility_level, group) && unless can?(current_user, :change_visibility_level, group) &&
Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility) Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
deny_visibility_level(group, new_visibility) deny_visibility_level(group, new_visibility)
return group return group
......
...@@ -10,7 +10,7 @@ module Issues ...@@ -10,7 +10,7 @@ module Issues
end end
if issue.previous_changes.include?('title') || if issue.previous_changes.include?('title') ||
issue.previous_changes.include?('description') issue.previous_changes.include?('description')
todo_service.update_issue(issue, current_user) todo_service.update_issue(issue, current_user)
end end
......
...@@ -42,7 +42,7 @@ module MergeRequests ...@@ -42,7 +42,7 @@ module MergeRequests
end end
if merge_request.source_project == merge_request.target_project && if merge_request.source_project == merge_request.target_project &&
merge_request.target_branch == merge_request.source_branch merge_request.target_branch == merge_request.source_branch
messages << 'You must select different branches' messages << 'You must select different branches'
end end
......
...@@ -25,7 +25,7 @@ module MergeRequests ...@@ -25,7 +25,7 @@ module MergeRequests
end end
if merge_request.previous_changes.include?('title') || if merge_request.previous_changes.include?('title') ||
merge_request.previous_changes.include?('description') merge_request.previous_changes.include?('description')
todo_service.update_merge_request(merge_request, current_user) todo_service.update_merge_request(merge_request, current_user)
end end
......
module Oauth2::AccessTokenValidationService
# Results:
VALID = :valid
EXPIRED = :expired
REVOKED = :revoked
INSUFFICIENT_SCOPE = :insufficient_scope
class << self
def validate(token, scopes: [])
if token.expired?
return EXPIRED
elsif token.revoked?
return REVOKED
elsif !self.sufficient_scope?(token, scopes)
return INSUFFICIENT_SCOPE
else
return VALID
end
end
protected
# True if the token's scope is a superset of required scopes,
# or the required scopes is empty.
def sufficient_scope?(token, scopes)
if scopes.blank?
# if no any scopes required, the scopes of token is sufficient.
return true
else
# If there are scopes required, then check whether
# the set of authorized scopes is a superset of the set of required scopes
required_scopes = Set.new(scopes)
authorized_scopes = Set.new(token.scopes)
return authorized_scopes >= required_scopes
end
end
end
end
...@@ -6,7 +6,7 @@ module Projects ...@@ -6,7 +6,7 @@ module Projects
if new_visibility && new_visibility.to_i != project.visibility_level if new_visibility && new_visibility.to_i != project.visibility_level
unless can?(current_user, :change_visibility_level, project) && unless can?(current_user, :change_visibility_level, project) &&
Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility) Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
deny_visibility_level(project, new_visibility) deny_visibility_level(project, new_visibility)
return project return project
......
...@@ -18,6 +18,12 @@ ...@@ -18,6 +18,12 @@
Use Use
%code= Doorkeeper.configuration.native_redirect_uri %code= Doorkeeper.configuration.native_redirect_uri
for local tests for local tests
.form-group
= f.label :scopes, class: 'col-sm-2 control-label'
.col-sm-10
= render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: application, scopes: @scopes
.form-actions .form-actions
= f.submit 'Submit', class: "btn btn-save wide" = f.submit 'Submit', class: "btn btn-save wide"
= link_to "Cancel", admin_applications_path, class: "btn btn-default" = link_to "Cancel", admin_applications_path, class: "btn btn-default"
...@@ -2,8 +2,7 @@ ...@@ -2,8 +2,7 @@
%h3.page-title %h3.page-title
Application: #{@application.name} Application: #{@application.name}
.table-holder.oauth-application-show
.table-holder
%table.table %table.table
%tr %tr
%td %td
...@@ -23,6 +22,9 @@ ...@@ -23,6 +22,9 @@
- @application.redirect_uri.split.each do |uri| - @application.redirect_uri.split.each do |uri|
%div %div
%span.monospace= uri %span.monospace= uri
= render "shared/tokens/scopes_list", token: @application
.form-actions .form-actions
= link_to 'Edit', edit_admin_application_path(@application), class: 'btn btn-primary wide pull-left' = link_to 'Edit', edit_admin_application_path(@application), class: 'btn btn-primary wide pull-left'
= render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger prepend-left-10' = render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger prepend-left-10'
- status = local_assigns.fetch(:status) - status = local_assigns.fetch(:status)
- css_classes = "ci-status ci-#{status.group}"
- if status.has_details? - if status.has_details?
= link_to status.details_path, class: "ci-status ci-#{status}" do = link_to status.details_path, class: css_classes do
= custom_icon(status.icon) = custom_icon(status.icon)
= status.text = status.text
- else - else
%span{ class: "ci-status ci-#{status}" } %span{ class: css_classes }
= custom_icon(status.icon) = custom_icon(status.icon)
= status.text = status.text
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- subject = local_assigns.fetch(:subject) - subject = local_assigns.fetch(:subject)
- status = subject.detailed_status(current_user) - status = subject.detailed_status(current_user)
- klass = "ci-status-icon ci-status-icon-#{status}" - klass = "ci-status-icon ci-status-icon-#{status.group}"
- if status.has_details? - if status.has_details?
= link_to status.details_path, data: { toggle: 'tooltip', title: "#{subject.name} - #{status.label}" } do = link_to status.details_path, data: { toggle: 'tooltip', title: "#{subject.name} - #{status.label}" } do
......
...@@ -17,5 +17,9 @@ ...@@ -17,5 +17,9 @@
%code= Doorkeeper.configuration.native_redirect_uri %code= Doorkeeper.configuration.native_redirect_uri
for local tests for local tests
.form-group
= f.label :scopes, class: 'label-light'
= render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: application, scopes: @scopes
.prepend-top-default .prepend-top-default
= f.submit 'Save application', class: "btn btn-create" = f.submit 'Save application', class: "btn btn-create"
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
%h3.page-title %h3.page-title
Application: #{@application.name} Application: #{@application.name}
.table-holder .table-holder.oauth-application-show
%table.table %table.table
%tr %tr
%td %td
...@@ -22,6 +22,9 @@ ...@@ -22,6 +22,9 @@
- @application.redirect_uri.split.each do |uri| - @application.redirect_uri.split.each do |uri|
%div %div
%span.monospace= uri %span.monospace= uri
= render "shared/tokens/scopes_list", token: @application
.form-actions .form-actions
= link_to 'Edit', edit_oauth_application_path(@application), class: 'btn btn-primary wide pull-left' = link_to 'Edit', edit_oauth_application_path(@application), class: 'btn btn-primary wide pull-left'
= render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger prepend-left-10' = render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger prepend-left-10'
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
= search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
%button.member-search-btn{ type: "submit", "aria-label" => "Submit search" } %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
= icon("search") = icon("search")
= render 'shared/members/sort_dropdown'
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
Users with access to Users with access to
......
- page_title "Bitbucket import" - page_title 'Bitbucket import'
- header_title "Projects", root_path - header_title 'Projects', root_path
%h3.page-title %h3.page-title
%i.fa.fa-bitbucket %i.fa.fa-bitbucket
Import projects from Bitbucket Import projects from Bitbucket
...@@ -10,13 +11,13 @@ ...@@ -10,13 +11,13 @@
%hr %hr
%p %p
- if @incompatible_repos.any? - if @incompatible_repos.any?
= button_tag class: "btn btn-import btn-success js-import-all" do = button_tag class: 'btn btn-import btn-success js-import-all' do
Import all compatible projects Import all compatible projects
= icon("spinner spin", class: "loading-icon") = icon('spinner spin', class: 'loading-icon')
- else - else
= button_tag class: "btn btn-success js-import-all" do = button_tag class: 'btn btn-import btn-success js-import-all' do
Import all projects Import all projects
= icon("spinner spin", class: "loading-icon") = icon('spinner spin', class: 'loading-icon')
.table-responsive .table-responsive
%table.table.import-jobs %table.table.import-jobs
...@@ -32,7 +33,7 @@ ...@@ -32,7 +33,7 @@
- @already_added_projects.each do |project| - @already_added_projects.each do |project|
%tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
%td %td
= link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: "_blank" = link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: '_blank'
%td %td
= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status %td.job-status
...@@ -47,31 +48,41 @@ ...@@ -47,31 +48,41 @@
= project.human_import_status_name = project.human_import_status_name
- @repos.each do |repo| - @repos.each do |repo|
%tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"} %tr{id: "repo_#{repo.owner}___#{repo.slug}"}
%td %td
= link_to "#{repo["owner"]}/#{repo["slug"]}", "https://bitbucket.org/#{repo["owner"]}/#{repo["slug"]}", target: "_blank" = link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: "_blank"
%td.import-target %td.import-target
= import_project_target(repo['owner'], repo['slug']) %fieldset.row
.input-group
.project-path.input-group-btn
- if current_user.can_select_namespace?
- selected = params[:namespace_id] || :current_user
- opts = current_user.can_create_group? ? { extra_group: Group.new(name: repo.owner, path: repo.owner) } : {}
= select_tag :namespace_id, namespaces_options(selected, opts.merge({ display_path: true })), { class: 'select2 js-select-namespace', tabindex: 1 }
- else
= text_field_tag :path, current_user.namespace_path, class: "input-large form-control", tabindex: 1, disabled: true
%span.input-group-addon /
= text_field_tag :path, repo.name, class: "input-mini form-control", tabindex: 2, autofocus: true, required: true
%td.import-actions.job-status %td.import-actions.job-status
= button_tag class: "btn btn-import js-add-to-import" do = button_tag class: 'btn btn-import js-add-to-import' do
Import Import
= icon("spinner spin", class: "loading-icon") = icon('spinner spin', class: 'loading-icon')
- @incompatible_repos.each do |repo| - @incompatible_repos.each do |repo|
%tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"} %tr{id: "repo_#{repo.owner}___#{repo.slug}"}
%td %td
= link_to "#{repo["owner"]}/#{repo["slug"]}", "https://bitbucket.org/#{repo["owner"]}/#{repo["slug"]}", target: "_blank" = link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank'
%td.import-target %td.import-target
%td.import-actions-job-status %td.import-actions-job-status
= label_tag "Incompatible Project", nil, class: "label label-danger" = label_tag 'Incompatible Project', nil, class: 'label label-danger'
- if @incompatible_repos.any? - if @incompatible_repos.any?
%p %p
One or more of your Bitbucket projects cannot be imported into GitLab One or more of your Bitbucket projects cannot be imported into GitLab
directly because they use Subversion or Mercurial for version control, directly because they use Subversion or Mercurial for version control,
rather than Git. Please convert rather than Git. Please convert
= link_to "them to Git,", "https://www.atlassian.com/git/tutorials/migrating-overview" = link_to 'them to Git,', 'https://www.atlassian.com/git/tutorials/migrating-overview'
and go through the and go through the
= link_to "import flow", status_import_bitbucket_path, "data-no-turbolink" => "true" = link_to 'import flow', status_import_bitbucket_path, 'data-no-turbolink' => 'true'
again. again.
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_path}", import_path: "#{import_bitbucket_path}" } } .js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_path}", import_path: "#{import_bitbucket_path}" } }
...@@ -3,6 +3,14 @@ ...@@ -3,6 +3,14 @@
- if project - if project
:javascript :javascript
GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(project.namespace, project, type: noteable_type, type_id: params[:id])}" gl.GfmAutoComplete.dataSources = {
GitLab.GfmAutoComplete.cachedData = undefined; emojis: "#{emojis_namespace_project_autocomplete_sources_path(project.namespace, project)}",
GitLab.GfmAutoComplete.setup(); members: "#{members_namespace_project_autocomplete_sources_path(project.namespace, project, type: noteable_type, type_id: params[:id])}",
issues: "#{issues_namespace_project_autocomplete_sources_path(project.namespace, project)}",
mergeRequests: "#{merge_requests_namespace_project_autocomplete_sources_path(project.namespace, project)}",
labels: "#{labels_namespace_project_autocomplete_sources_path(project.namespace, project)}",
milestones: "#{milestones_namespace_project_autocomplete_sources_path(project.namespace, project)}",
commands: "#{commands_namespace_project_autocomplete_sources_path(project.namespace, project, type: noteable_type, type_id: params[:id])}"
};
gl.GfmAutoComplete.setup();
...@@ -22,9 +22,10 @@ ...@@ -22,9 +22,10 @@
= render "layouts/nav/#{nav}" = render "layouts/nav/#{nav}"
.content-wrapper{ class: "#{layout_nav_class}" } .content-wrapper{ class: "#{layout_nav_class}" }
= yield :sub_nav = yield :sub_nav
= render "layouts/broadcast" .alert-wrapper
= render "layouts/flash" = render "layouts/broadcast"
= yield :flash_message = render "layouts/flash"
= yield :flash_message
%div{ class: "#{(container_class unless @no_container)} #{@content_class}" } %div{ class: "#{(container_class unless @no_container)} #{@content_class}" }
.content{ id: "content-body" } .content{ id: "content-body" }
= yield = yield
- personal_access_token = local_assigns.fetch(:personal_access_token)
- scopes = local_assigns.fetch(:scopes)
= form_for [:profile, personal_access_token], method: :post, html: { class: 'js-requires-input' } do |f|
= form_errors(personal_access_token)
.form-group
= f.label :name, class: 'label-light'
= f.text_field :name, class: "form-control", required: true
.form-group
= f.label :expires_at, class: 'label-light'
= f.text_field :expires_at, class: "datepicker form-control"
.form-group
= f.label :scopes, class: 'label-light'
= render 'shared/tokens/scopes_form', prefix: 'personal_access_token', token: personal_access_token, scopes: scopes
.prepend-top-default
= f.submit 'Create Personal Access Token', class: "btn btn-create"
...@@ -28,21 +28,8 @@ ...@@ -28,21 +28,8 @@
Add a Personal Access Token Add a Personal Access Token
%p.profile-settings-content %p.profile-settings-content
Pick a name for the application, and we'll give you a unique token. Pick a name for the application, and we'll give you a unique token.
= form_for [:profile, @personal_access_token],
method: :post, html: { class: 'js-requires-input' } do |f|
= form_errors(@personal_access_token) = render "form", personal_access_token: @personal_access_token, scopes: @scopes
.form-group
= f.label :name, class: 'label-light'
= f.text_field :name, class: "form-control", required: true
.form-group
= f.label :expires_at, class: 'label-light'
= f.text_field :expires_at, class: "datepicker form-control", required: false
.prepend-top-default
= f.submit 'Create Personal Access Token', class: "btn btn-create"
%hr %hr
...@@ -56,6 +43,7 @@ ...@@ -56,6 +43,7 @@
%th Name %th Name
%th Created %th Created
%th Expires %th Expires
%th Scopes
%th %th
%tbody %tbody
- @active_personal_access_tokens.each do |token| - @active_personal_access_tokens.each do |token|
...@@ -67,6 +55,7 @@ ...@@ -67,6 +55,7 @@
= token.expires_at.to_date.to_s(:medium) = token.expires_at.to_date.to_s(:medium)
- else - else
%span.personal-access-tokens-never-expires-label Never %span.personal-access-tokens-never-expires-label Never
%td= token.scopes.present? ? token.scopes.join(", ") : "<no scopes selected>"
%td= link_to "Revoke", revoke_profile_personal_access_token_path(token), method: :put, class: "btn btn-danger pull-right", data: { confirm: "Are you sure you want to revoke this token? This action cannot be undone." } %td= link_to "Revoke", revoke_profile_personal_access_token_path(token), method: :put, class: "btn btn-danger pull-right", data: { confirm: "Are you sure you want to revoke this token? This action cannot be undone." }
- else - else
......
...@@ -21,6 +21,8 @@ ...@@ -21,6 +21,8 @@
= dropdown_tag("Choose a .gitignore template", options: { toggle_class: 'btn js-gitignore-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } ) = dropdown_tag("Choose a .gitignore template", options: { toggle_class: 'btn js-gitignore-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
.gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.hidden .gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.hidden
= dropdown_tag("Choose a GitLab CI Yaml template", options: { toggle_class: 'btn js-gitlab-ci-yml-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } ) = dropdown_tag("Choose a GitLab CI Yaml template", options: { toggle_class: 'btn js-gitlab-ci-yml-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } )
.dockerfile-selector.js-dockerfile-selector-wrap.hidden
= dropdown_tag("Choose a Dockerfile template", options: { toggle_class: 'btn js-dockerfile-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: dockerfile_names } } )
= button_tag class: 'soft-wrap-toggle btn', type: 'button' do = button_tag class: 'soft-wrap-toggle btn', type: 'button' do
%span.no-wrap %span.no-wrap
= custom_icon('icon_no_wrap') = custom_icon('icon_no_wrap')
......
...@@ -30,11 +30,18 @@ ...@@ -30,11 +30,18 @@
- elsif @merge_request.can_be_merged? || resolved_conflicts - elsif @merge_request.can_be_merged? || resolved_conflicts
= render 'projects/merge_requests/widget/open/accept' = render 'projects/merge_requests/widget/open/accept'
- if mr_closes_issues.present? - if mr_closes_issues.present? || mr_issues_mentioned_but_not_closing.present?
.mr-widget-footer .mr-widget-footer
%span %span
%i.fa.fa-check = icon('check')
Accepting this merge request will close #{"issue".pluralize(mr_closes_issues.size)} - if mr_closes_issues.present?
= succeed '.' do Accepting this merge request will close #{"issue".pluralize(mr_closes_issues.size)}
!= markdown issues_sentence(mr_closes_issues), pipeline: :gfm, author: @merge_request.author = succeed '.' do
= mr_assign_issues_link != markdown issues_sentence(mr_closes_issues), pipeline: :gfm, author: @merge_request.author
= mr_assign_issues_link
- if mr_issues_mentioned_but_not_closing.present?
#{"Issue".pluralize(mr_issues_mentioned_but_not_closing.size)}
!= markdown issues_sentence(mr_issues_mentioned_but_not_closing), pipeline: :gfm, author: @merge_request.author
#{mr_issues_mentioned_but_not_closing.size > 1 ? 'are' : 'is'} mentioned but will not closed.
...@@ -41,6 +41,8 @@ ...@@ -41,6 +41,8 @@
Modify commit message Modify commit message
.js-toggle-content.hide.prepend-top-default .js-toggle-content.hide.prepend-top-default
= render 'shared/commit_message_container', params: params, = render 'shared/commit_message_container', params: params,
message_with_description: @merge_request.merge_commit_message(include_description: true),
message_without_description: @merge_request.merge_commit_message,
text: @merge_request.merge_commit_message, text: @merge_request.merge_commit_message,
rows: 14, hint: true rows: 14, hint: true
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
= search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
%button.member-search-btn{ type: "submit", "aria-label" => "Submit search" } %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
= icon("search") = icon("search")
= render 'shared/members/sort_dropdown'
- if @group_links.any? - if @group_links.any?
= render 'groups', group_links: @group_links = render 'groups', group_links: @group_links
......
- pretty_path_with_namespace = "#{@project ? @project.namespace.name : 'namespace'} / #{@project ? @project.name : 'name'}" - run_actions_text = "Perform common operations on this project: #{@project.name_with_namespace}"
- run_actions_text = "Perform common operations on this project: #{pretty_path_with_namespace}"
.well .well
This service allows GitLab users to perform common operations on this This service allows GitLab users to perform common operations on this
...@@ -27,7 +26,7 @@ ...@@ -27,7 +26,7 @@
.form-group .form-group
= label_tag :display_name, 'Display name', class: 'col-sm-2 col-xs-12 control-label' = label_tag :display_name, 'Display name', class: 'col-sm-2 col-xs-12 control-label'
.col-sm-10.col-xs-12.input-group .col-sm-10.col-xs-12.input-group
= text_field_tag :display_name, "GitLab / #{pretty_path_with_namespace}", class: 'form-control input-sm', readonly: 'readonly' = text_field_tag :display_name, "GitLab / #{@project.name_with_namespace}", class: 'form-control input-sm', readonly: 'readonly'
.input-group-btn .input-group-btn
= clipboard_button(clipboard_target: '#display_name') = clipboard_button(clipboard_target: '#display_name')
......
.form-group.commit_message-group .form-group.commit_message-group
- nonce = SecureRandom.hex - nonce = SecureRandom.hex
- descriptions = local_assigns.slice(:message_with_description, :message_without_description)
= label_tag "commit_message-#{nonce}", class: 'control-label' do = label_tag "commit_message-#{nonce}", class: 'control-label' do
Commit message Commit message
.col-sm-10 .col-sm-10
...@@ -8,9 +9,17 @@ ...@@ -8,9 +9,17 @@
= text_area_tag 'commit_message', = text_area_tag 'commit_message',
(params[:commit_message] || local_assigns[:text] || local_assigns[:placeholder]), (params[:commit_message] || local_assigns[:text] || local_assigns[:placeholder]),
class: 'form-control js-commit-message', placeholder: local_assigns[:placeholder], class: 'form-control js-commit-message', placeholder: local_assigns[:placeholder],
data: descriptions,
required: true, rows: (local_assigns[:rows] || 3), required: true, rows: (local_assigns[:rows] || 3),
id: "commit_message-#{nonce}" id: "commit_message-#{nonce}"
- if local_assigns[:hint] - if local_assigns[:hint]
%p.hint %p.hint
Try to keep the first line under 52 characters Try to keep the first line under 52 characters
and the others under 72. and the others under 72.
- if descriptions.present?
%p.hint.js-with-description-hint
= link_to "#", class: "js-with-description-link" do
Include description in commit message
%p.hint.js-without-description-hint.hide
= link_to "#", class: "js-without-description-link" do
Don't include description in commit message
.dropdown.inline.member-sort-dropdown
= dropdown_toggle(member_sort_options_hash[@sort], { toggle: 'dropdown' })
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable
%li.dropdown-header
Sort by
- member_sort_options_hash.each do |value, title|
%li
= link_to filter_group_project_member_path(sort: value), class: ("is-active" if @sort == value) do
= title
- scopes = local_assigns.fetch(:scopes)
- prefix = local_assigns.fetch(:prefix)
- token = local_assigns.fetch(:token)
- scopes.each do |scope|
%fieldset
= check_box_tag "#{prefix}[scopes][]", scope, token.scopes.include?(scope), id: "#{prefix}_scopes_#{scope}"
= label_tag "#{prefix}_scopes_#{scope}", scope
%span= "(#{t(scope, scope: [:doorkeeper, :scopes])})"
- token = local_assigns.fetch(:token)
- return unless token.scopes.present?
%tr
%td
Scopes
%td
%ul.scopes-list.append-bottom-0
- token.scopes.each do |scope|
%li
%span.scope-name= scope
= "(#{t(scope, scope: [:doorkeeper, :scopes])})"
---
title: Made comment autocomplete more performant and removed some loading bugs
merge_request: 6856
author:
---
title: Add scopes for personal access tokens and OAuth tokens
merge_request: 5951
author:
---
title: Add sorting functionality for group/project members
merge_request: 7032
author:
---
title: Prevent enviroment table to overflow when name has underscores
merge_request: 8142
author:
---
title: Accept environment variables from the `pre-receive` script
merge_request: 7967
author:
---
title: fix colors and margins for adjacent alert banners
merge_request: 8151
author:
---
title: Refactor Bitbucket importer to use BitBucket API Version 2
merge_request:
author:
---
title: Add support for Dockerfile templates
merge_request: 7247
author:
---
title: Pass variables from deployment project services to CI runner
merge_request: 8107
author:
---
title: Additional rounded label fixes
merge_request:
author:
...@@ -153,6 +153,12 @@ production: &base ...@@ -153,6 +153,12 @@ production: &base
# The location where LFS objects are stored (default: shared/lfs-objects). # The location where LFS objects are stored (default: shared/lfs-objects).
# storage_path: shared/lfs-objects # storage_path: shared/lfs-objects
## Mattermost
## For enabling Add to Mattermost button
mattermost:
enabled: false
host: 'https://mattermost.example.com'
## Gravatar ## Gravatar
## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html ## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html
gravatar: gravatar:
......
...@@ -261,6 +261,13 @@ Settings['lfs'] ||= Settingslogic.new({}) ...@@ -261,6 +261,13 @@ Settings['lfs'] ||= Settingslogic.new({})
Settings.lfs['enabled'] = true if Settings.lfs['enabled'].nil? Settings.lfs['enabled'] = true if Settings.lfs['enabled'].nil?
Settings.lfs['storage_path'] = File.expand_path(Settings.lfs['storage_path'] || File.join(Settings.shared['path'], "lfs-objects"), Rails.root) Settings.lfs['storage_path'] = File.expand_path(Settings.lfs['storage_path'] || File.join(Settings.shared['path'], "lfs-objects"), Rails.root)
#
# Mattermost
#
Settings['mattermost'] ||= Settingslogic.new({})
Settings.mattermost['enabled'] = false if Settings.mattermost['enabled'].nil?
Settings.mattermost['host'] = nil unless Settings.mattermost.enabled
# #
# Gravatar # Gravatar
# #
......
...@@ -52,8 +52,8 @@ Doorkeeper.configure do ...@@ -52,8 +52,8 @@ Doorkeeper.configure do
# Define access token scopes for your provider # Define access token scopes for your provider
# For more information go to # For more information go to
# https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes # https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes
default_scopes :api default_scopes(*Gitlab::Auth::DEFAULT_SCOPES)
# optional_scopes :write, :update optional_scopes(*Gitlab::Auth::OPTIONAL_SCOPES)
# Change the way client credentials are retrieved from the request object. # Change the way client credentials are retrieved from the request object.
# By default it retrieves first from the `HTTP_AUTHORIZATION` header, then # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
......
...@@ -26,3 +26,9 @@ if Gitlab.config.omniauth.enabled ...@@ -26,3 +26,9 @@ if Gitlab.config.omniauth.enabled
end end
end end
end end
module OmniAuth
module Strategies
autoload :Bitbucket, Rails.root.join('lib', 'omniauth', 'strategies', 'bitbucket')
end
end
path = File.expand_path("~/.ssh/bitbucket_rsa.pub")
Gitlab::BitbucketImport.public_key = File.read(path) if File.exist?(path)
...@@ -34,7 +34,7 @@ Sidekiq.configure_server do |config| ...@@ -34,7 +34,7 @@ Sidekiq.configure_server do |config|
# Database pool should be at least `sidekiq_concurrency` + 2 # Database pool should be at least `sidekiq_concurrency` + 2
# For more info, see: https://github.com/mperham/sidekiq/blob/master/4.0-Upgrade.md # For more info, see: https://github.com/mperham/sidekiq/blob/master/4.0-Upgrade.md
config = ActiveRecord::Base.configurations[Rails.env] || config = ActiveRecord::Base.configurations[Rails.env] ||
Rails.application.config.database_configuration[Rails.env] Rails.application.config.database_configuration[Rails.env]
config['pool'] = Sidekiq.options[:concurrency] + 2 config['pool'] = Sidekiq.options[:concurrency] + 2
ActiveRecord::Base.establish_connection(config) ActiveRecord::Base.establish_connection(config)
Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}") Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}")
......
...@@ -59,6 +59,7 @@ en: ...@@ -59,6 +59,7 @@ en:
unknown: "The access token is invalid" unknown: "The access token is invalid"
scopes: scopes:
api: Access your API api: Access your API
read_user: Read user information
flash: flash:
applications: applications:
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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