Merge branch 'master' of https://gitlab.com/gitlab-org/gitlab-ce into issue_17479_todos_not_remove_when_leave_project
This commit is contained in:
commit
ed9b649786
254 changed files with 4724 additions and 941 deletions
|
@ -21,6 +21,7 @@ AllCops:
|
|||
- 'lib/email_validator.rb'
|
||||
- 'lib/gitlab/upgrader.rb'
|
||||
- 'lib/gitlab/seeder.rb'
|
||||
- 'generator_templates/**/*'
|
||||
|
||||
|
||||
##################### Style ##################################
|
||||
|
|
39
CHANGELOG
39
CHANGELOG
|
@ -5,45 +5,54 @@ v 8.8.0 (unreleased)
|
|||
- Fix error when using link to uploads in global snippets
|
||||
- Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen)
|
||||
- Use a case-insensitive comparison in sanitizing URI schemes
|
||||
- Toggle sign-up confirmation emails in application settings
|
||||
- Make it possible to prevent tagged runner from picking untagged jobs
|
||||
- Project#open_branches has been cleaned up and no longer loads entire records into memory.
|
||||
- Escape HTML in commit titles in system note messages
|
||||
- Fix creation of Ci::Commit object which can lead to pending, failed in some scenarios
|
||||
- Improve multiple branch push performance by memoizing permission checking
|
||||
- Log to application.log when an admin starts and stops impersonating a user
|
||||
- Changing the confidentiality of an issue now creates a new system note (Alex Moore-Niemi)
|
||||
- Updated gitlab_git to 10.1.0
|
||||
- GitAccess#protected_tag? no longer loads all tags just to check if a single one exists
|
||||
- Reduce delay in destroying a project from 1-minute to immediately
|
||||
- Make build status canceled if any of the jobs was canceled and none failed
|
||||
- Upgrade Sidekiq to 4.1.2
|
||||
- Added /health_check endpoint for checking service status
|
||||
- Make 'upcoming' filter for milestones work better across projects
|
||||
- Sanitize repo paths in new project error message
|
||||
- Bump mail_room to 0.7.0 to fix stuck IDLE connections
|
||||
- Remove future dates from contribution calendar graph.
|
||||
- Support e-mail notifications for comments on project snippets
|
||||
- Fix API leak of notes of unauthorized issues, snippets and merge requests
|
||||
- Use ActionDispatch Remote IP for Akismet checking
|
||||
- Fix error when visiting commit builds page before build was updated
|
||||
- Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project
|
||||
- Update SVG sanitizer to conform to SVG 1.1
|
||||
- Speed up push emails with multiple recipients by only generating the email once
|
||||
- Updated search UI
|
||||
- Added authentication service for Container Registry
|
||||
- Display informative message when new milestone is created
|
||||
- Sanitize milestones and labels titles
|
||||
- Support multi-line tag messages. !3833 (Calin Seciu)
|
||||
- Force users to reset their password after an admin changes it
|
||||
- Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea)
|
||||
- Added button to toggle whitespaces changes on diff view
|
||||
- Backport GitHub Enterprise import support from EE
|
||||
- Create tags using Rugged for performance reasons. !3745
|
||||
- API: Expose Issue#user_notes_count. !3126 (Anton Popov)
|
||||
- Don't show forks button when user can't view forks
|
||||
- Fix atom feed links and rendering
|
||||
- Files over 5MB can only be viewed in their raw form, files over 1MB without highlighting !3718
|
||||
- Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes)
|
||||
- Add eager load paths to help prevent dependency load issues in Sidekiq workers. !3724
|
||||
- Added multiple colors for labels in dropdowns when dups happen.
|
||||
- Always group commits by server timezone, not commit timestamp
|
||||
- Show commits in the same order as `git log`
|
||||
- Improve description for the Two-factor Authentication sign-in screen. (Connor Shea)
|
||||
- API support for the 'since' and 'until' operators on commit requests (Paco Guzman)
|
||||
- Fix Gravatar hint in user profile when Gravatar is disabled. !3988 (Artem Sidorenko)
|
||||
- Expire repository exists? and has_visible_content? caches after a push if necessary
|
||||
- Fix unintentional filtering bug in issues sorted by milestone due (Takuya Noguchi)
|
||||
- Fix unintentional filtering bug in Issue/MR sorted by milestone due (Takuya Noguchi)
|
||||
- Fix adding a todo for private group members (Ahmad Sherif)
|
||||
- Bump ace-rails-ap gem version from 2.0.1 to 4.0.2 which upgrades Ace Editor from 1.1.2 to 1.2.3
|
||||
- Total method execution timings are no longer tracked
|
||||
|
@ -51,6 +60,16 @@ v 8.8.0 (unreleased)
|
|||
- Add API endpoints for un/subscribing from/to a label. !4051 (Ahmad Sherif)
|
||||
- Hide left sidebar on phone screens to give more space for content
|
||||
- Redesign navigation for profile and group pages
|
||||
- Add counter metrics for rails cache
|
||||
- Import pull requests from GitHub where the source or target branches were removed
|
||||
- All Grape API helpers are now instrumented
|
||||
- Improve Issue formatting for the Slack Service (Jeroen van Baarsen)
|
||||
- Fixed advice on invalid permissions on upload path !2948 (Ludovic Perrine)
|
||||
|
||||
v 8.7.6
|
||||
- Fix links on wiki pages for relative url setups. !4131 (Artem Sidorenko)
|
||||
- Fix import from GitLab.com to a private instance failure. !4181
|
||||
- Fix external imports not finding the import data. !4106
|
||||
|
||||
v 8.7.5
|
||||
- Fix relative links in wiki pages. !4050
|
||||
|
@ -72,6 +91,7 @@ v 8.7.3
|
|||
- Merge request widget displays TeamCity build state and code coverage correctly again.
|
||||
- Fix the line code when importing PR review comments from GitHub. !4010
|
||||
- Wikis are now initialized on legacy projects when checking repositories
|
||||
- Remove animate.css in favor of a smaller subset of animations. !3937 (Connor Shea)
|
||||
|
||||
v 8.7.2
|
||||
- The "New Branch" button is now loaded asynchronously
|
||||
|
@ -880,7 +900,7 @@ v 8.1.3
|
|||
- Use issue editor as cross reference comment author when issue is edited with a new mention
|
||||
- Add Facebook authentication
|
||||
|
||||
v 8.1.2
|
||||
v 8.1.1
|
||||
- Fix cloning Wiki repositories via HTTP (Stan Hu)
|
||||
- Add migration to remove satellites directory
|
||||
- Fix specific runners visibility
|
||||
|
@ -1505,20 +1525,17 @@ v 7.10.0
|
|||
- Fix stuck Merge Request merging events from old installations (Ben Bodenmiller)
|
||||
- Fix merge request comments on files with multiple commits
|
||||
- Fix Resource Owner Password Authentication Flow
|
||||
|
||||
v 7.9.4
|
||||
- Security: Fix project import URL regex to prevent arbitary local repos from being imported
|
||||
- Fixed issue where only 25 commits would load in file listings
|
||||
- Fix LDAP identities after config update
|
||||
|
||||
v 7.9.3
|
||||
- Contains no changes
|
||||
- Add icons to Add dropdown items.
|
||||
- Allow admin to create public deploy keys that are accessible to any project.
|
||||
- Warn when gitlab-shell version doesn't match requirement.
|
||||
- Skip email confirmation when set by admin or via LDAP.
|
||||
- Only allow users to reference groups, projects, issues, MRs, commits they have access to.
|
||||
|
||||
v 7.9.4
|
||||
- Security: Fix project import URL regex to prevent arbitary local repos from being imported
|
||||
- Fixed issue where only 25 commits would load in file listings
|
||||
- Fix LDAP identities after config update
|
||||
|
||||
v 7.9.3
|
||||
- Contains no changes
|
||||
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -36,6 +36,7 @@ gem 'omniauth-shibboleth', '~> 1.2.0'
|
|||
gem 'omniauth-twitter', '~> 1.2.0'
|
||||
gem 'omniauth_crowd', '~> 2.2.0'
|
||||
gem 'rack-oauth2', '~> 1.2.1'
|
||||
gem 'jwt'
|
||||
|
||||
# Spam and anti-bot protection
|
||||
gem 'recaptcha', require: 'recaptcha/rails'
|
||||
|
@ -224,6 +225,7 @@ gem 'request_store', '~> 1.3.0'
|
|||
gem 'select2-rails', '~> 3.5.9'
|
||||
gem 'virtus', '~> 1.0.1'
|
||||
gem 'net-ssh', '~> 3.0.1'
|
||||
gem 'base32', '~> 0.3.0'
|
||||
|
||||
# Sentry integration
|
||||
gem 'sentry-raven', '~> 0.15'
|
||||
|
|
|
@ -70,6 +70,7 @@ GEM
|
|||
ice_nine (~> 0.11.0)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
babosa (1.0.2)
|
||||
base32 (0.3.2)
|
||||
bcrypt (3.1.10)
|
||||
benchmark-ips (2.3.0)
|
||||
better_errors (1.0.1)
|
||||
|
@ -893,6 +894,7 @@ DEPENDENCIES
|
|||
attr_encrypted (~> 1.3.4)
|
||||
awesome_print (~> 1.2.0)
|
||||
babosa (~> 1.0.2)
|
||||
base32 (~> 0.3.0)
|
||||
benchmark-ips
|
||||
better_errors (~> 1.0.1)
|
||||
binding_of_caller (~> 0.7.2)
|
||||
|
@ -954,6 +956,7 @@ DEPENDENCIES
|
|||
jquery-rails (~> 4.1.0)
|
||||
jquery-turbolinks (~> 2.1.0)
|
||||
jquery-ui-rails (~> 5.0.0)
|
||||
jwt
|
||||
kaminari (~> 0.16.3)
|
||||
letter_opener_web (~> 1.3.0)
|
||||
licensee (~> 8.0.0)
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 25 KiB |
Binary file not shown.
Before Width: | Height: | Size: 5.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 4.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 6.5 KiB |
Binary file not shown.
Before Width: | Height: | Size: 74 KiB |
|
@ -10,7 +10,6 @@
|
|||
*= require dropzone/basic
|
||||
*= require cal-heatmap
|
||||
*= require cropper.css
|
||||
*= require animate
|
||||
*/
|
||||
|
||||
/*
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
@import 'framework/tw_bootstrap';
|
||||
@import "framework/layout";
|
||||
|
||||
@import "framework/animations.scss";
|
||||
@import "framework/avatar.scss";
|
||||
@import "framework/blocks.scss";
|
||||
@import "framework/buttons.scss";
|
||||
|
|
72
app/assets/stylesheets/framework/animations.scss
Normal file
72
app/assets/stylesheets/framework/animations.scss
Normal file
|
@ -0,0 +1,72 @@
|
|||
// This file is based off animate.css 3.5.1, available here:
|
||||
// https://github.com/daneden/animate.css/blob/3.5.1/animate.css
|
||||
//
|
||||
// animate.css - http://daneden.me/animate
|
||||
// Version - 3.5.1
|
||||
// Licensed under the MIT license - http://opensource.org/licenses/MIT
|
||||
//
|
||||
// Copyright (c) 2016 Daniel Eden
|
||||
|
||||
.animated {
|
||||
-webkit-animation-duration: 1s;
|
||||
animation-duration: 1s;
|
||||
-webkit-animation-fill-mode: both;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
.animated.infinite {
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
.animated.hinge {
|
||||
-webkit-animation-duration: 2s;
|
||||
animation-duration: 2s;
|
||||
}
|
||||
|
||||
.animated.flipOutX,
|
||||
.animated.flipOutY,
|
||||
.animated.bounceIn,
|
||||
.animated.bounceOut {
|
||||
-webkit-animation-duration: .75s;
|
||||
animation-duration: .75s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes pulse {
|
||||
from {
|
||||
-webkit-transform: scale3d(1, 1, 1);
|
||||
transform: scale3d(1, 1, 1);
|
||||
}
|
||||
|
||||
50% {
|
||||
-webkit-transform: scale3d(1.05, 1.05, 1.05);
|
||||
transform: scale3d(1.05, 1.05, 1.05);
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: scale3d(1, 1, 1);
|
||||
transform: scale3d(1, 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
from {
|
||||
-webkit-transform: scale3d(1, 1, 1);
|
||||
transform: scale3d(1, 1, 1);
|
||||
}
|
||||
|
||||
50% {
|
||||
-webkit-transform: scale3d(1.05, 1.05, 1.05);
|
||||
transform: scale3d(1.05, 1.05, 1.05);
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: scale3d(1, 1, 1);
|
||||
transform: scale3d(1, 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.pulse {
|
||||
-webkit-animation-name: pulse;
|
||||
animation-name: pulse;
|
||||
}
|
|
@ -9,6 +9,8 @@
|
|||
@mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) {
|
||||
.page-with-sidebar {
|
||||
.header-logo {
|
||||
background: $color-darker;
|
||||
|
||||
a {
|
||||
color: $color-light;
|
||||
|
||||
|
@ -88,8 +90,8 @@
|
|||
}
|
||||
|
||||
$theme-blue: #2980b9;
|
||||
$theme-charcoal: #333c47;
|
||||
$theme-graphite: #888;
|
||||
$theme-charcoal: #3d454d;
|
||||
$theme-graphite: #666;
|
||||
$theme-gray: #373737;
|
||||
$theme-green: #019875;
|
||||
$theme-violet: #548;
|
||||
|
@ -100,11 +102,11 @@ body {
|
|||
}
|
||||
|
||||
&.ui_charcoal {
|
||||
@include gitlab-theme(#c5d0de, $theme-charcoal, #2b333d, #24272d);
|
||||
@include gitlab-theme(#d6d7d9, #485157, $theme-charcoal, #353b41);
|
||||
}
|
||||
|
||||
&.ui_graphite {
|
||||
@include gitlab-theme(#ccc, $theme-graphite, #777, #666);
|
||||
@include gitlab-theme(#ccc, #777, $theme-graphite, #555);
|
||||
}
|
||||
|
||||
&.ui_gray {
|
||||
|
|
|
@ -12,7 +12,7 @@ $gutter_inner_width: 258px;
|
|||
*/
|
||||
$border-color: #e5e5e5;
|
||||
$focus-border-color: #3aabf0;
|
||||
$table-border-color: #ececec;
|
||||
$table-border-color: #f0f0f0;
|
||||
$background-color: #fafafa;
|
||||
|
||||
/*
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.zen-cotrol {
|
||||
.zen-control {
|
||||
padding: 0;
|
||||
color: #555;
|
||||
background: none;
|
||||
|
|
43
app/assets/stylesheets/mailers/repository_push_email.scss
Normal file
43
app/assets/stylesheets/mailers/repository_push_email.scss
Normal file
|
@ -0,0 +1,43 @@
|
|||
@import "framework/variables";
|
||||
|
||||
table.code {
|
||||
width: 100%;
|
||||
font-family: monospace;
|
||||
border: none;
|
||||
border-collapse: separate;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
-premailer-width: 100%;
|
||||
|
||||
td {
|
||||
line-height: $code_line_height;
|
||||
font-family: monospace;
|
||||
font-size: $code_font_size;
|
||||
}
|
||||
|
||||
td.diff-line-num {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: $background-color;
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
padding: 0 5px;
|
||||
border-right: 1px solid $border-color;
|
||||
text-align: right;
|
||||
min-width: 35px;
|
||||
max-width: 50px;
|
||||
width: 35px;
|
||||
}
|
||||
|
||||
td.line_content {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0 0.5em;
|
||||
border: none;
|
||||
white-space: pre;
|
||||
}
|
||||
}
|
||||
|
||||
@import "highlight/white";
|
4
app/assets/stylesheets/pages/pipelines.scss
Normal file
4
app/assets/stylesheets/pages/pipelines.scss
Normal file
|
@ -0,0 +1,4 @@
|
|||
.pipeline-stage {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
}
|
||||
.no-ssh-key-message, .project-limit-message {
|
||||
background-color: #f28d35;
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.new_project,
|
||||
.edit_project {
|
||||
|
|
|
@ -9,6 +9,6 @@ class Admin::AbuseReportsController < Admin::ApplicationController
|
|||
abuse_report.remove_user(deleted_by: current_user) if params[:remove_user]
|
||||
abuse_report.destroy
|
||||
|
||||
render nothing: true
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
|
|
|
@ -106,6 +106,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
|
|||
:email_author_in_body,
|
||||
:repository_checks_enabled,
|
||||
:metrics_packet_size,
|
||||
:send_user_confirmation_email,
|
||||
restricted_visibility_levels: [],
|
||||
import_sources: [],
|
||||
disabled_oauth_sign_in_sources: []
|
||||
|
|
|
@ -32,7 +32,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
format.html { redirect_back_or_default(default: { action: 'index' }) }
|
||||
format.js { render nothing: true }
|
||||
format.js { head :ok }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ class Admin::KeysController < Admin::ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.js { render nothing: true }
|
||||
format.js { head :ok }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -9,23 +9,18 @@ class Admin::RunnersController < Admin::ApplicationController
|
|||
end
|
||||
|
||||
def show
|
||||
@builds = @runner.builds.order('id DESC').first(30)
|
||||
@projects =
|
||||
if params[:search].present?
|
||||
::Project.search(params[:search])
|
||||
else
|
||||
Project.all
|
||||
end
|
||||
@projects = @projects.where.not(id: @runner.projects.select(:id)) if @runner.projects.any?
|
||||
@projects = @projects.page(params[:page]).per(30)
|
||||
assign_builds_and_projects
|
||||
end
|
||||
|
||||
def update
|
||||
@runner.update_attributes(runner_params)
|
||||
|
||||
respond_to do |format|
|
||||
format.js
|
||||
format.html { redirect_to admin_runner_path(@runner) }
|
||||
if @runner.update_attributes(runner_params)
|
||||
respond_to do |format|
|
||||
format.js
|
||||
format.html { redirect_to admin_runner_path(@runner) }
|
||||
end
|
||||
else
|
||||
assign_builds_and_projects
|
||||
render 'show'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -60,4 +55,16 @@ class Admin::RunnersController < Admin::ApplicationController
|
|||
def runner_params
|
||||
params.require(:runner).permit(Ci::Runner::FORM_EDITABLE)
|
||||
end
|
||||
|
||||
def assign_builds_and_projects
|
||||
@builds = runner.builds.order('id DESC').first(30)
|
||||
@projects =
|
||||
if params[:search].present?
|
||||
::Project.search(params[:search])
|
||||
else
|
||||
Project.all
|
||||
end
|
||||
@projects = @projects.where.not(id: runner.projects.select(:id)) if runner.projects.any?
|
||||
@projects = @projects.page(params[:page]).per(30)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,7 +11,7 @@ class Admin::SpamLogsController < Admin::ApplicationController
|
|||
redirect_to admin_spam_logs_path, notice: "User #{spam_log.user.username} was successfully removed."
|
||||
else
|
||||
spam_log.destroy
|
||||
render nothing: true
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -119,6 +119,7 @@ class Admin::UsersController < Admin::ApplicationController
|
|||
user_params_with_pass.merge!(
|
||||
password: params[:user][:password],
|
||||
password_confirmation: params[:user][:password_confirmation],
|
||||
password_expires_at: Time.now
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -153,7 +154,7 @@ class Admin::UsersController < Admin::ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
format.html { redirect_back_or_admin_user(notice: "Successfully removed email.") }
|
||||
format.js { render nothing: true }
|
||||
format.js { head :ok }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ module ToggleSubscriptionAction
|
|||
|
||||
subscribable_resource.toggle_subscription(current_user)
|
||||
|
||||
render nothing: true
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -12,7 +12,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to dashboard_todos_path, notice: todo_notice }
|
||||
format.js { render nothing: true }
|
||||
format.js { head :ok }
|
||||
format.json do
|
||||
render json: { count: @todos.size, done_count: current_user.todos.done.count }
|
||||
end
|
||||
|
@ -24,7 +24,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to dashboard_todos_path, notice: 'All todos were marked as done.' }
|
||||
format.js { render nothing: true }
|
||||
format.js { head :ok }
|
||||
format.json do
|
||||
find_todos
|
||||
render json: { count: @todos.size, done_count: current_user.todos.done.count }
|
||||
|
|
|
@ -40,7 +40,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' }
|
||||
format.js { render nothing: true }
|
||||
format.js { head :ok }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
87
app/controllers/jwt_controller.rb
Normal file
87
app/controllers/jwt_controller.rb
Normal file
|
@ -0,0 +1,87 @@
|
|||
class JwtController < ApplicationController
|
||||
skip_before_action :authenticate_user!
|
||||
skip_before_action :verify_authenticity_token
|
||||
before_action :authenticate_project_or_user
|
||||
|
||||
SERVICES = {
|
||||
Auth::ContainerRegistryAuthenticationService::AUDIENCE => Auth::ContainerRegistryAuthenticationService,
|
||||
}
|
||||
|
||||
def auth
|
||||
service = SERVICES[params[:service]]
|
||||
return head :not_found unless service
|
||||
|
||||
result = service.new(@project, @user, auth_params).execute
|
||||
|
||||
render json: result, status: result[:http_status]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authenticate_project_or_user
|
||||
authenticate_with_http_basic do |login, password|
|
||||
# if it's possible we first try to authenticate project with login and password
|
||||
@project = authenticate_project(login, password)
|
||||
return if @project
|
||||
|
||||
@user = authenticate_user(login, password)
|
||||
return if @user
|
||||
|
||||
render_403
|
||||
end
|
||||
end
|
||||
|
||||
def auth_params
|
||||
params.permit(:service, :scope, :offline_token, :account, :client_id)
|
||||
end
|
||||
|
||||
def authenticate_project(login, password)
|
||||
if login == 'gitlab_ci_token'
|
||||
Project.find_by(builds_enabled: true, runners_token: password)
|
||||
end
|
||||
end
|
||||
|
||||
def authenticate_user(login, password)
|
||||
# TODO: this is a copy and paste from grack_auth,
|
||||
# it should be refactored in the future
|
||||
|
||||
user = Gitlab::Auth.new.find(login, password)
|
||||
|
||||
# If the user authenticated successfully, we reset the auth failure count
|
||||
# from Rack::Attack for that IP. A client may attempt to authenticate
|
||||
# with a username and blank password first, and only after it receives
|
||||
# a 401 error does it present a password. Resetting the count prevents
|
||||
# false positives from occurring.
|
||||
#
|
||||
# Otherwise, we let Rack::Attack know there was a failed authentication
|
||||
# attempt from this IP. This information is stored in the Rails cache
|
||||
# (Redis) and will be used by the Rack::Attack middleware to decide
|
||||
# whether to block requests from this IP.
|
||||
config = Gitlab.config.rack_attack.git_basic_auth
|
||||
|
||||
if config.enabled
|
||||
if user
|
||||
# A successful login will reset the auth failure count from this IP
|
||||
Rack::Attack::Allow2Ban.reset(request.ip, config)
|
||||
else
|
||||
banned = Rack::Attack::Allow2Ban.filter(request.ip, config) do
|
||||
# Unless the IP is whitelisted, return true so that Allow2Ban
|
||||
# increments the counter (stored in Rails.cache) for the IP
|
||||
if config.ip_whitelist.include?(request.ip)
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
if banned
|
||||
Rails.logger.info "IP #{request.ip} failed to login " \
|
||||
"as #{login} but has been temporarily banned from Git auth"
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
user
|
||||
end
|
||||
end
|
|
@ -24,7 +24,7 @@ class Profiles::EmailsController < Profiles::ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to profile_emails_url }
|
||||
format.js { render nothing: true }
|
||||
format.js { head :ok }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ class Profiles::KeysController < Profiles::ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to profile_keys_url }
|
||||
format.js { render nothing: true }
|
||||
format.js { head :ok }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
34
app/controllers/projects/container_registry_controller.rb
Normal file
34
app/controllers/projects/container_registry_controller.rb
Normal file
|
@ -0,0 +1,34 @@
|
|||
class Projects::ContainerRegistryController < Projects::ApplicationController
|
||||
before_action :verify_registry_enabled
|
||||
before_action :authorize_read_container_image!
|
||||
before_action :authorize_update_container_image!, only: [:destroy]
|
||||
layout 'project'
|
||||
|
||||
def index
|
||||
@tags = container_registry_repository.tags
|
||||
end
|
||||
|
||||
def destroy
|
||||
url = namespace_project_container_registry_index_path(project.namespace, project)
|
||||
|
||||
if tag.delete
|
||||
redirect_to url
|
||||
else
|
||||
redirect_to url, alert: 'Failed to remove tag'
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def verify_registry_enabled
|
||||
render_404 unless Gitlab.config.registry.enabled
|
||||
end
|
||||
|
||||
def container_registry_repository
|
||||
@container_registry_repository ||= project.container_registry_repository
|
||||
end
|
||||
|
||||
def tag
|
||||
@tag ||= container_registry_repository.tag(params[:id])
|
||||
end
|
||||
end
|
|
@ -20,6 +20,7 @@ class Projects::ImportsController < Projects::ApplicationController
|
|||
@project.import_retry
|
||||
else
|
||||
@project.import_start
|
||||
@project.add_import_job
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ class Projects::MilestonesController < Projects::ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to namespace_project_milestones_path }
|
||||
format.js { render nothing: true }
|
||||
format.js { head :ok }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ class Projects::NotesController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.js { render nothing: true }
|
||||
format.js { head :ok }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -52,7 +52,7 @@ class Projects::NotesController < Projects::ApplicationController
|
|||
note.update_attribute(:attachment, nil)
|
||||
|
||||
respond_to do |format|
|
||||
format.js { render nothing: true }
|
||||
format.js { head :ok }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
59
app/controllers/projects/pipelines_controller.rb
Normal file
59
app/controllers/projects/pipelines_controller.rb
Normal file
|
@ -0,0 +1,59 @@
|
|||
class Projects::PipelinesController < Projects::ApplicationController
|
||||
before_action :pipeline, except: [:index, :new, :create]
|
||||
before_action :commit, only: [:show]
|
||||
before_action :authorize_read_pipeline!
|
||||
before_action :authorize_create_pipeline!, only: [:new, :create]
|
||||
before_action :authorize_update_pipeline!, only: [:retry, :cancel]
|
||||
|
||||
def index
|
||||
@scope = params[:scope]
|
||||
all_pipelines = project.ci_commits
|
||||
@pipelines_count = all_pipelines.count
|
||||
@running_or_pending_count = all_pipelines.running_or_pending.count
|
||||
@pipelines = PipelinesFinder.new(project).execute(all_pipelines, @scope)
|
||||
@pipelines = @pipelines.order(id: :desc).page(params[:page]).per(30)
|
||||
end
|
||||
|
||||
def new
|
||||
@pipeline = project.ci_commits.new(ref: @project.default_branch)
|
||||
end
|
||||
|
||||
def create
|
||||
@pipeline = Ci::CreatePipelineService.new(project, current_user, create_params).execute
|
||||
unless @pipeline.persisted?
|
||||
render 'new'
|
||||
return
|
||||
end
|
||||
|
||||
redirect_to namespace_project_pipeline_path(project.namespace, project, @pipeline)
|
||||
end
|
||||
|
||||
def show
|
||||
end
|
||||
|
||||
def retry
|
||||
pipeline.retry_failed
|
||||
|
||||
redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project)
|
||||
end
|
||||
|
||||
def cancel
|
||||
pipeline.cancel_running
|
||||
|
||||
redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_params
|
||||
params.require(:pipeline).permit(:ref)
|
||||
end
|
||||
|
||||
def pipeline
|
||||
@pipeline ||= project.ci_commits.find_by!(id: params[:id])
|
||||
end
|
||||
|
||||
def commit
|
||||
@commit ||= @pipeline.commit_data
|
||||
end
|
||||
end
|
|
@ -55,7 +55,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
|
|||
format.html do
|
||||
redirect_to namespace_project_project_members_path(@project.namespace, @project)
|
||||
end
|
||||
format.js { render nothing: true }
|
||||
format.js { head :ok }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -81,7 +81,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to dashboard_projects_path, notice: "You left the project." }
|
||||
format.js { render nothing: true }
|
||||
format.js { head :ok }
|
||||
end
|
||||
else
|
||||
if current_user == @project.owner
|
||||
|
|
|
@ -39,7 +39,7 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to namespace_project_protected_branches_path }
|
||||
format.js { render nothing: true }
|
||||
format.js { head :ok }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ class Projects::RunnersController < Projects::ApplicationController
|
|||
if @runner.update_attributes(runner_params)
|
||||
redirect_to runner_path(@runner), notice: 'Runner was successfully updated.'
|
||||
else
|
||||
redirect_to runner_path(@runner), alert: 'Runner was not updated.'
|
||||
render 'edit'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -3,20 +3,44 @@ class Projects::VariablesController < Projects::ApplicationController
|
|||
|
||||
layout 'project_settings'
|
||||
|
||||
def index
|
||||
@variable = Ci::Variable.new
|
||||
end
|
||||
|
||||
def show
|
||||
@variable = @project.variables.find(params[:id])
|
||||
end
|
||||
|
||||
def update
|
||||
if project.update_attributes(project_params)
|
||||
@variable = @project.variables.find(params[:id])
|
||||
|
||||
if @variable.update_attributes(project_params)
|
||||
redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variable was successfully updated.'
|
||||
else
|
||||
render action: "show"
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
@variable = Ci::Variable.new(project_params)
|
||||
|
||||
if @variable.valid? && @project.variables << @variable
|
||||
redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variables were successfully updated.'
|
||||
else
|
||||
render action: 'show'
|
||||
render action: "index"
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@key = @project.variables.find(params[:id])
|
||||
@key.destroy
|
||||
|
||||
redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variable was successfully removed.'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def project_params
|
||||
params.require(:project).permit({ variables_attributes: [:id, :key, :value, :_destroy] })
|
||||
params.require(:variable).permit([:id, :key, :value, :_destroy])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -235,7 +235,8 @@ class ProjectsController < Projects::ApplicationController
|
|||
def project_params
|
||||
params.require(:project).permit(
|
||||
:name, :path, :description, :issues_tracker, :tag_list, :runners_token,
|
||||
:issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch,
|
||||
:issues_enabled, :merge_requests_enabled, :snippets_enabled, :container_registry_enabled,
|
||||
:issues_tracker_id, :default_branch,
|
||||
:wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
|
||||
:builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
|
||||
:public_builds,
|
||||
|
|
|
@ -37,8 +37,8 @@ class RegistrationsController < Devise::RegistrationsController
|
|||
super
|
||||
end
|
||||
|
||||
def after_sign_up_path_for(_resource)
|
||||
users_almost_there_path
|
||||
def after_sign_up_path_for(user)
|
||||
user.confirmed_at.present? ? dashboard_projects_path : users_almost_there_path
|
||||
end
|
||||
|
||||
def after_inactive_sign_up_path_for(_resource)
|
||||
|
|
|
@ -252,8 +252,8 @@ class IssuableFinder
|
|||
if filter_by_no_milestone?
|
||||
items = items.where(milestone_id: [-1, nil])
|
||||
elsif filter_by_upcoming_milestone?
|
||||
upcoming = Milestone.where(project_id: projects).upcoming
|
||||
items = items.joins(:milestone).where(milestones: { title: upcoming.try(:title) })
|
||||
upcoming_ids = Milestone.upcoming_ids_by_projects(projects)
|
||||
items = items.joins(:milestone).where(milestone_id: upcoming_ids)
|
||||
else
|
||||
items = items.joins(:milestone).where(milestones: { title: params[:milestone_title] })
|
||||
|
||||
|
|
38
app/finders/pipelines_finder.rb
Normal file
38
app/finders/pipelines_finder.rb
Normal file
|
@ -0,0 +1,38 @@
|
|||
class PipelinesFinder
|
||||
attr_reader :project
|
||||
|
||||
def initialize(project)
|
||||
@project = project
|
||||
end
|
||||
|
||||
def execute(pipelines, scope)
|
||||
case scope
|
||||
when 'running'
|
||||
pipelines.running_or_pending
|
||||
when 'branches'
|
||||
from_ids(pipelines, ids_for_ref(pipelines, branches))
|
||||
when 'tags'
|
||||
from_ids(pipelines, ids_for_ref(pipelines, tags))
|
||||
else
|
||||
pipelines
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ids_for_ref(pipelines, refs)
|
||||
pipelines.where(ref: refs).group(:ref).select('max(id)')
|
||||
end
|
||||
|
||||
def from_ids(pipelines, ids)
|
||||
pipelines.unscoped.where(id: ids)
|
||||
end
|
||||
|
||||
def branches
|
||||
project.repository.branches.map(&:name)
|
||||
end
|
||||
|
||||
def tags
|
||||
project.repository.tags.map(&:name)
|
||||
end
|
||||
end
|
|
@ -36,7 +36,7 @@ class TodosFinder
|
|||
private
|
||||
|
||||
def action_id?
|
||||
action_id.present? && [Todo::ASSIGNED, Todo::MENTIONED].include?(action_id.to_i)
|
||||
action_id.present? && [Todo::ASSIGNED, Todo::MENTIONED, Todo::BUILD_FAILED].include?(action_id.to_i)
|
||||
end
|
||||
|
||||
def action_id
|
||||
|
|
|
@ -38,19 +38,30 @@ module CiStatusHelper
|
|||
icon(icon_name + ' fw')
|
||||
end
|
||||
|
||||
def render_ci_status(ci_commit, tooltip_placement: 'auto left')
|
||||
# TODO: split this method into
|
||||
# - render_commit_status
|
||||
# - render_pipeline_status
|
||||
link_to ci_icon_for_status(ci_commit.status),
|
||||
ci_status_path(ci_commit),
|
||||
class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}",
|
||||
title: "Build #{ci_label_for_status(ci_commit.status)}",
|
||||
data: { toggle: 'tooltip', placement: tooltip_placement }
|
||||
def render_commit_status(commit, tooltip_placement: 'auto left')
|
||||
project = commit.project
|
||||
path = builds_namespace_project_commit_path(project.namespace, project, commit)
|
||||
render_status_with_link('commit', commit.status, path, tooltip_placement)
|
||||
end
|
||||
|
||||
def render_pipeline_status(pipeline, tooltip_placement: 'auto left')
|
||||
project = pipeline.project
|
||||
path = namespace_project_pipeline_path(project.namespace, project, pipeline)
|
||||
render_status_with_link('pipeline', pipeline.status, path, tooltip_placement)
|
||||
end
|
||||
|
||||
def no_runners_for_project?(project)
|
||||
project.runners.blank? &&
|
||||
Ci::Runner.shared.blank?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def render_status_with_link(type, status, path, tooltip_placement)
|
||||
link_to ci_icon_for_status(status),
|
||||
path,
|
||||
class: "ci-status-link ci-status-icon-#{status.dasherize}",
|
||||
title: "#{type.titleize}: #{ci_label_for_status(status)}",
|
||||
data: { toggle: 'tooltip', placement: tooltip_placement }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,12 +32,6 @@ module EmailsHelper
|
|||
nil
|
||||
end
|
||||
|
||||
def color_email_diff(diffcontent)
|
||||
formatter = Rouge::Formatters::HTML.new(css_class: 'highlight', inline_theme: 'github')
|
||||
lexer = Rouge::Lexers::Diff
|
||||
raw formatter.format(lexer.lex(diffcontent))
|
||||
end
|
||||
|
||||
def password_reset_token_valid_time
|
||||
valid_hours = Devise.reset_password_within / 60 / 60
|
||||
if valid_hours >= 24
|
||||
|
|
|
@ -3,7 +3,7 @@ module EventsHelper
|
|||
author = event.author
|
||||
|
||||
if author
|
||||
link_to author.name, user_path(author.username), title: h(author.name)
|
||||
link_to author.name, user_path(author.username), title: author.name
|
||||
else
|
||||
event.author_name
|
||||
end
|
||||
|
@ -57,11 +57,7 @@ module EventsHelper
|
|||
words << event.ref_name
|
||||
words << "at"
|
||||
elsif event.commented?
|
||||
if event.note_commit?
|
||||
words << event.note_short_commit_id
|
||||
else
|
||||
words << "##{truncate event.note_target_iid}"
|
||||
end
|
||||
words << event.note_target_reference
|
||||
words << "at"
|
||||
elsif event.milestone?
|
||||
words << "##{event.target_iid}" if event.target_iid
|
||||
|
@ -84,21 +80,12 @@ module EventsHelper
|
|||
elsif event.merge_request?
|
||||
namespace_project_merge_request_url(event.project.namespace,
|
||||
event.project, event.merge_request)
|
||||
elsif event.note? && event.note_commit?
|
||||
elsif event.note? && event.commit_note?
|
||||
namespace_project_commit_url(event.project.namespace, event.project,
|
||||
event.note_target)
|
||||
elsif event.note?
|
||||
if event.note_target
|
||||
if event.note_commit?
|
||||
namespace_project_commit_path(event.project.namespace, event.project,
|
||||
event.note_commit_id,
|
||||
anchor: dom_id(event.target))
|
||||
elsif event.note_project_snippet?
|
||||
namespace_project_snippet_path(event.project.namespace,
|
||||
event.project, event.note_target)
|
||||
else
|
||||
event_note_target_path(event)
|
||||
end
|
||||
event_note_target_path(event)
|
||||
end
|
||||
elsif event.push?
|
||||
push_event_feed_url(event)
|
||||
|
@ -134,42 +121,30 @@ module EventsHelper
|
|||
end
|
||||
|
||||
def event_note_target_path(event)
|
||||
if event.note? && event.note_commit?
|
||||
namespace_project_commit_path(event.project.namespace, event.project,
|
||||
event.note_target)
|
||||
if event.note? && event.commit_note?
|
||||
namespace_project_commit_path(event.project.namespace,
|
||||
event.project,
|
||||
event.note_target,
|
||||
anchor: dom_id(event.target))
|
||||
elsif event.project_snippet_note?
|
||||
namespace_project_snippet_path(event.project.namespace,
|
||||
event.project,
|
||||
event.note_target,
|
||||
anchor: dom_id(event.target))
|
||||
else
|
||||
polymorphic_path([event.project.namespace.becomes(Namespace),
|
||||
event.project, event.note_target],
|
||||
anchor: dom_id(event.target))
|
||||
anchor: dom_id(event.target))
|
||||
end
|
||||
end
|
||||
|
||||
def event_note_title_html(event)
|
||||
if event.note_target
|
||||
if event.note_commit?
|
||||
link_to(
|
||||
namespace_project_commit_path(event.project.namespace, event.project,
|
||||
event.note_commit_id,
|
||||
anchor: dom_id(event.target), title: h(event.target_title)),
|
||||
class: "commit_short_id"
|
||||
) do
|
||||
"#{event.note_target_type} #{event.note_short_commit_id}"
|
||||
end
|
||||
elsif event.note_project_snippet?
|
||||
link_to(namespace_project_snippet_path(event.project.namespace,
|
||||
event.project,
|
||||
event.note_target), title: h(event.project.name)) do
|
||||
"#{event.note_target_type} #{truncate event.note_target.to_reference}"
|
||||
end
|
||||
else
|
||||
link_to event_note_target_path(event) do
|
||||
"#{event.note_target_type} #{truncate event.note_target.to_reference}"
|
||||
end
|
||||
link_to(event_note_target_path(event), title: event.target_title, class: 'has-tooltip') do
|
||||
"#{event.note_target_type} #{event.note_target_reference}"
|
||||
end
|
||||
else
|
||||
content_tag :strong do
|
||||
"(deleted)"
|
||||
end
|
||||
content_tag(:strong, '(deleted)')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -33,6 +33,10 @@ module GitlabRoutingHelper
|
|||
namespace_project_builds_path(project.namespace, project, *args)
|
||||
end
|
||||
|
||||
def project_container_registry_path(project, *args)
|
||||
namespace_project_container_registry_index_path(project.namespace, project, *args)
|
||||
end
|
||||
|
||||
def activity_project_path(project, *args)
|
||||
activity_namespace_project_path(project.namespace, project, *args)
|
||||
end
|
||||
|
|
|
@ -124,11 +124,7 @@ module ProjectsHelper
|
|||
end
|
||||
|
||||
def license_short_name(project)
|
||||
no_license_key = project.repository.license_key.nil? ||
|
||||
# Back-compat if cache contains 'no-license', can be removed in a few weeks
|
||||
project.repository.license_key == 'no-license'
|
||||
|
||||
return 'LICENSE' if no_license_key
|
||||
return 'LICENSE' if project.repository.license_key.nil?
|
||||
|
||||
license = Licensee::License.new(project.repository.license_key)
|
||||
|
||||
|
@ -152,6 +148,10 @@ module ProjectsHelper
|
|||
nav_tabs << :builds
|
||||
end
|
||||
|
||||
if Gitlab.config.registry.enabled && can?(current_user, :read_container_image, project)
|
||||
nav_tabs << :container_registry
|
||||
end
|
||||
|
||||
if can?(current_user, :admin_project, project)
|
||||
nav_tabs << :settings
|
||||
end
|
||||
|
|
|
@ -11,6 +11,7 @@ module TodosHelper
|
|||
case todo.action
|
||||
when Todo::ASSIGNED then 'assigned you'
|
||||
when Todo::MENTIONED then 'mentioned you on'
|
||||
when Todo::BUILD_FAILED then 'The build failed for your'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -28,8 +29,11 @@ module TodosHelper
|
|||
namespace_project_commit_path(todo.project.namespace.becomes(Namespace), todo.project,
|
||||
todo.target, anchor: anchor)
|
||||
else
|
||||
polymorphic_path([todo.project.namespace.becomes(Namespace),
|
||||
todo.project, todo.target], anchor: anchor)
|
||||
path = [todo.project.namespace.becomes(Namespace), todo.project, todo.target]
|
||||
|
||||
path.unshift(:builds) if todo.build_failed?
|
||||
|
||||
polymorphic_path(path, anchor: anchor)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -65,7 +65,8 @@ module Emails
|
|||
|
||||
# used in notify layout
|
||||
@target_url = @message.target_url
|
||||
@project = Project.find project_id
|
||||
@project = Project.find(project_id)
|
||||
@diff_notes_disabled = true
|
||||
|
||||
add_project_headers
|
||||
headers['X-GitLab-Author'] = @message.author_username
|
||||
|
|
|
@ -10,6 +10,8 @@ class Notify < BaseMailer
|
|||
include Emails::Builds
|
||||
|
||||
add_template_helper MergeRequestsHelper
|
||||
add_template_helper DiffHelper
|
||||
add_template_helper BlobHelper
|
||||
add_template_helper EmailsHelper
|
||||
|
||||
def test_email(recipient_email, subject, body)
|
||||
|
|
|
@ -61,6 +61,7 @@ class Ability
|
|||
:read_merge_request,
|
||||
:read_note,
|
||||
:read_commit_status,
|
||||
:read_container_image,
|
||||
:download_code
|
||||
]
|
||||
|
||||
|
@ -203,6 +204,8 @@ class Ability
|
|||
:admin_label,
|
||||
:read_commit_status,
|
||||
:read_build,
|
||||
:read_container_image,
|
||||
:read_pipeline,
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -214,9 +217,13 @@ class Ability
|
|||
:update_commit_status,
|
||||
:create_build,
|
||||
:update_build,
|
||||
:create_pipeline,
|
||||
:update_pipeline,
|
||||
:create_merge_request,
|
||||
:create_wiki,
|
||||
:push_code
|
||||
:push_code,
|
||||
:create_container_image,
|
||||
:update_container_image,
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -242,7 +249,9 @@ class Ability
|
|||
:admin_wiki,
|
||||
:admin_project,
|
||||
:admin_commit_status,
|
||||
:admin_build
|
||||
:admin_build,
|
||||
:admin_container_image,
|
||||
:admin_pipeline
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -285,6 +294,11 @@ class Ability
|
|||
|
||||
unless project.builds_enabled
|
||||
rules += named_abilities('build')
|
||||
rules += named_abilities('pipeline')
|
||||
end
|
||||
|
||||
unless project.container_registry_enabled
|
||||
rules += named_abilities('container_image')
|
||||
end
|
||||
|
||||
rules
|
||||
|
|
|
@ -120,7 +120,8 @@ class ApplicationSetting < ActiveRecord::Base
|
|||
recaptcha_enabled: false,
|
||||
akismet_enabled: false,
|
||||
repository_checks_enabled: true,
|
||||
disabled_oauth_sign_in_sources: []
|
||||
disabled_oauth_sign_in_sources: [],
|
||||
send_user_confirmation_email: false
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ module Ci
|
|||
new_build.stage_idx = build.stage_idx
|
||||
new_build.trigger_request = build.trigger_request
|
||||
new_build.save
|
||||
MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build)
|
||||
new_build
|
||||
end
|
||||
end
|
||||
|
@ -290,9 +291,15 @@ module Ci
|
|||
end
|
||||
|
||||
def can_be_served?(runner)
|
||||
return false unless has_tags? || runner.run_untagged?
|
||||
|
||||
(tag_list - runner.tag_list).empty?
|
||||
end
|
||||
|
||||
def has_tags?
|
||||
tag_list.any?
|
||||
end
|
||||
|
||||
def any_runners_online?
|
||||
project.any_runners? { |runner| runner.active? && runner.online? && can_be_served?(runner) }
|
||||
end
|
||||
|
|
|
@ -8,8 +8,6 @@ module Ci
|
|||
has_many :builds, class_name: 'Ci::Build'
|
||||
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
|
||||
|
||||
delegate :stages, to: :statuses
|
||||
|
||||
validates_presence_of :sha
|
||||
validates_presence_of :status
|
||||
validate :valid_commit_sha
|
||||
|
@ -22,7 +20,8 @@ module Ci
|
|||
end
|
||||
|
||||
def self.stages
|
||||
CommitStatus.where(commit: all).stages
|
||||
# We use pluck here due to problems with MySQL which doesn't allow LIMIT/OFFSET in queries
|
||||
CommitStatus.where(commit: pluck(:id)).stages
|
||||
end
|
||||
|
||||
def project_id
|
||||
|
@ -67,6 +66,25 @@ module Ci
|
|||
end
|
||||
end
|
||||
|
||||
def cancel_running
|
||||
builds.running_or_pending.each(&:cancel)
|
||||
end
|
||||
|
||||
def retry_failed
|
||||
builds.latest.failed.select(&:retryable?).each(&:retry)
|
||||
end
|
||||
|
||||
def latest?
|
||||
return false unless ref
|
||||
commit = project.commit(ref)
|
||||
return false unless commit
|
||||
commit.sha == sha
|
||||
end
|
||||
|
||||
def triggered?
|
||||
trigger_requests.any?
|
||||
end
|
||||
|
||||
def create_builds(user, trigger_request = nil)
|
||||
return unless config_processor
|
||||
config_processor.stages.any? do |stage|
|
||||
|
|
|
@ -4,7 +4,7 @@ module Ci
|
|||
|
||||
LAST_CONTACT_TIME = 5.minutes.ago
|
||||
AVAILABLE_SCOPES = %w[specific shared active paused online]
|
||||
FORM_EDITABLE = %i[description tag_list active]
|
||||
FORM_EDITABLE = %i[description tag_list active run_untagged]
|
||||
|
||||
has_many :builds, class_name: 'Ci::Build'
|
||||
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
|
||||
|
@ -26,6 +26,8 @@ module Ci
|
|||
.where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id)
|
||||
end
|
||||
|
||||
validate :tag_constraints
|
||||
|
||||
acts_as_taggable
|
||||
|
||||
# Searches for runners matching the given query.
|
||||
|
@ -96,5 +98,18 @@ module Ci
|
|||
def short_sha
|
||||
token[0...8] if token
|
||||
end
|
||||
|
||||
def has_tags?
|
||||
tag_list.any?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tag_constraints
|
||||
unless has_tags? || run_untagged?
|
||||
errors.add(:tags_list,
|
||||
'can not be empty when runner is not allowed to pick untagged jobs')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,7 +14,8 @@ class CommitStatus < ActiveRecord::Base
|
|||
alias_attribute :author, :user
|
||||
|
||||
scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :commit_id)) }
|
||||
scope :ordered, -> { order(:ref, :stage_idx, :name) }
|
||||
scope :retried, -> { where.not(id: latest) }
|
||||
scope :ordered, -> { order(:name) }
|
||||
scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
|
||||
|
||||
state_machine :status, initial: :pending do
|
||||
|
@ -45,6 +46,10 @@ class CommitStatus < ActiveRecord::Base
|
|||
after_transition [:pending, :running] => :success do |commit_status|
|
||||
MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status)
|
||||
end
|
||||
|
||||
after_transition any => :failed do |commit_status|
|
||||
MergeRequests::AddTodoWhenBuildFailsService.new(commit_status.commit.project, nil).execute(commit_status)
|
||||
end
|
||||
end
|
||||
|
||||
delegate :sha, :short_sha, to: :commit
|
||||
|
@ -54,13 +59,15 @@ class CommitStatus < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def self.stages
|
||||
order_by = 'max(stage_idx)'
|
||||
group('stage').order(order_by).pluck(:stage, order_by).map(&:first).compact
|
||||
# We group by stage name, but order stages by theirs' index
|
||||
unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage')
|
||||
end
|
||||
|
||||
def self.stages_status
|
||||
all.stages.inject({}) do |h, stage|
|
||||
h[stage] = all.where(stage: stage).status
|
||||
# We execute subquery for each stage to calculate a stage status
|
||||
statuses = unscoped.from(all, :sg).group('stage').pluck('sg.stage', all.where('stage=sg.stage').status_sql)
|
||||
statuses.inject({}) do |h, k|
|
||||
h[k.first] = k.last
|
||||
h
|
||||
end
|
||||
end
|
||||
|
|
|
@ -80,7 +80,7 @@ class Event < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def target_title
|
||||
target.title if target && target.respond_to?(:title)
|
||||
target.try(:title)
|
||||
end
|
||||
|
||||
def created?
|
||||
|
@ -266,28 +266,20 @@ class Event < ActiveRecord::Base
|
|||
branch? && project.default_branch != branch_name
|
||||
end
|
||||
|
||||
def note_commit_id
|
||||
target.commit_id
|
||||
end
|
||||
|
||||
def target_iid
|
||||
target.respond_to?(:iid) ? target.iid : target_id
|
||||
end
|
||||
|
||||
def note_short_commit_id
|
||||
Commit.truncate_sha(note_commit_id)
|
||||
end
|
||||
|
||||
def note_commit?
|
||||
target.noteable_type == "Commit"
|
||||
def commit_note?
|
||||
target.for_commit?
|
||||
end
|
||||
|
||||
def issue_note?
|
||||
note? && target && target.noteable_type == "Issue"
|
||||
note? && target && target.for_issue?
|
||||
end
|
||||
|
||||
def note_project_snippet?
|
||||
target.noteable_type == "Snippet"
|
||||
def project_snippet_note?
|
||||
target.for_snippet?
|
||||
end
|
||||
|
||||
def note_target
|
||||
|
@ -295,19 +287,22 @@ class Event < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def note_target_id
|
||||
if note_commit?
|
||||
if commit_note?
|
||||
target.commit_id
|
||||
else
|
||||
target.noteable_id.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def note_target_iid
|
||||
if note_target.respond_to?(:iid)
|
||||
note_target.iid
|
||||
def note_target_reference
|
||||
return unless note_target
|
||||
|
||||
# Commit#to_reference returns the full SHA, but we want the short one here
|
||||
if commit_note?
|
||||
note_target.short_id
|
||||
else
|
||||
note_target_id
|
||||
end.to_s
|
||||
note_target.to_reference
|
||||
end
|
||||
end
|
||||
|
||||
def note_target_type
|
||||
|
|
|
@ -26,6 +26,10 @@ class MergeRequest < ActiveRecord::Base
|
|||
# when creating new merge request
|
||||
attr_accessor :can_be_created, :compare_commits, :compare
|
||||
|
||||
# Temporary fields to store target_sha, and base_sha to
|
||||
# compare when importing pull requests from GitHub
|
||||
attr_accessor :base_target_sha, :head_source_sha
|
||||
|
||||
state_machine :state, initial: :opened do
|
||||
event :close do
|
||||
transition [:reopened, :opened] => :closed
|
||||
|
@ -490,10 +494,14 @@ class MergeRequest < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def target_sha
|
||||
@target_sha ||= target_project.repository.commit(target_branch).try(:sha)
|
||||
return @base_target_sha if defined?(@base_target_sha)
|
||||
|
||||
target_project.repository.commit(target_branch).try(:sha)
|
||||
end
|
||||
|
||||
def source_sha
|
||||
return @head_source_sha if defined?(@head_source_sha)
|
||||
|
||||
last_commit.try(:sha) || source_tip.try(:sha)
|
||||
end
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ class MergeRequestDiff < ActiveRecord::Base
|
|||
|
||||
belongs_to :merge_request
|
||||
|
||||
delegate :target_branch, :source_branch, to: :merge_request, prefix: nil
|
||||
delegate :head_source_sha, :target_branch, :source_branch, to: :merge_request, prefix: nil
|
||||
|
||||
state_machine :state, initial: :empty do
|
||||
state :collected
|
||||
|
@ -38,8 +38,8 @@ class MergeRequestDiff < ActiveRecord::Base
|
|||
@diffs_no_whitespace ||= begin
|
||||
compare = Gitlab::Git::Compare.new(
|
||||
self.repository.raw_repository,
|
||||
self.target_branch,
|
||||
self.source_sha,
|
||||
self.base,
|
||||
self.head,
|
||||
)
|
||||
compare.diffs(options)
|
||||
end
|
||||
|
@ -98,9 +98,7 @@ class MergeRequestDiff < ActiveRecord::Base
|
|||
commits = compare.commits
|
||||
|
||||
if commits.present?
|
||||
commits = Commit.decorate(commits, merge_request.source_project).
|
||||
sort_by(&:created_at).
|
||||
reverse
|
||||
commits = Commit.decorate(commits, merge_request.source_project).reverse
|
||||
end
|
||||
|
||||
commits
|
||||
|
@ -144,7 +142,7 @@ class MergeRequestDiff < ActiveRecord::Base
|
|||
|
||||
self.st_diffs = new_diffs
|
||||
|
||||
self.base_commit_sha = self.repository.merge_base(self.source_sha, self.target_branch)
|
||||
self.base_commit_sha = self.repository.merge_base(self.head, self.base)
|
||||
|
||||
self.save
|
||||
end
|
||||
|
@ -160,10 +158,24 @@ class MergeRequestDiff < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def source_sha
|
||||
return head_source_sha if head_source_sha.present?
|
||||
|
||||
source_commit = merge_request.source_project.commit(source_branch)
|
||||
source_commit.try(:sha)
|
||||
end
|
||||
|
||||
def target_sha
|
||||
merge_request.target_sha
|
||||
end
|
||||
|
||||
def base
|
||||
self.target_sha || self.target_branch
|
||||
end
|
||||
|
||||
def head
|
||||
self.source_sha
|
||||
end
|
||||
|
||||
def compare
|
||||
@compare ||=
|
||||
begin
|
||||
|
@ -172,8 +184,8 @@ class MergeRequestDiff < ActiveRecord::Base
|
|||
|
||||
Gitlab::Git::Compare.new(
|
||||
self.repository.raw_repository,
|
||||
self.target_branch,
|
||||
self.source_sha
|
||||
self.base,
|
||||
self.head
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -67,8 +67,18 @@ class Milestone < ActiveRecord::Base
|
|||
@link_reference_pattern ||= super("milestones", /(?<milestone>\d+)/)
|
||||
end
|
||||
|
||||
def self.upcoming
|
||||
self.where('due_date > ?', Time.now).reorder(due_date: :asc).first
|
||||
def self.upcoming_ids_by_projects(projects)
|
||||
rel = unscoped.of_projects(projects).active.where('due_date > ?', Time.now)
|
||||
|
||||
if Gitlab::Database.postgresql?
|
||||
rel.order(:project_id, :due_date).select('DISTINCT ON (project_id) id')
|
||||
else
|
||||
rel.
|
||||
group(:project_id).
|
||||
having('due_date = MIN(due_date)').
|
||||
pluck(:id, :project_id, :due_date).
|
||||
map(&:first)
|
||||
end
|
||||
end
|
||||
|
||||
def to_reference(from_project = nil)
|
||||
|
|
|
@ -110,6 +110,10 @@ class Namespace < ActiveRecord::Base
|
|||
# Ensure old directory exists before moving it
|
||||
gitlab_shell.add_namespace(path_was)
|
||||
|
||||
if any_project_has_container_registry_tags?
|
||||
raise Exception.new('Namespace cannot be moved, because at least one project has tags in container registry')
|
||||
end
|
||||
|
||||
if gitlab_shell.mv_namespace(path_was, path)
|
||||
Gitlab::UploadsTransfer.new.rename_namespace(path_was, path)
|
||||
|
||||
|
@ -131,6 +135,10 @@ class Namespace < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def any_project_has_container_registry_tags?
|
||||
projects.any?(&:has_container_registry_tags?)
|
||||
end
|
||||
|
||||
def send_update_instructions
|
||||
projects.each do |project|
|
||||
project.send_move_instructions("#{path_was}/#{project.path}")
|
||||
|
|
|
@ -19,6 +19,7 @@ class Note < ActiveRecord::Base
|
|||
delegate :gfm_reference, :local_reference, to: :noteable
|
||||
delegate :name, to: :project, prefix: true
|
||||
delegate :name, :email, to: :author, prefix: true
|
||||
delegate :title, to: :noteable, allow_nil: true
|
||||
|
||||
before_validation :set_award!
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ class Project < ActiveRecord::Base
|
|||
default_value_for :builds_enabled, gitlab_config_features.builds
|
||||
default_value_for :wiki_enabled, gitlab_config_features.wiki
|
||||
default_value_for :snippets_enabled, gitlab_config_features.snippets
|
||||
default_value_for :container_registry_enabled, gitlab_config_features.container_registry
|
||||
default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
|
||||
|
||||
# set last_activity_at to the same as created_at
|
||||
|
@ -49,6 +50,8 @@ class Project < ActiveRecord::Base
|
|||
attr_accessor :new_default_branch
|
||||
attr_accessor :old_path_with_namespace
|
||||
|
||||
alias_attribute :title, :name
|
||||
|
||||
# Relations
|
||||
belongs_to :creator, foreign_key: 'creator_id', class_name: 'User'
|
||||
belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id'
|
||||
|
@ -168,17 +171,17 @@ class Project < ActiveRecord::Base
|
|||
|
||||
scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) }
|
||||
scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }
|
||||
scope :sorted_by_names, -> { joins(:namespace).reorder('namespaces.name ASC, projects.name ASC') }
|
||||
|
||||
scope :without_user, ->(user) { where('projects.id NOT IN (:ids)', ids: user.authorized_projects.map(&:id) ) }
|
||||
scope :without_team, ->(team) { team.projects.present? ? where('projects.id NOT IN (:ids)', ids: team.projects.map(&:id)) : scoped }
|
||||
scope :not_in_group, ->(group) { where('projects.id NOT IN (:ids)', ids: group.project_ids ) }
|
||||
scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) }
|
||||
scope :in_group_namespace, -> { joins(:group) }
|
||||
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
|
||||
scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) }
|
||||
scope :visible_to_user, ->(user) { where(id: user.authorized_projects.select(:id).reorder(nil)) }
|
||||
scope :non_archived, -> { where(archived: false) }
|
||||
scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
|
||||
scope :with_push, -> { joins(:events).where('events.action = ?', Event::PUSHED) }
|
||||
|
||||
scope :active, -> { joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC') }
|
||||
scope :abandoned, -> { where('projects.last_activity_at < ?', 6.months.ago) }
|
||||
|
||||
state_machine :import_status, initial: :none do
|
||||
event :import_start do
|
||||
|
@ -201,23 +204,10 @@ class Project < ActiveRecord::Base
|
|||
state :finished
|
||||
state :failed
|
||||
|
||||
after_transition any => :started, do: :schedule_add_import_job
|
||||
after_transition any => :finished, do: :clear_import_data
|
||||
end
|
||||
|
||||
class << self
|
||||
def abandoned
|
||||
where('projects.last_activity_at < ?', 6.months.ago)
|
||||
end
|
||||
|
||||
def with_push
|
||||
joins(:events).where('events.action = ?', Event::PUSHED)
|
||||
end
|
||||
|
||||
def active
|
||||
joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC')
|
||||
end
|
||||
|
||||
# Searches for a list of projects based on the query given in `query`.
|
||||
#
|
||||
# On PostgreSQL this method uses "ILIKE" to perform a case-insensitive
|
||||
|
@ -279,10 +269,6 @@ class Project < ActiveRecord::Base
|
|||
projects.iwhere('projects.path' => project_path).take
|
||||
end
|
||||
|
||||
def find_by_ci_id(id)
|
||||
find_by(ci_id: id.to_i)
|
||||
end
|
||||
|
||||
def visibility_levels
|
||||
Gitlab::VisibilityLevel.options
|
||||
end
|
||||
|
@ -313,10 +299,6 @@ class Project < ActiveRecord::Base
|
|||
|
||||
joins(join_body).reorder('join_note_counts.amount DESC')
|
||||
end
|
||||
|
||||
def visible_to_user(user)
|
||||
where(id: user.authorized_projects.select(:id).reorder(nil))
|
||||
end
|
||||
end
|
||||
|
||||
def team
|
||||
|
@ -327,6 +309,30 @@ class Project < ActiveRecord::Base
|
|||
@repository ||= Repository.new(path_with_namespace, self)
|
||||
end
|
||||
|
||||
def container_registry_repository
|
||||
return unless Gitlab.config.registry.enabled
|
||||
|
||||
@container_registry_repository ||= begin
|
||||
token = Auth::ContainerRegistryAuthenticationService.full_access_token(path_with_namespace)
|
||||
url = Gitlab.config.registry.api_url
|
||||
host_port = Gitlab.config.registry.host_port
|
||||
registry = ContainerRegistry::Registry.new(url, token: token, path: host_port)
|
||||
registry.repository(path_with_namespace)
|
||||
end
|
||||
end
|
||||
|
||||
def container_registry_repository_url
|
||||
if Gitlab.config.registry.enabled
|
||||
"#{Gitlab.config.registry.host_port}/#{path_with_namespace}"
|
||||
end
|
||||
end
|
||||
|
||||
def has_container_registry_tags?
|
||||
return unless container_registry_repository
|
||||
|
||||
container_registry_repository.tags.any?
|
||||
end
|
||||
|
||||
def commit(id = 'HEAD')
|
||||
repository.commit(id)
|
||||
end
|
||||
|
@ -340,10 +346,6 @@ class Project < ActiveRecord::Base
|
|||
id && persisted?
|
||||
end
|
||||
|
||||
def schedule_add_import_job
|
||||
run_after_commit(:add_import_job)
|
||||
end
|
||||
|
||||
def add_import_job
|
||||
if forked?
|
||||
job_id = RepositoryForkWorker.perform_async(self.id, forked_from_project.path_with_namespace, self.namespace.path)
|
||||
|
@ -367,14 +369,14 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def import_url=(value)
|
||||
import_url = Gitlab::ImportUrl.new(value)
|
||||
import_url = Gitlab::UrlSanitizer.new(value)
|
||||
create_or_update_import_data(credentials: import_url.credentials)
|
||||
super(import_url.sanitized_url)
|
||||
end
|
||||
|
||||
def import_url
|
||||
if import_data && super
|
||||
import_url = Gitlab::ImportUrl.new(super, credentials: import_data.credentials)
|
||||
import_url = Gitlab::UrlSanitizer.new(super, credentials: import_data.credentials)
|
||||
import_url.full_url
|
||||
else
|
||||
super
|
||||
|
@ -742,6 +744,11 @@ class Project < ActiveRecord::Base
|
|||
|
||||
expire_caches_before_rename(old_path_with_namespace)
|
||||
|
||||
if has_container_registry_tags?
|
||||
# we currently doesn't support renaming repository if it contains tags in container registry
|
||||
raise Exception.new('Project cannot be renamed, because tags are present in its container registry')
|
||||
end
|
||||
|
||||
if gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace)
|
||||
# If repository moved successfully we need to send update instructions to users.
|
||||
# However we cannot allow rollback since we moved repository
|
||||
|
|
|
@ -34,7 +34,12 @@ class SlackService
|
|||
private
|
||||
|
||||
def message
|
||||
"#{user_name} #{state} #{issue_link} in #{project_link}: *#{title}*"
|
||||
case state
|
||||
when "opened"
|
||||
"[#{project_link}] Issue #{state} by #{user_name}"
|
||||
else
|
||||
"[#{project_link}] Issue #{issue_link} #{state} by #{user_name}"
|
||||
end
|
||||
end
|
||||
|
||||
def opened_issue?
|
||||
|
@ -42,7 +47,11 @@ class SlackService
|
|||
end
|
||||
|
||||
def description_message
|
||||
[{ text: format(description), color: attachment_color }]
|
||||
[{
|
||||
title: issue_title,
|
||||
title_link: issue_url,
|
||||
text: format(description),
|
||||
color: "#C95823" }]
|
||||
end
|
||||
|
||||
def project_link
|
||||
|
@ -50,7 +59,11 @@ class SlackService
|
|||
end
|
||||
|
||||
def issue_link
|
||||
"[issue ##{issue_iid}](#{issue_url})"
|
||||
"[#{issue_title}](#{issue_url})"
|
||||
end
|
||||
|
||||
def issue_title
|
||||
"##{issue_iid} #{title}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -40,7 +40,7 @@ class ProjectWiki
|
|||
end
|
||||
|
||||
def wiki_base_path
|
||||
["/", @project.path_with_namespace, "/wikis"].join('')
|
||||
[Gitlab.config.gitlab.relative_url_root, "/", @project.path_with_namespace, "/wikis"].join('')
|
||||
end
|
||||
|
||||
# Returns the Gollum::Wiki object.
|
||||
|
|
|
@ -195,6 +195,10 @@ class Repository
|
|||
cache.fetch(:branch_names) { branches.map(&:name) }
|
||||
end
|
||||
|
||||
def branch_exists?(branch_name)
|
||||
branch_names.include?(branch_name)
|
||||
end
|
||||
|
||||
def tag_names
|
||||
cache.fetch(:tag_names) { raw_repository.tag_names }
|
||||
end
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
class Todo < ActiveRecord::Base
|
||||
ASSIGNED = 1
|
||||
MENTIONED = 2
|
||||
ASSIGNED = 1
|
||||
MENTIONED = 2
|
||||
BUILD_FAILED = 3
|
||||
|
||||
belongs_to :author, class_name: "User"
|
||||
belongs_to :note
|
||||
|
@ -28,6 +29,10 @@ class Todo < ActiveRecord::Base
|
|||
state :done
|
||||
end
|
||||
|
||||
def build_failed?
|
||||
action == BUILD_FAILED
|
||||
end
|
||||
|
||||
def body
|
||||
if note.present?
|
||||
note.note
|
||||
|
|
|
@ -112,6 +112,7 @@ class User < ActiveRecord::Base
|
|||
before_save :ensure_external_user_rights
|
||||
after_save :ensure_namespace_correct
|
||||
after_initialize :set_projects_limit
|
||||
before_create :check_confirmation_email
|
||||
after_create :post_create_hook
|
||||
after_destroy :post_destroy_hook
|
||||
|
||||
|
@ -307,6 +308,10 @@ class User < ActiveRecord::Base
|
|||
@reset_token
|
||||
end
|
||||
|
||||
def check_confirmation_email
|
||||
skip_confirmation! unless current_application_settings.send_user_confirmation_email
|
||||
end
|
||||
|
||||
def recently_sent_password_reset?
|
||||
reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
|
||||
end
|
||||
|
@ -392,11 +397,6 @@ class User < ActiveRecord::Base
|
|||
owned_groups.select(:id), namespace.id).joins(:namespace)
|
||||
end
|
||||
|
||||
# Team membership in authorized projects
|
||||
def tm_in_authorized_projects
|
||||
ProjectMember.where(source_id: authorized_projects.map(&:id), user_id: self.id)
|
||||
end
|
||||
|
||||
def is_admin?
|
||||
admin
|
||||
end
|
||||
|
@ -486,10 +486,6 @@ class User < ActiveRecord::Base
|
|||
"#{name} (#{username})"
|
||||
end
|
||||
|
||||
def tm_of(project)
|
||||
project.project_member_by_id(self.id)
|
||||
end
|
||||
|
||||
def already_forked?(project)
|
||||
!!fork_of(project)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
module Auth
|
||||
class ContainerRegistryAuthenticationService < BaseService
|
||||
AUDIENCE = 'container_registry'
|
||||
|
||||
def execute
|
||||
return error('not found', 404) unless registry.enabled
|
||||
|
||||
if params[:offline_token]
|
||||
return error('unauthorized', 401) unless current_user
|
||||
else
|
||||
return error('forbidden', 403) unless scope
|
||||
end
|
||||
|
||||
{ token: authorized_token(scope).encoded }
|
||||
end
|
||||
|
||||
def self.full_access_token(*names)
|
||||
registry = Gitlab.config.registry
|
||||
token = JSONWebToken::RSAToken.new(registry.key)
|
||||
token.issuer = registry.issuer
|
||||
token.audience = AUDIENCE
|
||||
token[:access] = names.map do |name|
|
||||
{ type: 'repository', name: name, actions: %w(pull push) }
|
||||
end
|
||||
token.encoded
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authorized_token(*accesses)
|
||||
token = JSONWebToken::RSAToken.new(registry.key)
|
||||
token.issuer = registry.issuer
|
||||
token.audience = params[:service]
|
||||
token.subject = current_user.try(:username)
|
||||
token[:access] = accesses.compact
|
||||
token
|
||||
end
|
||||
|
||||
def scope
|
||||
return unless params[:scope]
|
||||
|
||||
@scope ||= process_scope(params[:scope])
|
||||
end
|
||||
|
||||
def process_scope(scope)
|
||||
type, name, actions = scope.split(':', 3)
|
||||
actions = actions.split(',')
|
||||
return unless type == 'repository'
|
||||
|
||||
process_repository_access(type, name, actions)
|
||||
end
|
||||
|
||||
def process_repository_access(type, name, actions)
|
||||
requested_project = Project.find_with_namespace(name)
|
||||
return unless requested_project
|
||||
|
||||
actions = actions.select do |action|
|
||||
can_access?(requested_project, action)
|
||||
end
|
||||
|
||||
{ type: type, name: name, actions: actions } if actions.present?
|
||||
end
|
||||
|
||||
def can_access?(requested_project, requested_action)
|
||||
return false unless requested_project.container_registry_enabled?
|
||||
|
||||
case requested_action
|
||||
when 'pull'
|
||||
requested_project == project || can?(current_user, :read_container_image, requested_project)
|
||||
when 'push'
|
||||
requested_project == project || can?(current_user, :create_container_image, requested_project)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def registry
|
||||
Gitlab.config.registry
|
||||
end
|
||||
end
|
||||
end
|
50
app/services/ci/create_pipeline_service.rb
Normal file
50
app/services/ci/create_pipeline_service.rb
Normal file
|
@ -0,0 +1,50 @@
|
|||
module Ci
|
||||
class CreatePipelineService < BaseService
|
||||
def execute
|
||||
pipeline = project.ci_commits.new(params)
|
||||
|
||||
unless ref_names.include?(params[:ref])
|
||||
pipeline.errors.add(:base, 'Reference not found')
|
||||
return pipeline
|
||||
end
|
||||
|
||||
unless commit
|
||||
pipeline.errors.add(:base, 'Commit not found')
|
||||
return pipeline
|
||||
end
|
||||
|
||||
unless can?(current_user, :create_pipeline, project)
|
||||
pipeline.errors.add(:base, 'Insufficient permissions to create a new pipeline')
|
||||
return pipeline
|
||||
end
|
||||
|
||||
begin
|
||||
Ci::Commit.transaction do
|
||||
pipeline.sha = commit.id
|
||||
|
||||
unless pipeline.config_processor
|
||||
pipeline.errors.add(:base, pipeline.yaml_errors || 'Missing .gitlab-ci.yml file')
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
pipeline.save!
|
||||
pipeline.create_builds(current_user)
|
||||
end
|
||||
rescue
|
||||
pipeline.errors.add(:base, 'The pipeline could not be created. Please try again.')
|
||||
end
|
||||
|
||||
pipeline
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ref_names
|
||||
@ref_names ||= project.repository.ref_names
|
||||
end
|
||||
|
||||
def commit
|
||||
@commit ||= project.commit(params[:ref])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -18,19 +18,16 @@ class CreateCommitBuildsService
|
|||
return false
|
||||
end
|
||||
|
||||
commit = project.ci_commit(sha, ref)
|
||||
unless commit
|
||||
commit = project.ci_commits.new(sha: sha, ref: ref, before_sha: before_sha, tag: tag)
|
||||
commit = Ci::Commit.new(project: project, sha: sha, ref: ref, before_sha: before_sha, tag: tag)
|
||||
|
||||
# Skip creating ci_commit when no gitlab-ci.yml is found
|
||||
unless commit.ci_yaml_file
|
||||
return false
|
||||
end
|
||||
|
||||
# Create a new ci_commit
|
||||
commit.save!
|
||||
# Skip creating ci_commit when no gitlab-ci.yml is found
|
||||
unless commit.ci_yaml_file
|
||||
return false
|
||||
end
|
||||
|
||||
# Create a new ci_commit
|
||||
commit.save!
|
||||
|
||||
# Skip creating builds for commits that have [ci skip]
|
||||
unless commit.skip_ci?
|
||||
# Create builds for commit
|
||||
|
|
|
@ -24,6 +24,10 @@ module Issues
|
|||
todo_service.reassigned_issue(issue, current_user)
|
||||
end
|
||||
|
||||
if issue.previous_changes.include?('confidential')
|
||||
create_confidentiality_note(issue)
|
||||
end
|
||||
|
||||
added_labels = issue.labels - old_labels
|
||||
if added_labels.present?
|
||||
notification_service.relabeled_issue(issue, added_labels, current_user)
|
||||
|
@ -37,5 +41,11 @@ module Issues
|
|||
def close_service
|
||||
Issues::CloseService
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_confidentiality_note(issue)
|
||||
SystemNoteService.change_issue_confidentiality(issue, issue.project, current_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
module MergeRequests
|
||||
class AddTodoWhenBuildFailsService < MergeRequests::BaseService
|
||||
# Adds a todo to the parent merge_request when a CI build fails
|
||||
def execute(commit_status)
|
||||
each_merge_request(commit_status) do |merge_request|
|
||||
todo_service.merge_request_build_failed(merge_request)
|
||||
end
|
||||
end
|
||||
|
||||
# Closes any pending build failed todos for the parent MRs when a build is retried
|
||||
def close(commit_status)
|
||||
each_merge_request(commit_status) do |merge_request|
|
||||
todo_service.merge_request_build_retried(merge_request)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -38,5 +38,30 @@ module MergeRequests
|
|||
def filter_params
|
||||
super(:merge_request)
|
||||
end
|
||||
|
||||
def merge_request_from(commit_status)
|
||||
branches = commit_status.ref
|
||||
|
||||
# This is for ref-less builds
|
||||
branches ||= @project.repository.branch_names_contains(commit_status.sha)
|
||||
|
||||
return [] if branches.blank?
|
||||
|
||||
merge_requests = @project.origin_merge_requests.opened.where(source_branch: branches).to_a
|
||||
merge_requests += @project.fork_merge_requests.opened.where(source_branch: branches).to_a
|
||||
|
||||
merge_requests.uniq.select(&:source_project)
|
||||
end
|
||||
|
||||
def each_merge_request(commit_status)
|
||||
merge_request_from(commit_status).each do |merge_request|
|
||||
ci_commit = merge_request.ci_commit
|
||||
|
||||
next unless ci_commit
|
||||
next unless ci_commit.sha == commit_status.sha
|
||||
|
||||
yield merge_request, ci_commit
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,15 +20,9 @@ module MergeRequests
|
|||
|
||||
# Triggers the automatic merge of merge_request once the build succeeds
|
||||
def trigger(commit_status)
|
||||
merge_requests = merge_request_from(commit_status)
|
||||
|
||||
merge_requests.each do |merge_request|
|
||||
each_merge_request(commit_status) do |merge_request, ci_commit|
|
||||
next unless merge_request.merge_when_build_succeeds?
|
||||
next unless merge_request.mergeable?
|
||||
|
||||
ci_commit = merge_request.ci_commit
|
||||
next unless ci_commit
|
||||
next unless ci_commit.sha == commit_status.sha
|
||||
next unless ci_commit.success?
|
||||
|
||||
MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params)
|
||||
|
@ -47,20 +41,5 @@ module MergeRequests
|
|||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def merge_request_from(commit_status)
|
||||
branches = commit_status.ref
|
||||
|
||||
# This is for ref-less builds
|
||||
branches ||= @project.repository.branch_names_contains(commit_status.sha)
|
||||
|
||||
return [] if branches.blank?
|
||||
|
||||
merge_requests = @project.origin_merge_requests.opened.where(source_branch: branches).to_a
|
||||
merge_requests += @project.fork_merge_requests.opened.where(source_branch: branches).to_a
|
||||
|
||||
merge_requests.uniq.select(&:source_project)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,6 +12,7 @@ module MergeRequests
|
|||
close_merge_requests
|
||||
reload_merge_requests
|
||||
reset_merge_when_build_succeeds
|
||||
mark_pending_todos_done
|
||||
|
||||
# Leave a system note if a branch was deleted/added
|
||||
if branch_added? || branch_removed?
|
||||
|
@ -80,6 +81,12 @@ module MergeRequests
|
|||
merge_requests_for_source_branch.each(&:reset_merge_when_build_succeeds)
|
||||
end
|
||||
|
||||
def mark_pending_todos_done
|
||||
merge_requests_for_source_branch.each do |merge_request|
|
||||
todo_service.merge_request_push(merge_request, @current_user)
|
||||
end
|
||||
end
|
||||
|
||||
def find_new_commits
|
||||
if branch_added?
|
||||
@commits = []
|
||||
|
|
|
@ -6,6 +6,7 @@ module Projects
|
|||
|
||||
def execute
|
||||
forked_from_project_id = params.delete(:forked_from_project_id)
|
||||
import_data = params.delete(:import_data)
|
||||
|
||||
@project = Project.new(params)
|
||||
|
||||
|
@ -49,16 +50,14 @@ module Projects
|
|||
@project.build_forked_project_link(forked_from_project_id: forked_from_project_id)
|
||||
end
|
||||
|
||||
Project.transaction do
|
||||
@project.save
|
||||
save_project_and_import_data(import_data)
|
||||
|
||||
if @project.persisted? && !@project.import?
|
||||
raise 'Failed to create repository' unless @project.create_repository
|
||||
end
|
||||
end
|
||||
@project.import_start if @project.import?
|
||||
|
||||
after_create_actions if @project.persisted?
|
||||
|
||||
@project.add_import_job if @project.import?
|
||||
|
||||
@project
|
||||
rescue => e
|
||||
message = "Unable to save project: #{e.message}"
|
||||
|
@ -93,8 +92,16 @@ module Projects
|
|||
unless @project.group
|
||||
@project.team << [current_user, :master, current_user]
|
||||
end
|
||||
end
|
||||
|
||||
@project.import_start if @project.import?
|
||||
def save_project_and_import_data(import_data)
|
||||
Project.transaction do
|
||||
@project.create_or_update_import_data(data: import_data[:data], credentials: import_data[:credentials]) if import_data
|
||||
|
||||
if @project.save && !@project.import?
|
||||
raise 'Failed to create repository' unless @project.create_repository
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,6 +26,10 @@ module Projects
|
|||
Project.transaction do
|
||||
project.destroy!
|
||||
|
||||
unless remove_registry_tags
|
||||
raise_error('Failed to remove project container registry. Please try again or contact administrator')
|
||||
end
|
||||
|
||||
unless remove_repository(repo_path)
|
||||
raise_error('Failed to remove project repository. Please try again or contact administrator')
|
||||
end
|
||||
|
@ -59,6 +63,12 @@ module Projects
|
|||
end
|
||||
end
|
||||
|
||||
def remove_registry_tags
|
||||
return true unless Gitlab.config.registry.enabled
|
||||
|
||||
project.container_registry_repository.delete_tags
|
||||
end
|
||||
|
||||
def raise_error(message)
|
||||
raise DestroyError.new(message)
|
||||
end
|
||||
|
|
|
@ -34,6 +34,11 @@ module Projects
|
|||
raise TransferError.new("Project with same path in target namespace already exists")
|
||||
end
|
||||
|
||||
if project.has_container_registry_tags?
|
||||
# we currently doesn't support renaming repository if it contains tags in container registry
|
||||
raise TransferError.new('Project cannot be transferred, because tags are present in its container registry')
|
||||
end
|
||||
|
||||
project.expire_caches_before_rename(old_path)
|
||||
|
||||
# Apply new namespace id and visibility level
|
||||
|
|
|
@ -169,12 +169,26 @@ class SystemNoteService
|
|||
#
|
||||
# Returns the created Note object
|
||||
def self.change_title(noteable, project, author, old_title)
|
||||
return unless noteable.respond_to?(:title)
|
||||
|
||||
body = "Title changed from **#{old_title}** to **#{noteable.title}**"
|
||||
create_note(noteable: noteable, project: project, author: author, note: body)
|
||||
end
|
||||
|
||||
# Called when the confidentiality changes
|
||||
#
|
||||
# issue - Issue object
|
||||
# project - Project owning the issue
|
||||
# author - User performing the change
|
||||
#
|
||||
# Example Note text:
|
||||
#
|
||||
# "Made the issue confidential"
|
||||
#
|
||||
# Returns the created Note object
|
||||
def self.change_issue_confidentiality(issue, project, author)
|
||||
body = issue.confidential ? 'Made the issue confidential' : 'Made the issue visible'
|
||||
create_note(noteable: issue, project: project, author: author, note: body)
|
||||
end
|
||||
|
||||
# Called when a branch in Noteable is changed
|
||||
#
|
||||
# noteable - Noteable object
|
||||
|
|
|
@ -80,6 +80,30 @@ class TodoService
|
|||
mark_pending_todos_as_done(merge_request, current_user)
|
||||
end
|
||||
|
||||
# When a build fails on the HEAD of a merge request we should:
|
||||
#
|
||||
# * create a todo for that user to fix it
|
||||
#
|
||||
def merge_request_build_failed(merge_request)
|
||||
create_build_failed_todo(merge_request)
|
||||
end
|
||||
|
||||
# When a new commit is pushed to a merge request we should:
|
||||
#
|
||||
# * mark all pending todos related to the merge request for that user as done
|
||||
#
|
||||
def merge_request_push(merge_request, current_user)
|
||||
mark_pending_todos_as_done(merge_request, current_user)
|
||||
end
|
||||
|
||||
# When a build is retried to a merge request we should:
|
||||
#
|
||||
# * mark all pending todos related to the merge request for the author as done
|
||||
#
|
||||
def merge_request_build_retried(merge_request)
|
||||
mark_pending_todos_as_done(merge_request, merge_request.author)
|
||||
end
|
||||
|
||||
# When create a note we should:
|
||||
#
|
||||
# * mark all pending todos related to the noteable for the note author as done
|
||||
|
@ -145,6 +169,12 @@ class TodoService
|
|||
create_todos(mentioned_users, attributes)
|
||||
end
|
||||
|
||||
def create_build_failed_todo(merge_request)
|
||||
author = merge_request.author
|
||||
attributes = attributes_for_todo(merge_request.project, merge_request, author, Todo::BUILD_FAILED)
|
||||
create_todos(author, attributes)
|
||||
end
|
||||
|
||||
def attributes_for_target(target)
|
||||
attributes = {
|
||||
project_id: target.project.id,
|
||||
|
|
|
@ -103,6 +103,12 @@
|
|||
= f.label :signup_enabled do
|
||||
= f.check_box :signup_enabled
|
||||
Sign-up enabled
|
||||
.form-group
|
||||
.col-sm-offset-2.col-sm-10
|
||||
.checkbox
|
||||
= f.label :send_user_confirmation_email do
|
||||
= f.check_box :send_user_confirmation_email
|
||||
Send confirmation email on sign-up
|
||||
.form-group
|
||||
.col-sm-offset-2.col-sm-10
|
||||
.checkbox
|
||||
|
|
|
@ -9,8 +9,6 @@
|
|||
%span.runner-state.runner-state-specific
|
||||
Specific
|
||||
|
||||
|
||||
|
||||
- if @runner.shared?
|
||||
.bs-callout.bs-callout-success
|
||||
%h4 This runner will process builds from ALL UNASSIGNED projects
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
%li{class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo), data:{url: todo_target_path(todo)} }
|
||||
.todo-item.todo-block
|
||||
= image_tag avatar_icon(todo.author_email, 40), class: 'avatar s40', alt:''
|
||||
|
||||
.todo-title.title
|
||||
%span.author-name
|
||||
- if todo.author
|
||||
= link_to_author(todo)
|
||||
- else
|
||||
(removed)
|
||||
- unless todo.build_failed?
|
||||
%span.author-name
|
||||
- if todo.author
|
||||
= link_to_author(todo)
|
||||
- else
|
||||
(removed)
|
||||
%span.todo-label
|
||||
= todo_action_name(todo)
|
||||
- if todo.target
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
= event_action_name(event)
|
||||
|
||||
- if event.target
|
||||
%strong= link_to event.target.reference_link_text, [event.project.namespace.becomes(Namespace), event.project, event.target]
|
||||
%strong= link_to event.target.reference_link_text, [event.project.namespace.becomes(Namespace), event.project, event.target], title: event.target_title
|
||||
|
||||
= event_preposition(event)
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
.nav-block
|
||||
- if current_user
|
||||
.controls
|
||||
= link_to dashboard_projects_path(:atom, { private_token: current_user.private_token }), class: 'btn rss-btn' do
|
||||
= link_to group_path(@group, format: :atom, private_token: current_user.private_token), class: 'btn rss-btn' do
|
||||
%i.fa.fa-rss
|
||||
= render 'shared/event_filter'
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
xml.instruct!
|
||||
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
|
||||
xml.title "#{@user.name} issues"
|
||||
xml.link href: issues_dashboard_url(format: :atom, private_token: @user.private_token), rel: "self", type: "application/atom+xml"
|
||||
xml.link href: issues_dashboard_url, rel: "alternate", type: "text/html"
|
||||
xml.id issues_dashboard_url
|
||||
xml.title "#{@group.name} issues"
|
||||
xml.link href: issues_group_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
|
||||
xml.link href: issues_group_url, rel: "alternate", type: "text/html"
|
||||
xml.id issues_group_url
|
||||
xml.updated @issues.first.created_at.xmlschema if @issues.any?
|
||||
|
||||
@issues.each do |issue|
|
||||
|
|
|
@ -39,6 +39,13 @@
|
|||
Commits
|
||||
|
||||
- if project_nav_tab? :builds
|
||||
= nav_link(controller: :pipelines) do
|
||||
= link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
|
||||
= icon('ship fw')
|
||||
%span
|
||||
Pipelines
|
||||
%span.count.ci_counter= number_with_delimiter(@project.ci_commits.running_or_pending.count)
|
||||
|
||||
= nav_link(controller: %w(builds)) do
|
||||
= link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
|
||||
= icon('cubes fw')
|
||||
|
@ -46,6 +53,13 @@
|
|||
Builds
|
||||
%span.count.builds_counter= number_with_delimiter(@project.builds.running_or_pending.count(:all))
|
||||
|
||||
- if project_nav_tab? :container_registry
|
||||
= nav_link(controller: %w(container_registry)) do
|
||||
= link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
|
||||
= icon('hdd-o fw')
|
||||
%span
|
||||
Container Registry
|
||||
|
||||
- if project_nav_tab? :graphs
|
||||
= nav_link(controller: %w(graphs)) do
|
||||
= link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs' do
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
%title
|
||||
GitLab
|
||||
= stylesheet_link_tag 'notify'
|
||||
= yield :head
|
||||
%body
|
||||
%div.content
|
||||
= yield
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
= content_for :head do
|
||||
= stylesheet_link_tag 'mailers/repository_push_email'
|
||||
|
||||
%h3
|
||||
#{@message.author_name} #{@message.action_name} #{@message.ref_type} #{@message.ref_name}
|
||||
at #{link_to(@message.project_name_with_namespace, namespace_project_url(@message.project_namespace, @message.project))}
|
||||
|
@ -43,26 +46,38 @@
|
|||
= diff.new_path
|
||||
|
||||
- unless @message.disable_diffs?
|
||||
%h4 Changes:
|
||||
- @message.diffs.each_with_index do |diff, i|
|
||||
%li{id: "diff-#{i}"}
|
||||
%a{href: @message.target_url + "#diff-#{i}"}
|
||||
- if diff.deleted_file
|
||||
%strong
|
||||
= diff.old_path
|
||||
deleted
|
||||
- elsif diff.renamed_file
|
||||
%strong
|
||||
= diff.old_path
|
||||
→
|
||||
%strong
|
||||
= diff.new_path
|
||||
- else
|
||||
%strong
|
||||
= diff.new_path
|
||||
%hr
|
||||
= color_email_diff(diff.diff)
|
||||
%br
|
||||
- diff_files = @message.diffs
|
||||
|
||||
- if @message.compare_timeout
|
||||
%h5 Huge diff. To prevent performance issues changes are hidden
|
||||
- if @message.compare_timeout
|
||||
%h5 The diff was not included because it is too large.
|
||||
- else
|
||||
%h4 Changes:
|
||||
- diff_files.each_with_index do |diff_file, i|
|
||||
%li{id: "diff-#{i}"}
|
||||
%a{href: @message.target_url + "#diff-#{i}"}<
|
||||
- if diff_file.deleted_file
|
||||
%strong<
|
||||
= diff_file.old_path
|
||||
deleted
|
||||
- elsif diff_file.renamed_file
|
||||
%strong<
|
||||
= diff_file.old_path
|
||||
→
|
||||
%strong<
|
||||
= diff_file.new_path
|
||||
- else
|
||||
%strong<
|
||||
= diff_file.new_path
|
||||
- if diff_file.too_large?
|
||||
The diff for this file was not included because it is too large.
|
||||
- else
|
||||
%hr
|
||||
- diff_commit = diff_file.deleted_file ? @message.diff_refs.first : @message.diff_refs.last
|
||||
- blob = @message.project.repository.blob_for_diff(diff_commit, diff_file)
|
||||
- if blob && blob.respond_to?(:text?) && blob_text_viewable?(blob)
|
||||
%table.code.white
|
||||
- diff_file.highlighted_diff_lines.each do |line|
|
||||
= render "projects/diffs/line", {line: line, diff_file: diff_file, line_code: nil, plain: true}
|
||||
- else
|
||||
No preview for this file type
|
||||
%br
|
||||
|
|
|
@ -25,24 +25,28 @@
|
|||
- else
|
||||
\- #{diff.new_path}
|
||||
- unless @message.disable_diffs?
|
||||
\
|
||||
\
|
||||
Changes:
|
||||
- @message.diffs.each do |diff|
|
||||
- if @message.compare_timeout
|
||||
\
|
||||
\=====================================
|
||||
- if diff.deleted_file
|
||||
#{diff.old_path} deleted
|
||||
- elsif diff.renamed_file
|
||||
#{diff.old_path} → #{diff.new_path}
|
||||
- else
|
||||
= diff.new_path
|
||||
\=====================================
|
||||
!= diff.diff
|
||||
- if @message.compare_timeout
|
||||
\
|
||||
\
|
||||
Huge diff. To prevent performance issues it was hidden
|
||||
\
|
||||
The diff was not included because it is too large.
|
||||
- else
|
||||
\
|
||||
\
|
||||
Changes:
|
||||
- @message.diffs.each do |diff_file|
|
||||
\
|
||||
\=====================================
|
||||
- if diff_file.deleted_file
|
||||
#{diff_file.old_path} deleted
|
||||
- elsif diff_file.renamed_file
|
||||
#{diff_file.old_path} → #{diff_file.new_path}
|
||||
- else
|
||||
= diff_file.new_path
|
||||
\=====================================
|
||||
- if diff_file.too_large?
|
||||
The diff for this file was not included because it is too large.
|
||||
- else
|
||||
!= diff_file.diff.diff
|
||||
- if @message.target_url
|
||||
\
|
||||
\
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
%a.js-md-preview-button{ href: "#md-preview-holder", tabindex: -1 }
|
||||
Preview
|
||||
%li.pull-right
|
||||
%button.zen-cotrol.zen-control-full.js-zen-enter{ type: 'button', tabindex: -1 }
|
||||
%button.zen-control.zen-control-full.js-zen-enter{ type: 'button', tabindex: -1 }
|
||||
Go full screen
|
||||
|
||||
.md-write-holder
|
||||
|
|
|
@ -4,5 +4,5 @@
|
|||
= f.text_area attr, class: classes, placeholder: placeholder
|
||||
- else
|
||||
= text_area_tag attr, nil, class: classes, placeholder: placeholder
|
||||
%a.zen-cotrol.zen-control-leave.js-zen-leave{ href: "#" }
|
||||
%a.zen-control.zen-control-leave.js-zen-leave{ href: "#" }
|
||||
= icon('compress')
|
||||
|
|
|
@ -13,7 +13,9 @@
|
|||
%strong ##{build.id}
|
||||
|
||||
- if build.stuck?
|
||||
%i.fa.fa-warning.text-warning
|
||||
= icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.')
|
||||
- if defined?(retried) && retried
|
||||
= icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.')
|
||||
|
||||
- if defined?(commit_sha) && commit_sha
|
||||
%td
|
||||
|
@ -40,25 +42,29 @@
|
|||
%td
|
||||
= build.name
|
||||
|
||||
%td
|
||||
.label-container
|
||||
- if build.tags.any?
|
||||
- build.tags.each do |tag|
|
||||
%span.label.label-primary
|
||||
= tag
|
||||
- if build.try(:trigger_request)
|
||||
%span.label.label-info triggered
|
||||
- if build.try(:allow_failure)
|
||||
%span.label.label-danger allowed to fail
|
||||
- if defined?(retried) && retried
|
||||
%span.label.label-warning retried
|
||||
.pull-right
|
||||
.label-container
|
||||
- if build.tags.any?
|
||||
- build.tags.each do |tag|
|
||||
%span.label.label-primary
|
||||
= tag
|
||||
- if build.try(:trigger_request)
|
||||
%span.label.label-info triggered
|
||||
- if build.try(:allow_failure)
|
||||
%span.label.label-danger allowed to fail
|
||||
- if defined?(retried) && retried
|
||||
%span.label.label-warning retried
|
||||
|
||||
%td.duration
|
||||
- if build.duration
|
||||
= icon("clock-o")
|
||||
|
||||
#{duration_in_words(build.finished_at, build.started_at)}
|
||||
|
||||
%td.timestamp
|
||||
- if build.finished_at
|
||||
= icon("calendar")
|
||||
|
||||
%span #{time_ago_with_tooltip(build.finished_at)}
|
||||
|
||||
- if defined?(coverage) && coverage
|
||||
|
@ -70,11 +76,11 @@
|
|||
.pull-right
|
||||
- if can?(current_user, :read_build, build) && build.artifacts?
|
||||
= link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts', class: 'btn btn-build' do
|
||||
%i.fa.fa-download
|
||||
= icon('download')
|
||||
- if can?(current_user, :update_build, build)
|
||||
- if build.active?
|
||||
= link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
|
||||
%i.fa.fa-remove.cred
|
||||
= icon('remove', class: 'cred')
|
||||
- elsif defined?(allow_retry) && allow_retry && build.retryable?
|
||||
= link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
|
||||
%i.fa.fa-refresh
|
||||
= icon('refresh')
|
||||
|
|
77
app/views/projects/ci/commits/_commit.html.haml
Normal file
77
app/views/projects/ci/commits/_commit.html.haml
Normal file
|
@ -0,0 +1,77 @@
|
|||
- status = commit.status
|
||||
%tr.commit
|
||||
%td.commit-link
|
||||
= link_to namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: "ci-status ci-#{status}" do
|
||||
= ci_icon_for_status(status)
|
||||
%strong ##{commit.id}
|
||||
|
||||
%td
|
||||
%div.branch-commit
|
||||
- if commit.ref
|
||||
= link_to commit.ref, namespace_project_commits_path(@project.namespace, @project, commit.ref), class: "monospace"
|
||||
·
|
||||
= link_to commit.short_sha, namespace_project_commit_path(@project.namespace, @project, commit.sha), class: "commit-id monospace"
|
||||
|
||||
- if commit.latest?
|
||||
%span.label.label-success latest
|
||||
- if commit.tag?
|
||||
%span.label.label-primary tag
|
||||
- if commit.triggered?
|
||||
%span.label.label-primary triggered
|
||||
- if commit.yaml_errors.present?
|
||||
%span.label.label-danger.has-tooltip{ title: "#{commit.yaml_errors}" } yaml invalid
|
||||
- if commit.builds.any?(&:stuck?)
|
||||
%span.label.label-warning stuck
|
||||
|
||||
%p
|
||||
%span
|
||||
- if commit_data = commit.commit_data
|
||||
= link_to_gfm commit_data.title, namespace_project_commit_path(@project.namespace, @project, commit_data.id), class: "commit-row-message"
|
||||
- else
|
||||
Cant find HEAD commit for this branch
|
||||
|
||||
|
||||
- stages_status = commit.statuses.stages_status
|
||||
- stages.each do |stage|
|
||||
%td
|
||||
- if status = stages_status[stage]
|
||||
- tooltip = "#{stage.titleize}: #{status}"
|
||||
%span.has-tooltip{ title: "#{tooltip}", class: "ci-status-icon-#{status}" }
|
||||
= ci_icon_for_status(status)
|
||||
|
||||
%td
|
||||
- if commit.started_at && commit.finished_at
|
||||
%p
|
||||
= icon("clock-o")
|
||||
|
||||
#{duration_in_words(commit.finished_at, commit.started_at)}
|
||||
- if commit.finished_at
|
||||
%p
|
||||
= icon("calendar")
|
||||
|
||||
#{time_ago_with_tooltip(commit.finished_at)}
|
||||
|
||||
%td
|
||||
.controls.hidden-xs.pull-right
|
||||
- artifacts = commit.builds.latest.select { |b| b.artifacts? }
|
||||
- if artifacts.present?
|
||||
.dropdown.inline.build-artifacts
|
||||
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
|
||||
= icon('download')
|
||||
%b.caret
|
||||
%ul.dropdown-menu.dropdown-menu-align-right
|
||||
- artifacts.each do |build|
|
||||
%li
|
||||
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do
|
||||
= icon("download")
|
||||
%span #{build.name}
|
||||
|
||||
- if can?(current_user, :update_pipeline, @project)
|
||||
|
||||
- if commit.retryable? && commit.builds.failed.any?
|
||||
= link_to retry_namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: 'btn has-tooltip', title: "Retry", method: :post do
|
||||
= icon("repeat")
|
||||
|
||||
- if commit.active?
|
||||
= link_to cancel_namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do
|
||||
= icon("remove")
|
|
@ -1,2 +1,2 @@
|
|||
- @ci_commits.each do |ci_commit|
|
||||
= render "ci_commit", ci_commit: ci_commit
|
||||
= render "ci_commit", ci_commit: ci_commit, pipeline_details: true
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue