From 7f7d39858f87d5920137d63e5d6005a0989ca392 Mon Sep 17 00:00:00 2001 From: Iman Mohamadi Date: Wed, 14 Oct 2015 11:14:59 +0330 Subject: [PATCH 001/140] ugly outlines removed form sidebar --- app/assets/stylesheets/framework/sidebar.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index c5ea3aca7c..1c42ba2fd7 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -64,6 +64,7 @@ text-decoration: none; padding-left: 22px; font-weight: normal; + outline: none; &:hover { text-decoration: none; @@ -176,6 +177,7 @@ text-align: center; line-height: 40px; transition-duration: .3s; + outline: none; } .collapse-nav a:hover { @@ -238,6 +240,7 @@ width: 100%; padding: 10px 22px; overflow: hidden; + outline: none; img { width: 36px; From 5f47b61cef9c110c09eab57a9e5f8f32654f21bd Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 17 Oct 2015 07:19:04 -0700 Subject: [PATCH 002/140] Use a relative link instead of full URL with New Issue button to be consistent Relates to #3095 --- app/views/projects/buttons/_dropdown.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index 4580c91269..fa8c2c599a 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -5,7 +5,7 @@ %ul.dropdown-menu.dropdown-menu-right.project-home-dropdown - if can?(current_user, :create_issue, @project) %li - = link_to url_for_new_issue do + = link_to url_for_new_issue(@project, only_path: true) do = icon('exclamation-circle fw') New issue - if can?(current_user, :create_merge_request, @project) From 3bb626f91cb50bd2eff58681e22db942b7d6a087 Mon Sep 17 00:00:00 2001 From: James Newton Date: Wed, 28 Oct 2015 16:39:23 +0100 Subject: [PATCH 003/140] refactor login as to be impersonation with better login/logout Modifies the existing "login as" feature to be called impersonation, as well as keeping track of who is impersonating to revert back to that user without having to log out. --- app/assets/stylesheets/framework/header.scss | 4 ++ .../admin/application_controller.rb | 6 +++ .../admin/impersonation_controller.rb | 32 ++++++++++++ app/controllers/admin/users_controller.rb | 6 --- app/views/admin/users/_head.html.haml | 2 +- app/views/layouts/header/_default.html.haml | 4 ++ config/routes.rb | 4 +- .../admin/users_controller_spec.rb | 15 ------ spec/features/admin/admin_users_spec.rb | 50 ++++++++++++++----- 9 files changed, 88 insertions(+), 35 deletions(-) create mode 100644 app/controllers/admin/impersonation_controller.rb diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 91e6975e26..02ea91602e 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -118,6 +118,10 @@ header { } } } + + .impersonation i { + color: $red-normal; + } } @mixin collapsed-header { diff --git a/app/controllers/admin/application_controller.rb b/app/controllers/admin/application_controller.rb index 56e2438646..9083bfb41c 100644 --- a/app/controllers/admin/application_controller.rb +++ b/app/controllers/admin/application_controller.rb @@ -8,4 +8,10 @@ class Admin::ApplicationController < ApplicationController def authenticate_admin! return render_404 unless current_user.is_admin? end + + def authorize_impersonator! + if session[:impersonator_id] + User.find_by!(username: session[:impersonator_id]).admin? + end + end end diff --git a/app/controllers/admin/impersonation_controller.rb b/app/controllers/admin/impersonation_controller.rb new file mode 100644 index 0000000000..0382402afa --- /dev/null +++ b/app/controllers/admin/impersonation_controller.rb @@ -0,0 +1,32 @@ +class Admin::ImpersonationController < Admin::ApplicationController + skip_before_action :authenticate_admin!, only: :destroy + + before_action :user + before_action :authorize_impersonator! + + def create + session[:impersonator_id] = current_user.username + session[:impersonator_return_to] = request.env['HTTP_REFERER'] + + warden.set_user(user, scope: 'user') + + flash[:alert] = "You are impersonating #{user.username}." + + redirect_to root_path + end + + def destroy + redirect = session[:impersonator_return_to] + + warden.set_user(user, scope: 'user') + + session[:impersonator_return_to] = nil + session[:impersonator_id] = nil + + redirect_to redirect || root_path + end + + def user + @user ||= User.find_by!(username: params[:id] || session[:impersonator_id]) + end +end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index c63d0793e3..d7c927d444 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -63,12 +63,6 @@ class Admin::UsersController < Admin::ApplicationController end end - def login_as - sign_in(user) - flash[:alert] = "Logged in as #{user.username}" - redirect_to root_path - end - def disable_two_factor user.disable_two_factor! redirect_to admin_user_path(user), diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml index 4245d0f1ed..8d1cab4137 100644 --- a/app/views/admin/users/_head.html.haml +++ b/app/views/admin/users/_head.html.haml @@ -7,7 +7,7 @@ .pull-right - unless @user == current_user - = link_to 'Log in as this user', login_as_admin_user_path(@user), method: :post, class: "btn btn-grouped btn-info" + = link_to 'Impersonate', impersonate_admin_user_path(@user), method: :post, class: "btn btn-grouped btn-info" = link_to edit_admin_user_path(@user), class: "btn btn-grouped" do %i.fa.fa-pencil-square-o Edit diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index c31b1cbe9a..13eeb0fe20 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -13,6 +13,10 @@ %li.visible-sm.visible-xs = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do = icon('search') + - if session[:impersonator_id] + %li.impersonation + = link_to stop_impersonation_admin_users_path, method: :delete, title: 'Stop impersonation', data: { toggle: 'tooltip', placement: 'bottom' } do + = icon('user-secret fw') - if current_user.is_admin? %li = link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do diff --git a/config/routes.rb b/config/routes.rb index f6812c9280..6ebedaefaf 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -212,6 +212,8 @@ Gitlab::Application.routes.draw do resources :keys, only: [:show, :destroy] resources :identities, only: [:index, :edit, :update, :destroy] + delete 'stop_impersonation' => 'impersonation#destroy', on: :collection + member do get :projects get :keys @@ -221,7 +223,7 @@ Gitlab::Application.routes.draw do put :unblock put :unlock put :confirm - post :login_as + post 'impersonate' => 'impersonation#create' patch :disable_two_factor delete 'remove/:email_id', action: 'remove_email', as: 'remove_email' end diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb index fcbe62cace..8b7af4d3a0 100644 --- a/spec/controllers/admin/users_controller_spec.rb +++ b/spec/controllers/admin/users_controller_spec.rb @@ -7,21 +7,6 @@ describe Admin::UsersController do sign_in(admin) end - describe 'POST login_as' do - let(:user) { create(:user) } - - it 'logs admin as another user' do - expect(warden.authenticate(scope: :user)).not_to eq(user) - post :login_as, id: user.username - expect(warden.authenticate(scope: :user)).to eq(user) - end - - it 'redirects user to homepage' do - post :login_as, id: user.username - expect(response).to redirect_to(root_path) - end - end - describe 'DELETE #user with projects' do let(:user) { create(:user) } let(:project) { create(:project, namespace: user.namespace) } diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index c2c7364f6c..4c756a8e73 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -111,24 +111,50 @@ describe "Admin::Users", feature: true do expect(page).to have_content(@user.name) end - describe 'Login as another user' do - it 'should show login button for other users and check that it works' do - another_user = create(:user) + describe 'Impersonation' do + let(:another_user) { create(:user) } + before { visit admin_user_path(another_user) } - visit admin_user_path(another_user) + context 'before impersonating' do + it 'shows impersonate button for other users' do + expect(page).to have_content('Impersonate') + end - click_link 'Log in as this user' + it 'should not show impersonate button for admin itself' do + visit admin_user_path(@user) - expect(page).to have_content("Logged in as #{another_user.username}") - - page.within '.sidebar-user .username' do - expect(page).to have_content(another_user.username) + expect(page).not_to have_content('Impersonate') end end - it 'should not show login button for admin itself' do - visit admin_user_path(@user) - expect(page).not_to have_content('Log in as this user') + context 'when impersonating' do + before { click_link 'Impersonate' } + + it 'logs in as the user when impersonate is clicked' do + page.within '.sidebar-user .username' do + expect(page).to have_content(another_user.username) + end + end + + it 'sees impersonation log out icon' do + icon = first('.fa.fa-user-secret') + + expect(icon).to_not eql nil + end + + it 'can log out of impersonated user back to original user' do + find(:css, 'li.impersonation a').click + + page.within '.sidebar-user .username' do + expect(page).to have_content(@user.username) + end + end + + it 'is redirected back to the impersonated users page in the admin after stopping' do + find(:css, 'li.impersonation a').click + + expect(current_path).to eql "/admin/users/#{another_user.username}" + end end end From 72ececfab18d4577fbd7e57d44b771576eb204bf Mon Sep 17 00:00:00 2001 From: Thirumal S Date: Fri, 30 Oct 2015 17:27:22 +0530 Subject: [PATCH 004/140] form-control class to select box --- app/views/profiles/notifications/_settings.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/profiles/notifications/_settings.html.haml b/app/views/profiles/notifications/_settings.html.haml index 2c85d2a9b2..742c5c4b68 100644 --- a/app/views/profiles/notifications/_settings.html.haml +++ b/app/views/profiles/notifications/_settings.html.haml @@ -14,4 +14,4 @@ = form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do = hidden_field_tag :notification_type, type, id: dom_id(membership, 'notification_type') = hidden_field_tag :notification_id, membership.id, id: dom_id(membership, 'notification_id') - = select_tag :notification_level, options_for_select(Notification.options_with_labels, notification.level), class: 'trigger-submit' + = select_tag :notification_level, options_for_select(Notification.options_with_labels, notification.level), class: 'form-control trigger-submit' From 74416daa660a047d3a7cb00e11e1d775b4ea0937 Mon Sep 17 00:00:00 2001 From: Thirumal S Date: Fri, 30 Oct 2015 19:26:56 +0530 Subject: [PATCH 005/140] form group alignment issue fixed in webhook index page --- app/assets/stylesheets/pages/projects.scss | 4 ++++ app/views/projects/hooks/index.html.haml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index f7a2284900..b384e3fae6 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -511,3 +511,7 @@ pre.light-well { margin-top: -1px; } } + +.form-control-padding-top { + padding-top: 10px; +} diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml index 85dbfd6786..65e00b38ad 100644 --- a/app/views/projects/hooks/index.html.haml +++ b/app/views/projects/hooks/index.html.haml @@ -19,7 +19,7 @@ = f.text_field :url, class: "form-control", placeholder: 'http://example.com/trigger-ci.json' .form-group = f.label :url, "Trigger", class: 'control-label' - .col-sm-10 + .col-sm-10.form-control-padding-top %div = f.check_box :push_events, class: 'pull-left' .prepend-left-20 From 91cbf9db0c5c95381e7d422fd684e623d58fadab Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 26 Oct 2015 07:26:03 +0100 Subject: [PATCH 006/140] Add allow_failure field to commit status API Closes #3196 --- CHANGELOG | 1 + doc/api/commits.md | 6 ++++-- lib/api/entities.rb | 2 +- spec/requests/api/commit_status_spec.rb | 4 +++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 16055208db..e7238c4566 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.2.0 (unreleased) - Force update refs/merge-requests/X/head upon a push to the source branch of a merge request (Stan Hu) - Improved performance of finding users by one of their Email addresses + - Add allow_failure field to commit status API (Stan Hu) - Improved performance of replacing references in comments - Show last project commit to default branch on project home page - Highlight comment based on anchor in URL diff --git a/doc/api/commits.md b/doc/api/commits.md index 9f72adc6ed..8e4a0ee1b8 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -22,7 +22,8 @@ Parameters: "author_name": "Dmitriy Zaporozhets", "author_email": "dzaporozhets@sphereconsultinginc.com", "created_at": "2012-09-20T11:50:22+03:00", - "message": "Replace sanitize with escape once" + "message": "Replace sanitize with escape once", + "allow_failure": false }, { "id": "6104942438c14ec7bd21c6cd5bd995272b3faff6", @@ -31,7 +32,8 @@ Parameters: "author_name": "randx", "author_email": "dmitriy.zaporozhets@gmail.com", "created_at": "2012-09-20T09:06:12+03:00", - "message": "Sanitize for network graph" + "message": "Sanitize for network graph", + "allow_failure": false } ] ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 883a5e14b1..20cadae229 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -231,7 +231,7 @@ module API class CommitStatus < Grape::Entity expose :id, :sha, :ref, :status, :name, :target_url, :description, - :created_at, :started_at, :finished_at + :created_at, :started_at, :finished_at, :allow_failure expose :author, using: Entities::UserBasic end diff --git a/spec/requests/api/commit_status_spec.rb b/spec/requests/api/commit_status_spec.rb index b9e6dfc15a..a28607bd24 100644 --- a/spec/requests/api/commit_status_spec.rb +++ b/spec/requests/api/commit_status_spec.rb @@ -18,7 +18,7 @@ describe API::API, api: true do before do @status1 = create(:commit_status, commit: ci_commit, status: 'running') @status2 = create(:commit_status, commit: ci_commit, name: 'coverage', status: 'pending') - @status3 = create(:commit_status, commit: ci_commit, name: 'coverage', ref: 'develop', status: 'running') + @status3 = create(:commit_status, commit: ci_commit, name: 'coverage', ref: 'develop', status: 'running', allow_failure: true) @status4 = create(:commit_status, commit: ci_commit, name: 'coverage', status: 'success') @status5 = create(:commit_status, commit: ci_commit, ref: 'develop', status: 'success') @status6 = create(:commit_status, commit: ci_commit, status: 'success') @@ -30,6 +30,8 @@ describe API::API, api: true do expect(json_response).to be_an Array expect(statuses_id).to contain_exactly(@status3.id, @status4.id, @status5.id, @status6.id) + json_response.sort_by!{ |status| status['id'] } + expect(json_response.map{ |status| status['allow_failure'] }).to eq([true, false, false, false]) end it "should return all commit statuses" do From bdb45360e76a272c275690cb11227a737c56e934 Mon Sep 17 00:00:00 2001 From: Tim Jabs Date: Thu, 8 Oct 2015 18:06:51 +0200 Subject: [PATCH 007/140] Changed documentation of converting a MySQL-Database with Gitlab to a Postgresdatatabase. The instructions were missleading. See -> https://gitlab.com/gitlab-org/gitlab-ce/issues/2904 --- doc/update/mysql_to_postgresql.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/update/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md index a596ea3845..a7de5648c0 100644 --- a/doc/update/mysql_to_postgresql.md +++ b/doc/update/mysql_to_postgresql.md @@ -60,6 +60,9 @@ sudo -u git -H python mysql-postgresql-converter/db_converter.py gitlabhq_produc sudo -u git -H ed -s db/database.sql < mysql-postgresql-converter/move_drop_indexes.ed # Compress database backup +# Warning: If you have Gitlab 7.12.0 or older skip this step and import the database.sql directly into the backup with: +# sudo -u git -H tar rf TIMESTAMP_gitlab_backup.tar db/database.sql +# The compressed databasedump is not supported at 7.12.0 and older. sudo -u git -H gzip db/database.sql # Replace the MySQL dump in TIMESTAMP_gitlab_backup.tar. @@ -71,4 +74,5 @@ sudo -u git -H tar rf TIMESTAMP_gitlab_backup.tar db/database.sql.gz # Done! TIMESTAMP_gitlab_backup.tar can now be restored into a Postgres GitLab # installation. +# See https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/raketasks/backup_restore.md for more information about backups. ``` From 8508be74d2063bb86e39d1cc8667e8adb9dfe63e Mon Sep 17 00:00:00 2001 From: Kjel Delaey Date: Wed, 4 Nov 2015 15:55:19 +0100 Subject: [PATCH 008/140] Style inline event items with titles containing long words properly.. On a tablet in portrait mode with a width of 768px the event-title isn't aligned properly when the title contains a long string. This also happens when resizing your browser viewport on a desktop. Example string: Administrator pushed new branch feature-branch-with-a-very-long-name at Gitlab Org / Gitlab Test In the UI it would look like the example below: ---------- | | | AVATAR | less than a minute ago | | ---------- Administrator pushed new branch feature-branch-with-a-very-long-name at... --- app/assets/stylesheets/pages/events.scss | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index dfb901652b..d2ca106cc8 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -11,9 +11,12 @@ color: #7f8fa4; &.event-inline { + padding-left: $gl-padding + $gl-avatar-size; + .avatar { position: relative; top: -2px; + margin-left: -$gl-avatar-size; } .event-title { @@ -155,6 +158,10 @@ @media (max-width: $screen-xs-max) { .event-item { + &.event-inline { + padding-left: $gl-padding; + } + .event-title { white-space: normal; overflow: visible; From 8b0906c86d10ff02865e99400f2c0e3bdd1ac6fe Mon Sep 17 00:00:00 2001 From: Kjel Delaey Date: Wed, 4 Nov 2015 16:22:27 +0100 Subject: [PATCH 009/140] Apply the same rules to block event items.. Try to avoid alignment issues as well. --- app/assets/stylesheets/pages/events.scss | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index d2ca106cc8..02401c7c73 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -4,19 +4,16 @@ */ .event-item { font-size: $gl-font-size; - padding: $gl-padding; + padding: $gl-padding $gl-padding $gl-padding ($gl-padding + $gl-avatar-size); margin-left: -$gl-padding; margin-right: -$gl-padding; border-bottom: 1px solid $table-border-color; color: #7f8fa4; &.event-inline { - padding-left: $gl-padding + $gl-avatar-size; - .avatar { position: relative; top: -2px; - margin-left: -$gl-avatar-size; } .event-title { @@ -33,6 +30,7 @@ } .avatar { + margin-left: -$gl-avatar-size; margin-right: 15px; } @@ -46,9 +44,6 @@ } .event-body { - margin-left: 63px; - margin-right: 80px; - .event-note { margin-top: 5px; word-wrap: break-word; @@ -158,9 +153,7 @@ @media (max-width: $screen-xs-max) { .event-item { - &.event-inline { - padding-left: $gl-padding; - } + padding-left: $gl-padding; .event-title { white-space: normal; From 3f2955de3bf08ef1ec378d323fb2360547a4ca16 Mon Sep 17 00:00:00 2001 From: Kjel Delaey Date: Wed, 4 Nov 2015 16:23:39 +0100 Subject: [PATCH 010/140] Combine selectors because the same style is being applied --- app/assets/stylesheets/pages/events.scss | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index 02401c7c73..f8d1afda73 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -16,10 +16,7 @@ top: -2px; } - .event-title { - line-height: 44px; - } - + .event-title, .event-item-timestamp { line-height: 44px; } From 530c9519d4d0760848daf5026f93fbcbff348656 Mon Sep 17 00:00:00 2001 From: Kjel Delaey Date: Wed, 4 Nov 2015 16:40:22 +0100 Subject: [PATCH 011/140] Make sure that multi-commit rows inside event bodies are aligned properly.. Prevent the second, third, ... rows from having a different indentation than the first commit row. Adding the extra "15px padding" to the event item prevents this. Having a 15px margin on the avatar only doesn't prevent this from happening. --- app/assets/stylesheets/pages/events.scss | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index f8d1afda73..c150b4ac19 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -4,7 +4,7 @@ */ .event-item { font-size: $gl-font-size; - padding: $gl-padding $gl-padding $gl-padding ($gl-padding + $gl-avatar-size); + padding: $gl-padding $gl-padding $gl-padding ($gl-padding + $gl-avatar-size + 15px); margin-left: -$gl-padding; margin-right: -$gl-padding; border-bottom: 1px solid $table-border-color; @@ -27,8 +27,7 @@ } .avatar { - margin-left: -$gl-avatar-size; - margin-right: 15px; + margin-left: -($gl-avatar-size + 15px); } .event-title { From dfccc28e38b6298eff8eeedf1ba91a93c0182353 Mon Sep 17 00:00:00 2001 From: Vincent Lequertier Date: Wed, 4 Nov 2015 19:04:20 +0100 Subject: [PATCH 012/140] It makes more sense like this --- doc/install/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 599d2541f2..5a09cf7c96 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -253,8 +253,8 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da nproc # Enable cluster mode if you expect to have a high load instance - # Ex. change amount of workers to 3 for 2GB RAM server # Set the number of workers to at least the number of cores + # Ex. change amount of workers to 3 for 2GB RAM server sudo -u git -H editor config/unicorn.rb # Copy the example Rack attack config From b6f89c3490f8c31607073c9761de3ebb30ae244e Mon Sep 17 00:00:00 2001 From: Kjel Delaey Date: Thu, 5 Nov 2015 09:56:51 +0100 Subject: [PATCH 013/140] Add right margin to event-body.. Otherwise text will flow under the "timeago" element. The original value was 80px (see commit 8b0906c8), but a value of 174px makes more sense. (see event-title `calc` function). --- app/assets/stylesheets/pages/events.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index c150b4ac19..282aaf2219 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -40,6 +40,8 @@ } .event-body { + margin-right: 174px; + .event-note { margin-top: 5px; word-wrap: break-word; From 33b8f002636ad6171637108b53732c74d90b14ad Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 5 Nov 2015 10:51:12 +0100 Subject: [PATCH 014/140] Add edit/update tag actions for future release notes Signed-off-by: Dmitriy Zaporozhets --- app/controllers/projects/tags_controller.rb | 8 ++++++++ app/views/projects/tags/_tag.html.haml | 2 ++ app/views/projects/tags/edit.html.haml | 0 config/routes.rb | 2 +- 4 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 app/views/projects/tags/edit.html.haml diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index f565fbbbbc..a30c284c41 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -10,6 +10,14 @@ class Projects::TagsController < Projects::ApplicationController @tags = Kaminari.paginate_array(sorted).page(params[:page]).per(PER_PAGE) end + def edit + # TODO: implement + end + + def update + # TODO: implement + end + def create result = CreateTagService.new(@project, current_user). execute(params[:tag_name], params[:ref], params[:message]) diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index 2ca295fc5f..887f3ab075 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -9,6 +9,8 @@   = strip_gpg_signature(tag.message) .controls + = link_to edit_namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn-grouped btn' do + = icon("pencil") - if can? current_user, :download_code, @project = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-xs' - if can?(current_user, :admin_project, @project) diff --git a/app/views/projects/tags/edit.html.haml b/app/views/projects/tags/edit.html.haml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/config/routes.rb b/config/routes.rb index 0458f538eb..8dc167cbbd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -569,7 +569,7 @@ Gitlab::Application.routes.draw do end resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } - resources :tags, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } + resources :tags, constraints: { id: Gitlab::Regex.git_reference_regex } resources :protected_branches, only: [:index, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } resource :variables, only: [:show, :update] resources :triggers, only: [:index, :create, :destroy] From 1c4d1c3bd69a6f9ec43cce4ab59de4ba47f73229 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 5 Nov 2015 11:03:02 +0100 Subject: [PATCH 015/140] Add release model Signed-off-by: Dmitriy Zaporozhets --- app/models/release.rb | 2 ++ db/migrate/20151105094515_create_releases.rb | 14 ++++++++++++++ db/schema.rb | 13 ++++++++++++- spec/factories/releases.rb | 9 +++++++++ spec/models/release_spec.rb | 5 +++++ 5 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 app/models/release.rb create mode 100644 db/migrate/20151105094515_create_releases.rb create mode 100644 spec/factories/releases.rb create mode 100644 spec/models/release_spec.rb diff --git a/app/models/release.rb b/app/models/release.rb new file mode 100644 index 0000000000..1dc9ce6dd4 --- /dev/null +++ b/app/models/release.rb @@ -0,0 +1,2 @@ +class Release < ActiveRecord::Base +end diff --git a/db/migrate/20151105094515_create_releases.rb b/db/migrate/20151105094515_create_releases.rb new file mode 100644 index 0000000000..fe4608c666 --- /dev/null +++ b/db/migrate/20151105094515_create_releases.rb @@ -0,0 +1,14 @@ +class CreateReleases < ActiveRecord::Migration + def change + create_table :releases do |t| + t.string :tag + t.text :description + t.integer :project_id + + t.timestamps + end + + add_index :releases, :project_id + add_index :releases, [:project_id, :tag] + end +end diff --git a/db/schema.rb b/db/schema.rb index 73fc83c3d6..3444902447 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20151026182941) do +ActiveRecord::Schema.define(version: 20151105094515) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -637,6 +637,17 @@ ActiveRecord::Schema.define(version: 20151026182941) do add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree + create_table "releases", force: true do |t| + t.string "tag" + t.text "description" + t.integer "project_id" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "releases", ["project_id", "tag"], name: "index_releases_on_project_id_and_tag", using: :btree + add_index "releases", ["project_id"], name: "index_releases_on_project_id", using: :btree + create_table "sent_notifications", force: true do |t| t.integer "project_id" t.integer "noteable_id" diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb new file mode 100644 index 0000000000..067d8138e4 --- /dev/null +++ b/spec/factories/releases.rb @@ -0,0 +1,9 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :release do + tag "MyString" + description "MyText" + project_id 1 + end +end diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb new file mode 100644 index 0000000000..e533734ba0 --- /dev/null +++ b/spec/models/release_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Release, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end From ba67af79a9ec0d37d08e51af034dd6c21170713c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 5 Nov 2015 11:16:41 +0100 Subject: [PATCH 016/140] More release related logic to separate resource Signed-off-by: Dmitriy Zaporozhets --- .../projects/releases_controller.rb | 32 +++++++++++++++++++ app/controllers/projects/tags_controller.rb | 8 ----- app/models/project.rb | 3 +- app/views/projects/releases/edit.html.haml | 6 ++++ app/views/projects/releases/show.html.haml | 1 + app/views/projects/tags/_tag.html.haml | 2 +- app/views/projects/tags/edit.html.haml | 0 config/routes.rb | 5 ++- 8 files changed, 46 insertions(+), 11 deletions(-) create mode 100644 app/controllers/projects/releases_controller.rb create mode 100644 app/views/projects/releases/edit.html.haml create mode 100644 app/views/projects/releases/show.html.haml delete mode 100644 app/views/projects/tags/edit.html.haml diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb new file mode 100644 index 0000000000..877cc0f367 --- /dev/null +++ b/app/controllers/projects/releases_controller.rb @@ -0,0 +1,32 @@ +class Projects::ReleasesController < Projects::ApplicationController + # Authorize + before_action :require_non_empty_project + before_action :authorize_download_code! + before_action :authorize_push_code! + before_action :tag + before_action :release + + def show + end + + def edit + end + + def update + description = params[:release][:description] + release.update_attributes(description: description) + release.save + + redirect_to namespace_project_tag_release_path(@project.namespace, @project, @tag.name) + end + + private + + def tag + @tag ||= @repository.find_tag(params[:tag_id]) + end + + def release + @release ||= @project.releases.find_or_initialize_by(tag: @tag.name) + end +end diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index a30c284c41..f565fbbbbc 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -10,14 +10,6 @@ class Projects::TagsController < Projects::ApplicationController @tags = Kaminari.paginate_array(sorted).page(params[:page]).per(PER_PAGE) end - def edit - # TODO: implement - end - - def update - # TODO: implement - end - def create result = CreateTagService.new(@project, current_user). execute(params[:tag_name], params[:ref], params[:message]) diff --git a/app/models/project.rb b/app/models/project.rb index 74b89aad49..cd3de01a95 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -121,6 +121,7 @@ class Project < ActiveRecord::Base has_many :starrers, through: :users_star_projects, source: :user has_many :ci_commits, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id has_many :ci_builds, through: :ci_commits, source: :builds, dependent: :destroy, class_name: 'Ci::Build' + has_many :releases, dependent: :destroy has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" has_one :gitlab_ci_project, dependent: :destroy, class_name: "Ci::Project", foreign_key: :gitlab_id @@ -247,7 +248,7 @@ class Project < ActiveRecord::Base joins(:namespace). iwhere('namespaces.path' => namespace_path) - projects.where('projects.path' => project_path).take || + projects.where('projects.path' => project_path).take || projects.iwhere('projects.path' => project_path).take end diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml new file mode 100644 index 0000000000..3b4a5e7223 --- /dev/null +++ b/app/views/projects/releases/edit.html.haml @@ -0,0 +1,6 @@ += form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal gfm-form' }) do |f| + = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do + = render 'projects/zen', f: f, attr: :description, classes: 'js-quick-submit' + = render 'projects/notes/hints' + .error-alert + = f.submit 'Save changes', class: 'btn btn-save' diff --git a/app/views/projects/releases/show.html.haml b/app/views/projects/releases/show.html.haml new file mode 100644 index 0000000000..bd83150008 --- /dev/null +++ b/app/views/projects/releases/show.html.haml @@ -0,0 +1 @@ += debug @release diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index 887f3ab075..526dd3f580 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -9,7 +9,7 @@   = strip_gpg_signature(tag.message) .controls - = link_to edit_namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn-grouped btn' do + = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn-grouped btn' do = icon("pencil") - if can? current_user, :download_code, @project = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-xs' diff --git a/app/views/projects/tags/edit.html.haml b/app/views/projects/tags/edit.html.haml deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/config/routes.rb b/config/routes.rb index 8dc167cbbd..5836da47eb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -569,7 +569,10 @@ Gitlab::Application.routes.draw do end resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } - resources :tags, constraints: { id: Gitlab::Regex.git_reference_regex } + resources :tags, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } do + resource :release + end + resources :protected_branches, only: [:index, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } resource :variables, only: [:show, :update] resources :triggers, only: [:index, :create, :destroy] From a4d75e3aec2e721231bc1e01a2e5e87aefe15113 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 5 Nov 2015 12:15:25 +0100 Subject: [PATCH 017/140] Add ability to edit and show release notes Signed-off-by: Dmitriy Zaporozhets --- app/assets/javascripts/dispatcher.js.coffee | 3 ++ app/assets/stylesheets/framework/common.scss | 1 + .../projects/releases_controller.rb | 1 + app/views/layouts/nav/_project.html.haml | 2 +- app/views/projects/commits/_head.html.haml | 2 +- app/views/projects/releases/edit.html.haml | 24 +++++++++---- app/views/projects/releases/show.html.haml | 36 ++++++++++++++++++- 7 files changed, 60 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 5bf0b30217..030826be74 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -39,6 +39,9 @@ class Dispatcher shortcut_handler = new ShortcutsNavigation() new DropzoneInput($('.merge-request-form')) new IssuableForm($('.merge-request-form')) + when 'projects:releases:edit' + new ZenMode() + new DropzoneInput($('.release-form')) when 'projects:merge_requests:show' new Diff() shortcut_handler = new ShortcutsIssuable() diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 41287d52f6..ddbacd7fd4 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -16,6 +16,7 @@ .append-bottom-10 { margin-bottom:10px } .append-bottom-15 { margin-bottom:15px } .append-bottom-20 { margin-bottom:20px } +.append-bottom-default { margin-bottom: $gl-padding; } .inline { display: inline-block } .center { text-align: center } diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb index 877cc0f367..7d1a011cc0 100644 --- a/app/controllers/projects/releases_controller.rb +++ b/app/controllers/projects/releases_controller.rb @@ -7,6 +7,7 @@ class Projects::ReleasesController < Projects::ApplicationController before_action :release def show + @commit = @repository.commit(@tag.target) end def edit diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 20db2866d1..2b91d7721f 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -32,7 +32,7 @@ Files - if project_nav_tab? :commits - = nav_link(controller: %w(commit commits compare repositories tags branches)) do + = nav_link(controller: %w(commit commits compare repositories tags branches releases)) do = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits', data: {placement: 'right'} do = icon('history fw') %span diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml index a849bf8469..f11a41cfd7 100644 --- a/app/views/projects/commits/_head.html.haml +++ b/app/views/projects/commits/_head.html.haml @@ -12,7 +12,7 @@ Branches %span.badge.js-totalbranch-count= @repository.branches.size - = nav_link(controller: :tags) do + = nav_link(controller: [:tags, :releases]) do = link_to namespace_project_tags_path(@project.namespace, @project) do Tags %span.badge.js-totaltags-count= @repository.tags.length diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml index 3b4a5e7223..612e469622 100644 --- a/app/views/projects/releases/edit.html.haml +++ b/app/views/projects/releases/edit.html.haml @@ -1,6 +1,18 @@ -= form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal gfm-form' }) do |f| - = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do - = render 'projects/zen', f: f, attr: :description, classes: 'js-quick-submit' - = render 'projects/notes/hints' - .error-alert - = f.submit 'Save changes', class: 'btn btn-save' += render "projects/commits/header_title" += render "projects/commits/head" + +.gray-content-block + .oneline + Release notes for #{@tag.name} + +.prepend-top-default + = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal gfm-form release-form' }) do |f| + = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do + = render 'projects/zen', f: f, attr: :description, classes: 'description js-quick-submit' + = render 'projects/notes/hints' + .error-alert + .prepend-top-default + = f.submit 'Save changes', class: 'btn btn-save' + - if @release.persisted? + = link_to "Cancel", namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel" + diff --git a/app/views/projects/releases/show.html.haml b/app/views/projects/releases/show.html.haml index bd83150008..606510f132 100644 --- a/app/views/projects/releases/show.html.haml +++ b/app/views/projects/releases/show.html.haml @@ -1 +1,35 @@ -= debug @release +- page_title @release.tag, "Releases" += render "projects/commits/header_title" += render "projects/commits/head" + +.gray-content-block + .pull-right + = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn' do + = icon("pencil") + .oneline Release notes for #{@tag.name} + +.append-bottom-default.prepend-top-default + - if @release.description.present? + .description + .wiki + = preserve do + = markdown @release.description + - else + This tag has no release notes yet. Press edit button to add one + +.gray-content-block.middle-block.clearfix + = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped' do + Browse code + = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped' do + Commits + - if can? current_user, :download_code, @project + = render 'projects/repositories/download_archive', ref: @tag.name, btn_class: 'btn-grouped' + -#- if can?(current_user, :admin_project, @project) + = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'}, remote: true do + %i.fa.fa-trash-o + +.gray-content-block.second-block + - if @commit + = render 'projects/commits/commit', commit: @commit, project: @project + - else + Cant find HEAD commit for this tag From 850bb21b12b21fe0cf943278bc8cadad85d48dc5 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 5 Nov 2015 13:49:34 +0100 Subject: [PATCH 018/140] Create show page for tag and render release notes there and on index page Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/callout.scss | 6 +++--- app/assets/stylesheets/pages/commits.scss | 7 +++++++ .../projects/releases_controller.rb | 6 +----- app/controllers/projects/tags_controller.rb | 7 +++++++ app/views/projects/branches/_commit.html.haml | 4 ++-- app/views/projects/releases/edit.html.haml | 3 +-- app/views/projects/tags/_tag.html.haml | 11 ++++++++++- .../{releases => tags}/show.html.haml | 19 ++++++++++++++----- config/routes.rb | 4 ++-- 9 files changed, 47 insertions(+), 20 deletions(-) rename app/views/projects/{releases => tags}/show.html.haml (68%) diff --git a/app/assets/stylesheets/framework/callout.scss b/app/assets/stylesheets/framework/callout.scss index f1699d21c9..f3ce4e3c21 100644 --- a/app/assets/stylesheets/framework/callout.scss +++ b/app/assets/stylesheets/framework/callout.scss @@ -9,9 +9,9 @@ .bs-callout { margin: 20px 0; padding: 20px; - border-left: 3px solid #eee; - color: #666; - background: #f9f9f9; + border-left: 3px solid $border-color; + color: $text-color; + background: $background-color; } .bs-callout h4 { margin-top: 0; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index e485487bcf..c9dfcff629 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -115,3 +115,10 @@ li.commit { } } } + +.branch-commit { + color: $gl-gray; + .commit-id, .commit-row-message { + color: $gl-gray; + } +} diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb index 7d1a011cc0..f69a4bc729 100644 --- a/app/controllers/projects/releases_controller.rb +++ b/app/controllers/projects/releases_controller.rb @@ -6,10 +6,6 @@ class Projects::ReleasesController < Projects::ApplicationController before_action :tag before_action :release - def show - @commit = @repository.commit(@tag.target) - end - def edit end @@ -18,7 +14,7 @@ class Projects::ReleasesController < Projects::ApplicationController release.update_attributes(description: description) release.save - redirect_to namespace_project_tag_release_path(@project.namespace, @project, @tag.name) + redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name) end private diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index f565fbbbbc..dfc8dbe01c 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -8,6 +8,13 @@ class Projects::TagsController < Projects::ApplicationController def index sorted = VersionSorter.rsort(@repository.tag_names) @tags = Kaminari.paginate_array(sorted).page(params[:page]).per(PER_PAGE) + @releases = project.releases.where(tag: @tags) + end + + def show + @tag = @repository.find_tag(params[:id]) + @release = @project.releases.find_or_initialize_by(tag: @tag.name) + @commit = @repository.commit(@tag.target) end def create diff --git a/app/views/projects/branches/_commit.html.haml b/app/views/projects/branches/_commit.html.haml index 68326e65d8..22d77dda93 100644 --- a/app/views/projects/branches/_commit.html.haml +++ b/app/views/projects/branches/_commit.html.haml @@ -1,5 +1,5 @@ -.branch-commit.light - = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" +.branch-commit + = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-id" · %span.str-truncated = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml index 612e469622..fb841b77a2 100644 --- a/app/views/projects/releases/edit.html.haml +++ b/app/views/projects/releases/edit.html.haml @@ -13,6 +13,5 @@ .error-alert .prepend-top-default = f.submit 'Save changes', class: 'btn btn-save' - - if @release.persisted? - = link_to "Cancel", namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel" + = link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel" diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index 526dd3f580..5d203903f8 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -1,13 +1,17 @@ - commit = @repository.commit(tag.target) +- release = @releases.find { |release| release.tag == tag.name } %li %div - = link_to namespace_project_commits_path(@project.namespace, @project, tag.name), class: "" do + = link_to namespace_project_tag_path(@project.namespace, @project, tag.name) do %strong %i.fa.fa-tag = tag.name - if tag.message.present?   = strip_gpg_signature(tag.message) + - if release + %span.label.label-success release + .controls = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn-grouped btn' do = icon("pencil") @@ -22,3 +26,8 @@ - else %p Cant find HEAD commit for this tag + - if release && release.description.present? + .description.prepend-top-default + .wiki + = preserve do + = markdown release.description diff --git a/app/views/projects/releases/show.html.haml b/app/views/projects/tags/show.html.haml similarity index 68% rename from app/views/projects/releases/show.html.haml rename to app/views/projects/tags/show.html.haml index 606510f132..d2f23b96a8 100644 --- a/app/views/projects/releases/show.html.haml +++ b/app/views/projects/tags/show.html.haml @@ -1,4 +1,4 @@ -- page_title @release.tag, "Releases" +- page_title @tag.name, "Tags" = render "projects/commits/header_title" = render "projects/commits/head" @@ -6,7 +6,14 @@ .pull-right = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn' do = icon("pencil") - .oneline Release notes for #{@tag.name} + - if @tag.message.present? + .title + %strong= @tag.name + = strip_gpg_signature(@tag.message) + - else + .oneline + .title + %strong= @tag.name .append-bottom-default.prepend-top-default - if @release.description.present? @@ -24,12 +31,14 @@ Commits - if can? current_user, :download_code, @project = render 'projects/repositories/download_archive', ref: @tag.name, btn_class: 'btn-grouped' - -#- if can?(current_user, :admin_project, @project) - = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'}, remote: true do - %i.fa.fa-trash-o + - if can?(current_user, :admin_project, @project) + .pull-right + = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'}, remote: true do + %i.fa.fa-trash-o .gray-content-block.second-block - if @commit = render 'projects/commits/commit', commit: @commit, project: @project - else Cant find HEAD commit for this tag + diff --git a/config/routes.rb b/config/routes.rb index 5836da47eb..c7aa95118e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -569,8 +569,8 @@ Gitlab::Application.routes.draw do end resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } - resources :tags, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } do - resource :release + resources :tags, only: [:index, :show, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } do + resource :release, only: [:edit, :update] end resources :protected_branches, only: [:index, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } From 312cf11b61e6bbee8283dfb267516e6b42454431 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 5 Nov 2015 14:03:48 +0100 Subject: [PATCH 019/140] Add release description to new tag form Signed-off-by: Dmitriy Zaporozhets --- app/assets/javascripts/dispatcher.js.coffee | 3 +++ app/controllers/projects/tags_controller.rb | 7 +++++++ app/views/projects/tags/new.html.haml | 19 ++++++++++++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 030826be74..951173af5d 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -39,6 +39,9 @@ class Dispatcher shortcut_handler = new ShortcutsNavigation() new DropzoneInput($('.merge-request-form')) new IssuableForm($('.merge-request-form')) + when 'projects:tags:new' + new ZenMode() + new DropzoneInput($('.tag-form')) when 'projects:releases:edit' new ZenMode() new DropzoneInput($('.release-form')) diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index dfc8dbe01c..c4a3e3dca9 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -23,6 +23,13 @@ class Projects::TagsController < Projects::ApplicationController if result[:status] == :success @tag = result[:tag] + + if params[:release_description] + release = @project.releases.find_or_initialize_by(tag: @tag.name) + release.update_attributes(description: params[:release_description]) + release.save + end + redirect_to namespace_project_tags_path(@project.namespace, @project) else @error = result[:message] diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml index 9f5c1be125..9b224ff89b 100644 --- a/app/views/projects/tags/new.html.haml +++ b/app/views/projects/tags/new.html.haml @@ -8,7 +8,7 @@ %h3.page-title %i.fa.fa-code-fork New tag -= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal" do += form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal tag-form" do .form-group = label_tag :tag_name, 'Name for new tag', class: 'control-label' .col-sm-10 @@ -23,6 +23,23 @@ .col-sm-10 = text_field_tag :message, nil, placeholder: 'Enter message.', required: false, tabindex: 3, class: 'form-control' .light (Optional) Entering a message will create an annotated tag. + %hr + .form-group + = label_tag :release_description, 'Release description', class: 'control-label' + .col-sm-10 + = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do + .zennable + %input#zen-toggle-comment.zen-toggle-comment(tabindex="-1" type="checkbox") + .zen-backdrop + = text_area_tag :release_description, nil, class: 'js-gfm-input markdown-area description js-quick-submit form-control', placeholder: '' + %a.zen-enter-link(tabindex="-1" href="#") + = icon('expand') + Edit in fullscreen + %a.zen-leave-link(href="#") + = icon('compress') + + = render 'projects/notes/hints' + .help-block You can add release description to your tag. It will be stored in GitLab database and displayed on tags page .form-actions = button_tag 'Create tag', class: 'btn btn-create', tabindex: 3 = link_to 'Cancel', namespace_project_tags_path(@project.namespace, @project), class: 'btn btn-cancel' From 26677fbe213069a3820f9f20d528bd560d447bea Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 5 Nov 2015 14:07:55 +0100 Subject: [PATCH 020/140] After tag is created - redirect to tag page Signed-off-by: Dmitriy Zaporozhets --- app/controllers/projects/tags_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index c4a3e3dca9..055f328677 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -30,7 +30,7 @@ class Projects::TagsController < Projects::ApplicationController release.save end - redirect_to namespace_project_tags_path(@project.namespace, @project) + redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name) else @error = result[:message] render action: 'new' From ba68facf8d744f6de49b40a2e9923e6555c92cd7 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 3 Nov 2015 11:44:07 +0100 Subject: [PATCH 021/140] CI details cleanup - Add page titles to CI settings. - Fix CI admin navigation. - Remove duplicated scope. - Use monospace font for commit sha. - Add page title and header title to build page. - Proper authorization for cancel/retry builds. - Use gitlab pagination theme for builds and group members. - Don't paginate builds widget on build page. - Add badges to commit page Changes/Builds tabs. - Add "Builds" to commit Builds tab page title. - Add and use Ci::Build#retryable? method. - Add CI::Build#retried? method. - Allow all failed commit builds to be retried. - Proper authorization for cancel/retry all builds. - Remove unused param. - Use time_ago_with_tooltip where appropriate. - Tweak builds index text - Remove duplication between builds/build and commit_statuses/commit_status. - Use POST rather than GET for canceling and retrying builds. - Remove redundant URL helpers. - Add build ID to build page. - Link branch name on build page. - Move commit/:sha/ci to commit/:sha/builds. --- app/controllers/projects/builds_controller.rb | 10 ++-- app/controllers/projects/commit_controller.rb | 38 ++++++++----- app/helpers/builds_helper.rb | 13 ----- app/helpers/ci/gitlab_helper.rb | 19 ------- app/helpers/ci_status_helper.rb | 2 +- app/models/ci/build.rb | 10 +++- app/models/commit_status.rb | 4 +- .../project_services/ci/hip_chat_message.rb | 2 +- .../project_services/ci/slack_message.rb | 2 +- .../project_services/gitlab_ci_service.rb | 2 +- .../ci/notify/build_fail_email.html.haml | 4 +- .../ci/notify/build_success_email.html.haml | 4 +- app/views/dashboard/groups/index.html.haml | 2 +- app/views/layouts/ci/_nav_admin.html.haml | 18 ++++--- app/views/projects/_last_commit.html.haml | 2 +- app/views/projects/builds/_build.html.haml | 53 ------------------- .../projects/builds/_header_title.html.haml | 1 + app/views/projects/builds/index.html.haml | 12 ++--- app/views/projects/builds/show.html.haml | 39 +++++++------- app/views/projects/ci_services/edit.html.haml | 1 + .../projects/ci_services/index.html.haml | 1 + app/views/projects/ci_settings/edit.html.haml | 1 + .../projects/ci_web_hooks/index.html.haml | 1 + app/views/projects/commit/_ci_menu.html.haml | 6 ++- .../projects/commit/_commit_box.html.haml | 4 +- .../commit/{ci.html.haml => builds.html.haml} | 9 ++-- .../commit_statuses/_commit_status.html.haml | 31 ++++++++--- app/views/projects/runners/edit.html.haml | 2 + app/views/projects/runners/index.html.haml | 1 + app/views/projects/runners/show.html.haml | 21 ++++---- app/views/projects/triggers/index.html.haml | 1 + app/views/projects/variables/show.html.haml | 1 + config/routes.rb | 9 ++-- spec/features/builds_spec.rb | 14 +++-- spec/features/commits_spec.rb | 2 +- .../gitlab_ci_service_spec.rb | 2 +- 36 files changed, 160 insertions(+), 184 deletions(-) delete mode 100644 app/helpers/builds_helper.rb delete mode 100644 app/views/projects/builds/_build.html.haml create mode 100644 app/views/projects/builds/_header_title.html.haml rename app/views/projects/commit/{ci.html.haml => builds.html.haml} (77%) diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index 7d72e0b951..953f30e7c0 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -30,7 +30,7 @@ class Projects::BuildsController < Projects::ApplicationController def show @builds = @ci_project.commits.find_by_sha(@build.sha).builds.order('id DESC') - @builds = @builds.where("id not in (?)", @build.id).page(params[:page]).per(20) + @builds = @builds.where("id not in (?)", @build.id) @commit = @build.commit respond_to do |format| @@ -42,17 +42,13 @@ class Projects::BuildsController < Projects::ApplicationController end def retry - if @build.commands.blank? + unless @build.retryable? return page_404 end build = Ci::Build.retry(@build) - if params[:return_to] - redirect_to URI.parse(params[:return_to]).path - else - redirect_to build_path(build) - end + redirect_to build_path(build) end def status diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 878c3a66e7..deefdd7666 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -7,14 +7,14 @@ class Projects::CommitController < Projects::ApplicationController before_action :authorize_download_code!, except: [:cancel_builds] before_action :authorize_manage_builds!, only: [:cancel_builds] before_action :commit + before_action :authorize_manage_builds!, only: [:cancel_builds, :retry_builds] + before_action :define_show_vars, only: [:show, :builds] def show return git_not_found! unless @commit @line_notes = commit.notes.inline - @diffs = @commit.diffs @note = @project.build_commit_note(commit) - @notes_count = commit.notes.count @notes = commit.notes.not_inline.fresh @noteable = @commit @comments_allowed = @reply_allowed = true @@ -23,8 +23,6 @@ class Projects::CommitController < Projects::ApplicationController commit_id: @commit.id } - @ci_commit = project.ci_commit(commit.sha) - respond_to do |format| format.html format.diff { render text: @commit.to_diff } @@ -32,20 +30,25 @@ class Projects::CommitController < Projects::ApplicationController end end - def ci - @ci_commit = @project.ci_commit(@commit.sha) - @builds = @ci_commit.builds if @ci_commit - @notes_count = @commit.notes.count + def builds @ci_project = @project.gitlab_ci_project end def cancel_builds - @ci_commit = @project.ci_commit(@commit.sha) - @ci_commit.builds.running_or_pending.each(&:cancel) + ci_commit.builds.running_or_pending.each(&:cancel) - redirect_to ci_namespace_project_commit_path(project.namespace, project, commit.sha) + redirect_to builds_namespace_project_commit_path(project.namespace, project, commit.sha) end + def retry_builds + ci_commit.builds.latest.failed.each do |build| + if build.retryable? + Ci::Build.retry(build) + end + end + + redirect_to builds_namespace_project_commit_path(project.namespace, project, commit.sha) + end def branches @branches = @project.repository.branch_names_contains(commit.id) @@ -53,11 +56,22 @@ class Projects::CommitController < Projects::ApplicationController render layout: false end + private + def commit @commit ||= @project.commit(params[:id]) end - private + def ci_commit + @ci_commit ||= project.ci_commit(commit.sha) + end + + def define_show_vars + @diffs = commit.diffs + @notes_count = commit.notes.count + + @builds = ci_commit.builds if ci_commit + end def authorize_manage_builds! unless can?(current_user, :manage_builds, project) diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb deleted file mode 100644 index 1b5a2c31d7..0000000000 --- a/app/helpers/builds_helper.rb +++ /dev/null @@ -1,13 +0,0 @@ -module BuildsHelper - def build_ref_link build - gitlab_ref_link build.project, build.ref - end - - def build_commit_link build - gitlab_commit_link build.project, build.short_sha - end - - def build_url(build) - namespace_project_build_path(build.gl_project, build.project, build) - end -end diff --git a/app/helpers/ci/gitlab_helper.rb b/app/helpers/ci/gitlab_helper.rb index baddbc806f..e34c8be1df 100644 --- a/app/helpers/ci/gitlab_helper.rb +++ b/app/helpers/ci/gitlab_helper.rb @@ -4,25 +4,6 @@ module Ci { :"data-no-turbolink" => "data-no-turbolink" } end - def gitlab_ref_link project, ref - gitlab_url = project.gitlab_url.dup - gitlab_url << "/commits/#{ref}" - link_to ref, gitlab_url, no_turbolink - end - - def gitlab_compare_link project, before, after - gitlab_url = project.gitlab_url.dup - gitlab_url << "/compare/#{before}...#{after}" - - link_to "#{before}...#{after}", gitlab_url, no_turbolink - end - - def gitlab_commit_link project, sha - gitlab_url = project.gitlab_url.dup - gitlab_url << "/commit/#{sha}" - link_to Ci::Commit.truncate_sha(sha), gitlab_url, no_turbolink - end - def yaml_web_editor_link(project) commits = project.commits diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index ed88df5dd8..0ecf77bb45 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -1,7 +1,7 @@ module CiStatusHelper def ci_status_path(ci_commit) project = ci_commit.gl_project - ci_namespace_project_commit_path(project.namespace, project, ci_commit.sha) + builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha) end def ci_status_icon(ci_commit) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index b19e2ac136..7f185ae7cc 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -106,6 +106,14 @@ module Ci failed? && allow_failure? end + def retryable? + commands.present? + end + + def retried? + !self.commit.latest_builds_for_ref(self.ref).include?(self) + end + def trace_html html = Ci::Ansi2html::convert(trace) if trace.present? html || '' @@ -222,7 +230,7 @@ module Ci end def retry_url - if commands.present? + if retryable? Gitlab::Application.routes.url_helpers. retry_namespace_project_build_path(gl_project.namespace, gl_project, self) end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 0b73ab6d2e..7d54d83974 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -15,8 +15,8 @@ class CommitStatus < ActiveRecord::Base scope :pending, -> { where(status: 'pending') } scope :success, -> { where(status: 'success') } scope :failed, -> { where(status: 'failed') } - scope :running_or_pending, -> { where(status:[:running, :pending]) } - scope :finished, -> { where(status:[:success, :failed, :canceled]) } + scope :running_or_pending, -> { where(status: [:running, :pending]) } + scope :finished, -> { where(status: [:success, :failed, :canceled]) } scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :ref)) } scope :ordered, -> { order(:ref, :stage_idx, :name) } scope :for_ref, ->(ref) { where(ref: ref) } diff --git a/app/models/project_services/ci/hip_chat_message.rb b/app/models/project_services/ci/hip_chat_message.rb index cbf325cc52..d89466b689 100644 --- a/app/models/project_services/ci/hip_chat_message.rb +++ b/app/models/project_services/ci/hip_chat_message.rb @@ -11,7 +11,7 @@ module Ci def to_s lines = Array.new lines.push("#{project.name} - ") - lines.push("Commit ##{commit.id}
") + lines.push("Commit ##{commit.id}
") lines.push("#{commit.short_sha} #{commit.git_author_name} - #{commit.git_commit_message}
") lines.push("#{humanized_status(commit_status)} in #{commit.duration} second(s).") lines.join('') diff --git a/app/models/project_services/ci/slack_message.rb b/app/models/project_services/ci/slack_message.rb index dc050a3fc5..1a6ff8e34c 100644 --- a/app/models/project_services/ci/slack_message.rb +++ b/app/models/project_services/ci/slack_message.rb @@ -45,7 +45,7 @@ module Ci def attachment_message out = "<#{ci_project_url(project)}|#{project_name}>: " - out << "Commit <#{ci_namespace_project_commit_url(commit.gl_project.namespace, commit.gl_project, commit.sha)}|\##{commit.id}> " + out << "Commit <#{builds_namespace_project_commit_url(commit.gl_project.namespace, commit.gl_project, commit.sha)}|\##{commit.id}> " out << "(<#{commit_sha_link}|#{commit.short_sha}>) " out << "of <#{commit_ref_link}|#{commit.ref}> " out << "by #{commit.git_author_name} " if commit.git_author_name diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb index 4dcd16ede3..095d04e0df 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -71,7 +71,7 @@ class GitlabCiService < CiService def build_page(sha, ref) if project.gitlab_ci_project.present? - ci_namespace_project_commit_url(project.namespace, project, sha) + builds_namespace_project_commit_url(project.namespace, project, sha) end end diff --git a/app/views/ci/notify/build_fail_email.html.haml b/app/views/ci/notify/build_fail_email.html.haml index 69689a7502..cefb75040e 100644 --- a/app/views/ci/notify/build_fail_email.html.haml +++ b/app/views/ci/notify/build_fail_email.html.haml @@ -7,7 +7,7 @@ = @project.name %p - Commit link: #{gitlab_commit_link(@project, @build.commit.short_sha)} + Commit: #{link_to @build.short_sha, namespace_project_commit_path(@build.gl_project.namespace, @build.gl_project, @build.sha)} %p Author: #{@build.commit.git_author_name} %p @@ -16,4 +16,4 @@ Message: #{@build.commit.git_commit_message} %p - Url: #{link_to @build.short_sha, namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build)} + Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build)} diff --git a/app/views/ci/notify/build_success_email.html.haml b/app/views/ci/notify/build_success_email.html.haml index 4e3015a356..617b88f734 100644 --- a/app/views/ci/notify/build_success_email.html.haml +++ b/app/views/ci/notify/build_success_email.html.haml @@ -8,7 +8,7 @@ = @project.name %p - Commit link: #{gitlab_commit_link(@project, @build.commit.short_sha)} + Commit: #{link_to @build.short_sha, namespace_project_commit_path(@build.gl_project.namespace, @build.gl_project, @build.sha)} %p Author: #{@build.commit.git_author_name} %p @@ -17,4 +17,4 @@ Message: #{@build.commit.git_commit_message} %p - Url: #{link_to @build.short_sha, namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build)} + Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build)} diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml index c249f5cace..f3f3f58111 100644 --- a/app/views/dashboard/groups/index.html.haml +++ b/app/views/dashboard/groups/index.html.haml @@ -16,4 +16,4 @@ - group = group_member.group = render 'shared/groups/group', group: group, group_member: group_member -= paginate @group_members += paginate @group_members, theme: 'gitlab' diff --git a/app/views/layouts/ci/_nav_admin.html.haml b/app/views/layouts/ci/_nav_admin.html.haml index af2545a22d..dcda04a463 100644 --- a/app/views/layouts/ci/_nav_admin.html.haml +++ b/app/views/layouts/ci/_nav_admin.html.haml @@ -9,23 +9,25 @@ = nav_link path: 'projects#index' do = link_to ci_admin_projects_path do = icon('list-alt fw') - Projects + %span + Projects = nav_link path: 'events#index' do = link_to ci_admin_events_path do = icon('book fw') - Events + %span + Events = nav_link path: ['runners#index', 'runners#show'] do = link_to ci_admin_runners_path do = icon('cog fw') - Runners - %small.pull-right - = Ci::Runner.count(:all) + %span + Runners + %span.count= Ci::Runner.count(:all) = nav_link path: 'builds#index' do = link_to ci_admin_builds_path do = icon('link fw') - Builds - %small.pull-right - = Ci::Build.count(:all) + %span + Builds + %span.count= Ci::Build.count(:all) = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do = link_to ci_admin_application_settings_path do = icon('cogs fw') diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml index d7b20bfc6b..7e1ee2b7fc 100644 --- a/app/views/projects/_last_commit.html.haml +++ b/app/views/projects/_last_commit.html.haml @@ -6,7 +6,7 @@ = ci_commit.status = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" - = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" + = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message" · #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} by = commit_author_link(commit, avatar: true, size: 24) diff --git a/app/views/projects/builds/_build.html.haml b/app/views/projects/builds/_build.html.haml deleted file mode 100644 index 4ce4ed63b4..0000000000 --- a/app/views/projects/builds/_build.html.haml +++ /dev/null @@ -1,53 +0,0 @@ -%tr.build - %td.status - = ci_status_with_icon(build.status) - - %td.commit_status-link - - if build.target_url - = link_to build.target_url do - %strong Build ##{build.id} - - else - %strong Build ##{build.id} - - - if build.show_warning? - %i.fa.fa-warning.text-warning - - %td - = link_to build.short_sha, namespace_project_commit_path(@project.namespace, @project, build.sha) - - %td - = link_to build.ref, namespace_project_commits_path(@project.namespace, @project, build.ref) - - %td - - if build.runner - = runner_link(build.runner) - - else - .light none - - %td - = build.name - - .pull-right - - if build.tags.any? - - build.tags.each do |tag| - %span.label.label-primary - = tag - - if build.trigger_request - %span.label.label-info triggered - - if build.allow_failure - %span.label.label-danger allowed to fail - - %td.duration - - if build.duration - #{duration_in_words(build.finished_at, build.started_at)} - - %td.timestamp - - if build.finished_at - %span #{time_ago_in_words build.finished_at} ago - - %td - .pull-right - - if current_user && can?(current_user, :manage_builds, @project) - - if build.cancel_url - = link_to build.cancel_url, title: 'Cancel' do - %i.fa.fa-remove.cred diff --git a/app/views/projects/builds/_header_title.html.haml b/app/views/projects/builds/_header_title.html.haml new file mode 100644 index 0000000000..082dab1f5b --- /dev/null +++ b/app/views/projects/builds/_header_title.html.haml @@ -0,0 +1 @@ +- header_title project_title(@project, "Builds", project_builds_path(@project)) diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index e08556673e..dab7164153 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -1,12 +1,12 @@ - page_title "Builds" -- header_title project_title(@project, "Builds", project_builds_path(@project)) += render "header_title" .project-issuable-filter .controls - if @ci_project && current_user && can?(current_user, :manage_builds, @project) .pull-left.hidden-xs - if @all_builds.running_or_pending.any? - = link_to 'Cancel all', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger' + = link_to 'Cancel all', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post %ul.center-top-menu %li{class: ('active' if @scope.nil?)} @@ -25,7 +25,7 @@ %span.badge.js-totalbuilds-count= @all_builds.count(:id) .gray-content-block - List of #{@scope || 'running'} builds from this project + #{(@scope || 'running').capitalize} builds from this project %ul.content-list - if @builds.blank? @@ -40,14 +40,14 @@ %th Build ID %th Commit %th Ref - %th Runner + %th Stage %th Name %th Duration %th Finished at %th - @builds.each do |build| - = render 'projects/builds/build', build: build + = render 'projects/commit_statuses/commit_status', commit_status: build, commit_sha: true, stage: true, allow_retry: true - = paginate @builds + = paginate @builds, theme: 'gitlab' diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index e3d8d73491..3374d5432a 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -1,10 +1,13 @@ +- page_title "#{@build.name} (#{@build.id})", "Builds" += render "header_title" + .build-page .gray-content-block - Build for commit + Build ##{@build.id} for commit %strong.monospace = link_to @build.commit.short_sha, ci_status_path(@build.commit) from - %code #{@build.ref} + = link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref) #up-build-trace - if @commit.matrix_for_ref?(@build.ref) @@ -20,7 +23,7 @@ = build.id - - unless @commit.latest_builds_for_ref(@build.ref).include?(@build) + - if @build.retried? %li.active %a Build ##{@build.id} @@ -37,7 +40,7 @@ %i.fa.fa-time #{duration_in_words(@build.finished_at, @build.started_at)} .pull-right - = @build.updated_at.stamp('19:00 Aug 27') + #{time_ago_with_tooltip(@build.finished_at) if @build.finished_at} - if @build.show_warning? - unless @build.any_runners_online? @@ -87,13 +90,13 @@ .build-widget %h4.title - Build + Build ##{@build.id} - if current_user && can?(current_user, :manage_builds, @project) .pull-right - - if @build.active? - = link_to "Cancel", cancel_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-danger' - - elsif @build.commands.present? - = link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-primary', method: :post + - if @build.cancel_url + = link_to "Cancel", @build.cancel_url, class: 'btn btn-sm btn-danger', method: :post + - elsif @build.retry_url + = link_to "Retry", @build.retry_url, class: 'btn btn-sm btn-primary', method: :post - if @build.duration %p @@ -101,15 +104,15 @@ #{duration_in_words(@build.finished_at, @build.started_at)} %p %span.attr-name Created: - #{time_ago_in_words(@build.created_at)} ago + #{time_ago_with_tooltip(@build.created_at)} - if @build.finished_at %p %span.attr-name Finished: - #{time_ago_in_words(@build.finished_at)} ago + #{time_ago_with_tooltip(@build.finished_at)} %p %span.attr-name Runner: - if @build.runner && current_user && current_user.admin - \#{link_to "##{@build.runner.id}", ci_admin_runner_path(@build.runner.id)} + = link_to "##{@build.runner.id}", ci_admin_runner_path(@build.runner.id) - elsif @build.runner \##{@build.runner.id} @@ -134,10 +137,11 @@ %h4.title Commit .pull-right - %small #{build_commit_link @build} + %small + = link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace" %p %span.attr-name Branch: - #{build_ref_link @build} + = link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref) %p %span.attr-name Author: #{@build.commit.git_author_name} @@ -155,7 +159,9 @@ - if @builds.present? .build-widget - %h4.title #{pluralize(@builds.count(:id), "other build")} for #{@build.short_sha}: + %h4.title #{pluralize(@builds.count(:id), "other build")} for + = succeed ":" do + = link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace" %table.table.builds - @builds.each_with_index do |build, i| %tr.build @@ -171,8 +177,5 @@ %td.status= build.status - = paginate @builds - - :javascript new CiBuild("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{@build.status}") diff --git a/app/views/projects/ci_services/edit.html.haml b/app/views/projects/ci_services/edit.html.haml index bcc5832792..dacb6b4f6f 100644 --- a/app/views/projects/ci_services/edit.html.haml +++ b/app/views/projects/ci_services/edit.html.haml @@ -1 +1,2 @@ +- page_title @service.title, "CI Services" = render 'form' diff --git a/app/views/projects/ci_services/index.html.haml b/app/views/projects/ci_services/index.html.haml index c164b2d4bc..3f26c7851d 100644 --- a/app/views/projects/ci_services/index.html.haml +++ b/app/views/projects/ci_services/index.html.haml @@ -1,3 +1,4 @@ +- page_title "CI Services" %h3.page-title Project services %p.light Project services allow you to integrate GitLab CI with other applications diff --git a/app/views/projects/ci_settings/edit.html.haml b/app/views/projects/ci_settings/edit.html.haml index eedf484bf0..665556f5c2 100644 --- a/app/views/projects/ci_settings/edit.html.haml +++ b/app/views/projects/ci_settings/edit.html.haml @@ -1,3 +1,4 @@ +- page_title "CI Settings" - if @ci_project.generated_yaml_config %p.alert.alert-danger CI Jobs are deprecated now, you can #{link_to "download", dumped_yaml_ci_project_path(@ci_project)} diff --git a/app/views/projects/ci_web_hooks/index.html.haml b/app/views/projects/ci_web_hooks/index.html.haml index 369086b39e..2998fb08ff 100644 --- a/app/views/projects/ci_web_hooks/index.html.haml +++ b/app/views/projects/ci_web_hooks/index.html.haml @@ -1,3 +1,4 @@ +- page_title "CI Web Hooks" %h3.page-title CI Web hooks diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml index a634ae5dfd..c73ba74f5e 100644 --- a/app/views/projects/commit/_ci_menu.html.haml +++ b/app/views/projects/commit/_ci_menu.html.haml @@ -2,6 +2,8 @@ = nav_link(path: 'commit#show') do = link_to namespace_project_commit_path(@project.namespace, @project, @commit.id) do Changes - = nav_link(path: 'commit#ci') do - = link_to ci_namespace_project_commit_path(@project.namespace, @project, @commit.id) do + %span.badge= @diffs.count + = nav_link(path: 'commit#builds') do + = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id) do Builds + %span.badge= @builds.count(:id) diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index fbf0a9ec0c..a6458b8486 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -19,7 +19,7 @@ %p %span.light Commit - = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit) + = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" .commit-info-row %span.light Authored by %strong @@ -36,7 +36,7 @@ .commit-info-row %span.cgray= pluralize(@commit.parents.count, "parent") - @commit.parents.each do |parent| - = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent) + = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "monospace" - if @ci_commit .pull-right diff --git a/app/views/projects/commit/ci.html.haml b/app/views/projects/commit/builds.html.haml similarity index 77% rename from app/views/projects/commit/ci.html.haml rename to app/views/projects/commit/builds.html.haml index 43033cad24..f7a89232e0 100644 --- a/app/views/projects/commit/ci.html.haml +++ b/app/views/projects/commit/builds.html.haml @@ -1,4 +1,4 @@ -- page_title "#{@commit.title} (#{@commit.short_id})", "Commits" +- page_title "Builds", "#{@commit.title} (#{@commit.short_id})", "Commits" = render "projects/commits/header_title" = render "commit_box" = render "ci_menu" @@ -26,8 +26,11 @@   - if @ci_project && current_user && can?(current_user, :manage_builds, @project) + - if @ci_commit.builds.latest.failed.any?(&:retryable?) + = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, @commit.sha), class: 'btn btn-xs btn-primary', method: :post + - if @ci_commit.builds.running_or_pending.any? - = link_to "Cancel all", cancel_builds_namespace_project_commit_path(@project.namespace, @project, @commit.sha), class: 'btn btn-xs btn-danger' + = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@project.namespace, @project, @commit.sha), class: 'btn btn-xs btn-danger', method: :post .table-holder %table.table.builds @@ -45,7 +48,7 @@ %th - @ci_commit.refs.each do |ref| = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.statuses.for_ref(ref).latest.ordered, - locals: { coverage: @ci_project.try(:coverage_enabled?), allow_retry: true } + locals: { coverage: @ci_project.try(:coverage_enabled?), stage: true, allow_retry: true } - if @ci_commit.retried.any? .gray-content-block.second-block diff --git a/app/views/projects/commit_statuses/_commit_status.html.haml b/app/views/projects/commit_statuses/_commit_status.html.haml index 637154f56a..c255559b88 100644 --- a/app/views/projects/commit_statuses/_commit_status.html.haml +++ b/app/views/projects/commit_statuses/_commit_status.html.haml @@ -12,14 +12,30 @@ - if commit_status.show_warning? %i.fa.fa-warning.text-warning - %td - = commit_status.ref + - if defined?(commit_sha) && commit_sha + %td + = link_to commit_status.short_sha, namespace_project_commit_path(@project.namespace, @project, commit_status.sha), class: "monospace" %td - = commit_status.stage + - if commit_status.ref + = link_to commit_status.ref, namespace_project_commits_path(@project.namespace, @project, commit_status.ref) + - else + .light none + + - if defined?(runner) && runner + %td + - if commit_status.try(:runner) + = runner_link(commit_status.runner) + - else + .light none + + - if defined?(stage) && stage + %td + = commit_status.stage %td = commit_status.name + .pull-right - if commit_status.tags.any? - commit_status.tags.each do |tag| @@ -36,7 +52,7 @@ %td.timestamp - if commit_status.finished_at - %span #{time_ago_in_words commit_status.finished_at} ago + %span #{time_ago_with_tooltip(commit_status.finished_at)} - if defined?(coverage) && coverage %td.coverage @@ -46,9 +62,10 @@ %td .pull-right - if current_user && can?(current_user, :manage_builds, commit_status.gl_project) - - if commit_status.cancel_url - = link_to commit_status.cancel_url, title: 'Cancel' do - %i.fa.fa-remove.cred + - if commit_status.active? + - if commit_status.cancel_url + = link_to commit_status.cancel_url, method: :post, title: 'Cancel' do + %i.fa.fa-remove.cred - elsif defined?(allow_retry) && allow_retry && commit_status.retry_url = link_to commit_status.retry_url, method: :post, title: 'Retry' do %i.fa.fa-repeat diff --git a/app/views/projects/runners/edit.html.haml b/app/views/projects/runners/edit.html.haml index 66851d3831..dde9e448cb 100644 --- a/app/views/projects/runners/edit.html.haml +++ b/app/views/projects/runners/edit.html.haml @@ -1,3 +1,5 @@ +- page_title "Edit", "#{@runner.description} ##{@runner.id}", "Runners" + %h4 Runner ##{@runner.id} %hr = form_for @runner, url: runner_path(@runner), html: { class: 'form-horizontal' } do |f| diff --git a/app/views/projects/runners/index.html.haml b/app/views/projects/runners/index.html.haml index 529fb9c296..315afe4a76 100644 --- a/app/views/projects/runners/index.html.haml +++ b/app/views/projects/runners/index.html.haml @@ -1,3 +1,4 @@ +- page_title "Runners" .light %p A 'runner' is a process which runs a build. diff --git a/app/views/projects/runners/show.html.haml b/app/views/projects/runners/show.html.haml index c255cd51bd..5bf4c09ca2 100644 --- a/app/views/projects/runners/show.html.haml +++ b/app/views/projects/runners/show.html.haml @@ -1,13 +1,14 @@ -= content_for :title do - %h3.project-title - Runner ##{@runner.id} - .pull-right - - if @runner.shared? - %span.runner-state.runner-state-shared - Shared - - else - %span.runner-state.runner-state-specific - Specific +- page_title "#{@runner.description} ##{@runner.id}", "Runners" + +%h3.page-title + Runner ##{@runner.id} + .pull-right + - if @runner.shared? + %span.runner-state.runner-state-shared + Shared + - else + %span.runner-state.runner-state-specific + Specific .table-holder %table.table diff --git a/app/views/projects/triggers/index.html.haml b/app/views/projects/triggers/index.html.haml index 18a37302c3..b3ad79a200 100644 --- a/app/views/projects/triggers/index.html.haml +++ b/app/views/projects/triggers/index.html.haml @@ -1,3 +1,4 @@ +- page_title "Triggers" %h3.page-title Triggers diff --git a/app/views/projects/variables/show.html.haml b/app/views/projects/variables/show.html.haml index 29416a94ff..e052da1ac4 100644 --- a/app/views/projects/variables/show.html.haml +++ b/app/views/projects/variables/show.html.haml @@ -1,3 +1,4 @@ +- page_title "Variables" %h3.page-title Secret Variables diff --git a/config/routes.rb b/config/routes.rb index 0458f538eb..990a00e3d0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -472,8 +472,9 @@ Gitlab::Application.routes.draw do resources :commit, only: [:show], constraints: { id: /[[:alnum:]]{6,40}/ } do member do get :branches - get :ci - get :cancel_builds + get :builds + post :cancel_builds + post :retry_builds end end @@ -588,12 +589,12 @@ Gitlab::Application.routes.draw do resources :builds, only: [:index, :show] do collection do - get :cancel_all + post :cancel_all end member do - get :cancel get :status + post :cancel post :retry end end diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb index 154857e77f..158e85e598 100644 --- a/spec/features/builds_spec.rb +++ b/spec/features/builds_spec.rb @@ -47,10 +47,11 @@ describe "Builds" do end end - describe "GET /:project/builds/:id/cancel_all" do + describe "POST /:project/builds/:id/cancel_all" do before do @build.run! - visit cancel_all_namespace_project_builds_path(@gl_project.namespace, @gl_project) + visit namespace_project_builds_path(@gl_project.namespace, @gl_project) + click_link "Cancel all" end it { expect(page).to have_content 'No builds to show' } @@ -67,10 +68,11 @@ describe "Builds" do it { expect(page).to have_content @commit.git_author_name } end - describe "GET /:project/builds/:id/cancel" do + describe "POST /:project/builds/:id/cancel" do before do @build.run! - visit cancel_namespace_project_build_path(@gl_project.namespace, @gl_project, @build) + visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build) + click_link "Cancel" end it { expect(page).to have_content 'canceled' } @@ -79,7 +81,9 @@ describe "Builds" do describe "POST /:project/builds/:id/retry" do before do - visit cancel_namespace_project_build_path(@gl_project.namespace, @gl_project, @build) + @build.run! + visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build) + click_link "Cancel" click_link 'Retry' end diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index 1adc2cdf70..340924fafe 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -32,7 +32,7 @@ describe "Commits" do describe "Cancel all builds" do it "cancels commit" do visit ci_status_path(@commit) - click_on "Cancel all" + click_on "Cancel running" expect(page).to have_content "canceled" end end diff --git a/spec/models/project_services/gitlab_ci_service_spec.rb b/spec/models/project_services/gitlab_ci_service_spec.rb index 842089ebe0..b9006b693b 100644 --- a/spec/models/project_services/gitlab_ci_service_spec.rb +++ b/spec/models/project_services/gitlab_ci_service_spec.rb @@ -39,7 +39,7 @@ describe GitlabCiService do end describe :build_page do - it { expect(@service.build_page("2ab7834c", 'master')).to eq("http://localhost/#{@ci_project.gl_project.path_with_namespace}/commit/2ab7834c/ci")} + it { expect(@service.build_page("2ab7834c", 'master')).to eq("http://localhost/#{@ci_project.gl_project.path_with_namespace}/commit/2ab7834c/builds")} end describe "execute" do From 2becd53015c5b283d8c5bfc6b8702b22416e55d2 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 3 Nov 2015 14:24:00 +0100 Subject: [PATCH 022/140] Add missing stage to builds view --- app/views/projects/commit/builds.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/commit/builds.html.haml b/app/views/projects/commit/builds.html.haml index f7a89232e0..00cf9c7610 100644 --- a/app/views/projects/commit/builds.html.haml +++ b/app/views/projects/commit/builds.html.haml @@ -69,4 +69,4 @@ %th Coverage %th = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.retried, - locals: { coverage: @ci_project.try(:coverage_enabled?) } + locals: { coverage: @ci_project.try(:coverage_enabled?), stage: true } From aecc4376e20b3ad23662f6181a776ac5747baa06 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 5 Nov 2015 16:41:03 +0200 Subject: [PATCH 023/140] make migrations reversible --- db/migrate/20151019111551_fix_build_tags.rb | 6 +++++- db/migrate/20151019111703_fail_build_without_names.rb | 5 ++++- db/migrate/20151023112551_fail_build_with_empty_name.rb | 5 ++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/db/migrate/20151019111551_fix_build_tags.rb b/db/migrate/20151019111551_fix_build_tags.rb index 84b142183f..299a24b0a7 100644 --- a/db/migrate/20151019111551_fix_build_tags.rb +++ b/db/migrate/20151019111551_fix_build_tags.rb @@ -1,5 +1,9 @@ class FixBuildTags < ActiveRecord::Migration - def change + def up execute("UPDATE taggings SET taggable_type='CommitStatus' WHERE taggable_type='Ci::Build'") end + + def down + execute("UPDATE taggings SET taggable_type='Ci::Build' WHERE taggable_type='CommitStatus'") + end end diff --git a/db/migrate/20151019111703_fail_build_without_names.rb b/db/migrate/20151019111703_fail_build_without_names.rb index 546b03d812..dcdb5d1b25 100644 --- a/db/migrate/20151019111703_fail_build_without_names.rb +++ b/db/migrate/20151019111703_fail_build_without_names.rb @@ -1,5 +1,8 @@ class FailBuildWithoutNames < ActiveRecord::Migration - def change + def up execute("UPDATE ci_builds SET status='failed' WHERE name IS NULL AND status='pending'") end + + def down + end end diff --git a/db/migrate/20151023112551_fail_build_with_empty_name.rb b/db/migrate/20151023112551_fail_build_with_empty_name.rb index f069bc60ac..41c0f0649c 100644 --- a/db/migrate/20151023112551_fail_build_with_empty_name.rb +++ b/db/migrate/20151023112551_fail_build_with_empty_name.rb @@ -1,5 +1,8 @@ class FailBuildWithEmptyName < ActiveRecord::Migration - def change + def up execute("UPDATE ci_builds SET status='failed' WHERE (name IS NULL OR name='') AND status='pending'") end + + def down + end end From 6fcd5127a316ce3abf1e5c5dde5b1901e8c7941f Mon Sep 17 00:00:00 2001 From: Thirumal S Date: Thu, 5 Nov 2015 20:21:46 +0530 Subject: [PATCH 024/140] defined class prepend-top-10 used for alignment issue --- app/assets/stylesheets/pages/projects.scss | 4 ---- app/views/projects/hooks/index.html.haml | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 2e7ad1173a..d3b1004002 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -552,8 +552,4 @@ pre.light-well { z-index: 100; position: relative; } -} - -.form-control-padding-top { - padding-top: 10px; } \ No newline at end of file diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml index 65e00b38ad..3702aeaecb 100644 --- a/app/views/projects/hooks/index.html.haml +++ b/app/views/projects/hooks/index.html.haml @@ -19,7 +19,7 @@ = f.text_field :url, class: "form-control", placeholder: 'http://example.com/trigger-ci.json' .form-group = f.label :url, "Trigger", class: 'control-label' - .col-sm-10.form-control-padding-top + .col-sm-10.prepend-top-10 %div = f.check_box :push_events, class: 'pull-left' .prepend-left-20 From b18671a1b2c565a87663544441000063f6b83c8e Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 3 Nov 2015 14:45:41 +0100 Subject: [PATCH 025/140] Enable shared runners for all new projects --- CHANGELOG | 1 + app/controllers/admin/application_settings_controller.rb | 1 + app/models/application_setting.rb | 3 ++- app/models/project.rb | 5 ++++- app/views/admin/application_settings/_form.html.haml | 9 +++++++++ config/initializers/1_settings.rb | 9 +++++---- db/migrate/20151103133339_add_shared_runners_setting.rb | 5 +++++ db/schema.rb | 3 ++- lib/gitlab/current_settings.rb | 3 ++- 9 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 db/migrate/20151103133339_add_shared_runners_setting.rb diff --git a/CHANGELOG b/CHANGELOG index 3a75f50e9a..2201221116 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.2.0 (unreleased) - Use git follow flag for commits page when retrieve history for file or directory - Show merge request CI status on merge requests index page - Extend yml syntax for only and except to support specifying repository path + - Enable shared runners to all new projects - Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu) - Remove deprecated CI events from project settings page - Use issue editor as cross reference comment author when issue is edited with a new mention. diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 039f18f23e..3d9c59050f 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -57,6 +57,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :version_check_enabled, :admin_notification_email, :user_oauth_applications, + :shared_runners_enabled, restricted_visibility_levels: [], import_sources: [] ) diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 05430c2ee1..266045f7af 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -87,7 +87,8 @@ class ApplicationSetting < ActiveRecord::Base default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], restricted_signup_domains: Settings.gitlab['restricted_signup_domains'], - import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'] + import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'], + shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], ) end diff --git a/app/models/project.rb b/app/models/project.rb index 74b89aad49..57db7f9f0a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -37,6 +37,7 @@ class Project < ActiveRecord::Base include Gitlab::ConfigHelper include Gitlab::ShellAdapter include Gitlab::VisibilityLevel + include Gitlab::CurrentSettings include Referable include Sortable include AfterCommitQueue @@ -775,7 +776,9 @@ class Project < ActiveRecord::Base end def ensure_gitlab_ci_project - gitlab_ci_project || create_gitlab_ci_project + gitlab_ci_project || create_gitlab_ci_project( + shared_runners_enabled: current_application_settings.shared_runners_enabled + ) end def enable_ci diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 7a78526e09..7253218c2e 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -130,5 +130,14 @@ = f.text_area :help_page_text, class: 'form-control', rows: 4 .help-block Markdown enabled + %fieldset + %legend Continuous Integration + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :shared_runners_enabled do + = f.check_box :shared_runners_enabled + Enable shared runners for a new projects + .form-actions = f.submit 'Save', class: 'btn btn-primary' diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index c189c88bdc..64d73d7232 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -181,10 +181,11 @@ Settings.gitlab['import_sources'] ||= ['github','bitbucket','gitlab','gitorious' # CI # Settings['gitlab_ci'] ||= Settingslogic.new({}) -Settings.gitlab_ci['all_broken_builds'] = true if Settings.gitlab_ci['all_broken_builds'].nil? -Settings.gitlab_ci['add_pusher'] = false if Settings.gitlab_ci['add_pusher'].nil? -Settings.gitlab_ci['url'] ||= Settings.send(:build_gitlab_ci_url) -Settings.gitlab_ci['builds_path'] = File.expand_path(Settings.gitlab_ci['builds_path'] || "builds/", Rails.root) +Settings.gitlab_ci['shared_runners_enabled'] = true if Settings.gitlab_ci['shared_runners_enabled'].nil? +Settings.gitlab_ci['all_broken_builds'] = true if Settings.gitlab_ci['all_broken_builds'].nil? +Settings.gitlab_ci['add_pusher'] = false if Settings.gitlab_ci['add_pusher'].nil? +Settings.gitlab_ci['url'] ||= Settings.send(:build_gitlab_ci_url) +Settings.gitlab_ci['builds_path'] = File.expand_path(Settings.gitlab_ci['builds_path'] || "builds/", Rails.root) # # Reply by email diff --git a/db/migrate/20151103133339_add_shared_runners_setting.rb b/db/migrate/20151103133339_add_shared_runners_setting.rb new file mode 100644 index 0000000000..4231dfd5c2 --- /dev/null +++ b/db/migrate/20151103133339_add_shared_runners_setting.rb @@ -0,0 +1,5 @@ +class AddSharedRunnersSetting < ActiveRecord::Migration + def up + add_column :application_settings, :shared_runners_enabled, :boolean, default: true, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 17d445a8ba..de896f2764 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20151103001141) do +ActiveRecord::Schema.define(version: 20151103133339) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -47,6 +47,7 @@ ActiveRecord::Schema.define(version: 20151103001141) do t.text "import_sources" t.text "help_page_text" t.string "admin_notification_email" + t.boolean "shared_runners_enabled", default: true, null: false end create_table "audit_events", force: true do |t| diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 0ea1b6a2f6..cd84afa31d 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -23,7 +23,8 @@ module Gitlab restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'], max_attachment_size: Settings.gitlab['max_attachment_size'], session_expire_delay: Settings.gitlab['session_expire_delay'], - import_sources: Settings.gitlab['import_sources'] + import_sources: Settings.gitlab['import_sources'], + shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], ) end From 900419c43c5a540cde22f5488675121b3ce05d31 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 5 Nov 2015 17:08:47 +0100 Subject: [PATCH 026/140] Improve UI for tags page Signed-off-by: Dmitriy Zaporozhets --- app/controllers/projects/tags_controller.rb | 10 +++------- app/views/projects/tags/_download.html.haml | 17 ++++++++++++++++ app/views/projects/tags/_tag.html.haml | 8 +++----- app/views/projects/tags/destroy.js.haml | 3 --- app/views/projects/tags/show.html.haml | 22 ++++++++++----------- 5 files changed, 33 insertions(+), 27 deletions(-) create mode 100644 app/views/projects/tags/_download.html.haml delete mode 100644 app/views/projects/tags/destroy.js.haml diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 055f328677..670f5d3067 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -39,13 +39,9 @@ class Projects::TagsController < Projects::ApplicationController def destroy DeleteTagService.new(project, current_user).execute(params[:id]) + release = project.releases.find_by(tag: params[:id]) + release.destroy if release - respond_to do |format| - format.html do - redirect_to namespace_project_tags_path(@project.namespace, - @project) - end - format.js - end + redirect_to namespace_project_tags_path(@project.namespace, @project) end end diff --git a/app/views/projects/tags/_download.html.haml b/app/views/projects/tags/_download.html.haml new file mode 100644 index 0000000000..667057ef2d --- /dev/null +++ b/app/views/projects/tags/_download.html.haml @@ -0,0 +1,17 @@ +%span.btn-group.btn-grouped + = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), class: 'btn btn-default', rel: 'nofollow' do + %i.fa.fa-download + %span source code + %a.btn.btn-default.dropdown-toggle{ 'data-toggle' => 'dropdown' } + %span.caret + %span.sr-only + Select Archive Format + %ul.col-xs-10.dropdown-menu{ role: 'menu' } + %li + = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do + %i.fa.fa-download + %span Download zip + %li + = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do + %i.fa.fa-download + %span Download tar.gz diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index 5d203903f8..f3dc314a89 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -4,22 +4,20 @@ %div = link_to namespace_project_tag_path(@project.namespace, @project, tag.name) do %strong - %i.fa.fa-tag + = icon('tag') = tag.name - if tag.message.present?   = strip_gpg_signature(tag.message) - if release +   %span.label.label-success release .controls = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn-grouped btn' do = icon("pencil") - if can? current_user, :download_code, @project - = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-xs' - - if can?(current_user, :admin_project, @project) - = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn btn-xs btn-remove remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'}, remote: true do - %i.fa.fa-trash-o + = render 'projects/tags/download', ref: tag.name, project: @project - if commit = render 'projects/branches/commit', commit: commit, project: @project diff --git a/app/views/projects/tags/destroy.js.haml b/app/views/projects/tags/destroy.js.haml deleted file mode 100644 index ada6710f94..0000000000 --- a/app/views/projects/tags/destroy.js.haml +++ /dev/null @@ -1,3 +0,0 @@ -$('.js-totaltags-count').html("#{@repository.tags.size}") -- if @repository.tags.size == 0 - $('.tags').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000) diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml index d2f23b96a8..22697464fd 100644 --- a/app/views/projects/tags/show.html.haml +++ b/app/views/projects/tags/show.html.haml @@ -6,6 +6,16 @@ .pull-right = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn' do = icon("pencil") + = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped' do + = icon('files-o') + = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped' do + = icon('history') + - if can? current_user, :download_code, @project + = render 'projects/tags/download', ref: @tag.name, project: @project + - if can?(current_user, :admin_project, @project) + .pull-right + = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'} do + %i.fa.fa-trash-o - if @tag.message.present? .title %strong= @tag.name @@ -24,18 +34,6 @@ - else This tag has no release notes yet. Press edit button to add one -.gray-content-block.middle-block.clearfix - = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped' do - Browse code - = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped' do - Commits - - if can? current_user, :download_code, @project - = render 'projects/repositories/download_archive', ref: @tag.name, btn_class: 'btn-grouped' - - if can?(current_user, :admin_project, @project) - .pull-right - = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'}, remote: true do - %i.fa.fa-trash-o - .gray-content-block.second-block - if @commit = render 'projects/commits/commit', commit: @commit, project: @project From 3c0244d3d1533dab3276465a63a26e0937aef543 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 5 Nov 2015 18:37:31 +0100 Subject: [PATCH 027/140] Retyle tag show page Signed-off-by: Dmitriy Zaporozhets --- app/views/projects/tags/show.html.haml | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml index 22697464fd..f80ba5516a 100644 --- a/app/views/projects/tags/show.html.haml +++ b/app/views/projects/tags/show.html.haml @@ -16,14 +16,17 @@ .pull-right = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'} do %i.fa.fa-trash-o - - if @tag.message.present? - .title - %strong= @tag.name - = strip_gpg_signature(@tag.message) + .title + %strong= @tag.name + - if @tag.message.present? + %span.light +   + = strip_gpg_signature(@tag.message) + - if @commit + = render 'projects/branches/commit', commit: @commit, project: @project - else - .oneline - .title - %strong= @tag.name + Cant find HEAD commit for this tag + .append-bottom-default.prepend-top-default - if @release.description.present? @@ -33,10 +36,3 @@ = markdown @release.description - else This tag has no release notes yet. Press edit button to add one - -.gray-content-block.second-block - - if @commit - = render 'projects/commits/commit', commit: @commit, project: @project - - else - Cant find HEAD commit for this tag - From c55b5c72e94344d138af7de198016c8ea54b80f8 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 5 Nov 2015 21:09:02 +0200 Subject: [PATCH 028/140] Remove duplicate documentation links --- doc/README.md | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/doc/README.md b/doc/README.md index a0ff856ebb..3cd98fab0d 100644 --- a/doc/README.md +++ b/doc/README.md @@ -17,20 +17,20 @@ ## CI Documentation -+ [Quick Start](ci/quick_start/README.md) -+ [Configuring project (.gitlab-ci.yml)](ci/yaml/README.md) -+ [Configuring runner](ci/runners/README.md) -+ [Configuring deployment](ci/deployment/README.md) -+ [Using Docker Images](ci/docker/using_docker_images.md) -+ [Using Docker Build](ci/docker/using_docker_build.md) -+ [Using Variables](ci/variables/README.md) +- [Quick Start](ci/quick_start/README.md) +- [Configuring project (.gitlab-ci.yml)](ci/yaml/README.md) +- [Configuring runner](ci/runners/README.md) +- [Configuring deployment](ci/deployment/README.md) +- [Using Docker Images](ci/docker/using_docker_images.md) +- [Using Docker Build](ci/docker/using_docker_build.md) +- [Using Variables](ci/variables/README.md) ### CI Examples -+ [Test and deploy Ruby applications to Heroku](ci/examples/test-and-deploy-ruby-application-to-heroku.md) -+ [Test and deploy Python applications to Heroku](ci/examples/test-and-deploy-python-application-to-heroku.md) -+ [Test Clojure applications](ci/examples/test-clojure-application.md) -+ Help your favorite programming language and GitLab by sending a merge request with a guide for that language. +- [Test and deploy Ruby applications to Heroku](ci/examples/test-and-deploy-ruby-application-to-heroku.md) +- [Test and deploy Python applications to Heroku](ci/examples/test-and-deploy-python-application-to-heroku.md) +- [Test Clojure applications](ci/examples/test-clojure-application.md) +- Help your favorite programming language and GitLab by sending a merge request with a guide for that language. ## Administrator documentation @@ -49,11 +49,6 @@ - [Reply by email](incoming_email/README.md) Allow users to comment on issues and merge requests by replying to notification emails. - [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE. -### Administrator documentation - -+ [User permissions](permissions/permissions.md) -+ [API](api/README.md) - ## Contributor documentation - [Development](development/README.md) Explains the architecture and the guidelines for shell commands. From 5f696d0c22dd367fb4687b2d18f8db92e179bbcc Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 5 Nov 2015 21:21:12 +0200 Subject: [PATCH 029/140] Add CI permissions and api links to README [ci skip] --- doc/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/README.md b/doc/README.md index 3cd98fab0d..0f6866475f 100644 --- a/doc/README.md +++ b/doc/README.md @@ -24,6 +24,8 @@ - [Using Docker Images](ci/docker/using_docker_images.md) - [Using Docker Build](ci/docker/using_docker_build.md) - [Using Variables](ci/variables/README.md) +- [User permissions](ci/permissions/README.md) +- [API](ci/api/README.md) ### CI Examples From c468461579f0dedc0ad92609e6bb54624c2e2b04 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Thu, 5 Nov 2015 21:17:15 -0800 Subject: [PATCH 030/140] Only have one link in emails to make clicking it easier. --- app/views/layouts/notify.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml index 854cda57c3..f58b9bd6ba 100644 --- a/app/views/layouts/notify.html.haml +++ b/app/views/layouts/notify.html.haml @@ -40,9 +40,9 @@ Reply to this email directly or #{link_to "view it on GitLab", @target_url}. - else - #{link_to "View it on GitLab", @target_url} + #{link_to "View it on GitLab", @target_url}. %br - You're receiving this email because of your account on #{link_to Gitlab.config.gitlab.host, root_url}. + You're receiving this email because of your account on #{Gitlab.config.gitlab.host}. If you'd like to receive fewer emails, you can adjust your notification settings. = email_action @target_url From 40a9b1ce00ed4e4f03431b731ca5a6716356d5a6 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 5 Nov 2015 23:47:04 -0800 Subject: [PATCH 031/140] Fix bug where manually merged branches in a MR would end up with an empty diff Closes #3314 --- CHANGELOG | 1 + app/services/merge_requests/refresh_service.rb | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 16055208db..3614d79036 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.2.0 (unreleased) + - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) - Force update refs/merge-requests/X/head upon a push to the source branch of a merge request (Stan Hu) - Improved performance of finding users by one of their Email addresses - Improved performance of replacing references in comments diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index d68bc79ecc..e180edb4bf 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -7,17 +7,17 @@ module MergeRequests @branch_name = Gitlab::Git.ref_name(ref) find_new_commits + # Be sure to close outstanding MRs before reloading them to avoid generating an + # empty diff during a manual merge + close_merge_requests reload_merge_requests # Leave a system note if a branch was deleted/added if branch_added? || branch_removed? comment_mr_branch_presence_changed - comment_mr_with_commits - else - comment_mr_with_commits - close_merge_requests end + comment_mr_with_commits execute_mr_web_hooks true From cea2afa85ed81abfa809a622ca3a548f770ad228 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 6 Nov 2015 14:14:43 +0100 Subject: [PATCH 032/140] Add changelog item and remove release labels for tags list Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + app/views/projects/tags/_tag.html.haml | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0ec6030b13..a7a9e073ae 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,6 +22,7 @@ v 8.2.0 (unreleased) - Include commit logs in project search - Add "added", "modified" and "removed" properties to commit object in webhook - Rename "Back to" links to "Go to" because its not always a case it point to place user come from + - Ability to add release notes (markdown text and attachments) to git tags v 8.1.3 - Spread out runner contacted_at updates diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index f3dc314a89..e2c5178185 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -9,9 +9,6 @@ - if tag.message.present?   = strip_gpg_signature(tag.message) - - if release -   - %span.label.label-success release .controls = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn-grouped btn' do From ed757ef6e3aab55c8fe0f2c1efdd79d65025c550 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 6 Nov 2015 14:44:08 +0100 Subject: [PATCH 033/140] Rewrite remove tag test Signed-off-by: Dmitriy Zaporozhets --- features/project/commits/tags.feature | 9 +-------- features/steps/project/commits/tags.rb | 25 ++++++------------------- 2 files changed, 7 insertions(+), 27 deletions(-) diff --git a/features/project/commits/tags.feature b/features/project/commits/tags.feature index 02f399f7ca..24fb84d1cc 100644 --- a/features/project/commits/tags.feature +++ b/features/project/commits/tags.feature @@ -29,13 +29,6 @@ Feature: Project Commits Tags @javascript Scenario: I delete a tag + Given I visit tag 'v1.1.0' page Given I delete tag 'v1.1.0' Then I should not see tag 'v1.1.0' - - @javascript - Scenario: I delete all tags and see info message - Given I delete all tags - Then I should see tags info message - - # @wip - # Scenario: I can download project by tag diff --git a/features/steps/project/commits/tags.rb b/features/steps/project/commits/tags.rb index e6f8faf50f..ff824c7695 100644 --- a/features/steps/project/commits/tags.rb +++ b/features/steps/project/commits/tags.rb @@ -52,11 +52,13 @@ class Spinach::Features::ProjectCommitsTags < Spinach::FeatureSteps expect(page).to have_content 'Tag already exists' end + step "I visit tag 'v1.1.0' page" do + click_link 'v1.1.0' + end + step "I delete tag 'v1.1.0'" do - page.within '.tags' do - first('.btn-remove').click - sleep 0.05 - end + first('.btn-remove').click + sleep 0.05 end step "I should not see tag 'v1.1.0'" do @@ -64,19 +66,4 @@ class Spinach::Features::ProjectCommitsTags < Spinach::FeatureSteps expect(page.all(visible: true)).not_to have_content 'v1.1.0' end end - - step 'I delete all tags' do - page.within '.tags' do - page.all('.btn-remove').each do |remove| - remove.click - sleep 0.05 - end - end - end - - step 'I should see tags info message' do - page.within '.tags' do - expect(page).to have_content 'Repository has no tags yet.' - end - end end From c1a893d91a569cf65b381e8c2b56c82824e9f593 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 6 Nov 2015 14:44:46 +0100 Subject: [PATCH 034/140] Fix list rendering issue when dropdown was hidden in row Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/lists.scss | 2 +- app/assets/stylesheets/framework/mixins.scss | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index f6942db581..45f3b5849b 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -117,7 +117,7 @@ ul.content-list { } .controls { - padding-top: 4px; + padding-top: 1px; float: right; .btn { diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index b9c179f288..11c48d26ab 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -72,9 +72,10 @@ list-style: none; > li { + @include clearfix; + padding: 10px 0; border-bottom: 1px solid #EEE; - overflow: hidden; display: block; margin: 0px; From b60ad399acdd753efe4f2d724c47800b6a70056b Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 6 Nov 2015 14:47:29 +0100 Subject: [PATCH 035/140] Fix test Signed-off-by: Dmitriy Zaporozhets --- features/project/commits/tags.feature | 1 - features/steps/project/commits/tags.rb | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/features/project/commits/tags.feature b/features/project/commits/tags.feature index 24fb84d1cc..660238c231 100644 --- a/features/project/commits/tags.feature +++ b/features/project/commits/tags.feature @@ -27,7 +27,6 @@ Feature: Project Commits Tags And I submit new tag form with tag that already exists Then I should see new an error that tag already exists - @javascript Scenario: I delete a tag Given I visit tag 'v1.1.0' page Given I delete tag 'v1.1.0' diff --git a/features/steps/project/commits/tags.rb b/features/steps/project/commits/tags.rb index ff824c7695..cb5fe20834 100644 --- a/features/steps/project/commits/tags.rb +++ b/features/steps/project/commits/tags.rb @@ -57,13 +57,14 @@ class Spinach::Features::ProjectCommitsTags < Spinach::FeatureSteps end step "I delete tag 'v1.1.0'" do - first('.btn-remove').click - sleep 0.05 + page.within('.content') do + first('.btn-remove').click + end end step "I should not see tag 'v1.1.0'" do page.within '.tags' do - expect(page.all(visible: true)).not_to have_content 'v1.1.0' + expect(page).not_to have_link 'v1.1.0' end end end From 962b57df5a3f476407858b12455030d8cda66e4b Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 6 Nov 2015 15:05:19 +0100 Subject: [PATCH 036/140] Small UI improvements to new git tag page Signed-off-by: Dmitriy Zaporozhets --- app/views/projects/releases/edit.html.haml | 4 +++- app/views/projects/tags/new.html.haml | 14 ++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml index fb841b77a2..7874141634 100644 --- a/app/views/projects/releases/edit.html.haml +++ b/app/views/projects/releases/edit.html.haml @@ -3,7 +3,9 @@ .gray-content-block .oneline - Release notes for #{@tag.name} + .title + Release notes for tag + %strong #{@tag.name} .prepend-top-default = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal gfm-form release-form' }) do |f| diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml index 9b224ff89b..be8f2e6f70 100644 --- a/app/views/projects/tags/new.html.haml +++ b/app/views/projects/tags/new.html.haml @@ -5,9 +5,11 @@ .alert.alert-danger %button{ type: "button", class: "close", "data-dismiss" => "alert"} × = @error + %h3.page-title - %i.fa.fa-code-fork - New tag + New git tag +%hr + = form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal tag-form" do .form-group = label_tag :tag_name, 'Name for new tag', class: 'control-label' @@ -17,15 +19,15 @@ = label_tag :ref, 'Create from', class: 'control-label' .col-sm-10 = text_field_tag :ref, params[:ref], placeholder: 'master', required: true, tabindex: 2, class: 'form-control' - .light Branch name or commit SHA + .help-block Branch name or commit SHA .form-group = label_tag :message, 'Message', class: 'control-label' .col-sm-10 = text_field_tag :message, nil, placeholder: 'Enter message.', required: false, tabindex: 3, class: 'form-control' - .light (Optional) Entering a message will create an annotated tag. + .help-block (Optional) Entering a message will create an annotated tag. %hr .form-group - = label_tag :release_description, 'Release description', class: 'control-label' + = label_tag :release_description, 'Release notes', class: 'control-label' .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do .zennable @@ -39,7 +41,7 @@ = icon('compress') = render 'projects/notes/hints' - .help-block You can add release description to your tag. It will be stored in GitLab database and displayed on tags page + .help-block (Optional) You can add release notes to your tag. It will be stored in GitLab database and displayed on tags page .form-actions = button_tag 'Create tag', class: 'btn btn-create', tabindex: 3 = link_to 'Cancel', namespace_project_tags_path(@project.namespace, @project), class: 'btn btn-cancel' From cb8b9c3fe2caba14752978f4283affc07581087c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 6 Nov 2015 15:29:04 +0100 Subject: [PATCH 037/140] Add association and validation for Release model Signed-off-by: Dmitriy Zaporozhets --- app/models/release.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/models/release.rb b/app/models/release.rb index 1dc9ce6dd4..05647839e8 100644 --- a/app/models/release.rb +++ b/app/models/release.rb @@ -1,2 +1,5 @@ class Release < ActiveRecord::Base + belongs_to :project + + validates :description, :project, presence: true end From 6f15356aea488ce0085c982aac2c97cdd46db96b Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 6 Nov 2015 15:43:59 +0100 Subject: [PATCH 038/140] Add tests to release notes feature Signed-off-by: Dmitriy Zaporozhets --- app/views/projects/tags/show.html.haml | 6 +++--- features/project/commits/tags.feature | 12 ++++++++++++ features/steps/project/commits/tags.rb | 20 ++++++++++++++++++++ spec/factories/releases.rb | 6 +++--- spec/models/release_spec.rb | 13 ++++++++++++- 5 files changed, 50 insertions(+), 7 deletions(-) diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml index f80ba5516a..f95ae9edc4 100644 --- a/app/views/projects/tags/show.html.haml +++ b/app/views/projects/tags/show.html.haml @@ -4,11 +4,11 @@ .gray-content-block .pull-right - = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn' do + = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn', title: 'Edit release notes' do = icon("pencil") - = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped' do + = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped', title: 'Browse source code' do = icon('files-o') - = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped' do + = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped', title: 'Browse commits' do = icon('history') - if can? current_user, :download_code, @project = render 'projects/tags/download', ref: @tag.name, project: @project diff --git a/features/project/commits/tags.feature b/features/project/commits/tags.feature index 660238c231..56ee091acc 100644 --- a/features/project/commits/tags.feature +++ b/features/project/commits/tags.feature @@ -12,6 +12,12 @@ Feature: Project Commits Tags And I submit new tag form Then I should see new tag created + Scenario: I create a tag with release notes + Given I click new tag link + And I submit new tag form with release notes + Then I should see new tag created + And I should see tag release notes + Scenario: I create a tag with invalid name And I click new tag link And I submit new tag form with invalid name @@ -31,3 +37,9 @@ Feature: Project Commits Tags Given I visit tag 'v1.1.0' page Given I delete tag 'v1.1.0' Then I should not see tag 'v1.1.0' + + Scenario: I add release notes to the tag + Given I visit tag 'v1.1.0' page + When I click edit tag link + And I fill release notes and submit form + Then I should see tag release notes diff --git a/features/steps/project/commits/tags.rb b/features/steps/project/commits/tags.rb index cb5fe20834..eff4234a44 100644 --- a/features/steps/project/commits/tags.rb +++ b/features/steps/project/commits/tags.rb @@ -18,6 +18,18 @@ class Spinach::Features::ProjectCommitsTags < Spinach::FeatureSteps click_button 'Create tag' end + step 'I submit new tag form with release notes' do + fill_in 'tag_name', with: 'v7.0' + fill_in 'ref', with: 'master' + fill_in 'release_description', with: 'Awesome release notes' + click_button 'Create tag' + end + + step 'I fill release notes and submit form' do + fill_in 'release_description', with: 'Awesome release notes' + click_button 'Save changes' + end + step 'I submit new tag form with invalid name' do fill_in 'tag_name', with: 'v 1.0' fill_in 'ref', with: 'master' @@ -67,4 +79,12 @@ class Spinach::Features::ProjectCommitsTags < Spinach::FeatureSteps expect(page).not_to have_link 'v1.1.0' end end + + step 'I click edit tag link' do + click_link 'Edit release notes' + end + + step 'I should see tag release notes' do + expect(page).to have_content 'Awesome release notes' + end end diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb index 067d8138e4..80d6bbee6c 100644 --- a/spec/factories/releases.rb +++ b/spec/factories/releases.rb @@ -2,8 +2,8 @@ FactoryGirl.define do factory :release do - tag "MyString" - description "MyText" - project_id 1 + tag "v1.1.0" + description "Awesome release" + project end end diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb index e533734ba0..527005b2b6 100644 --- a/spec/models/release_spec.rb +++ b/spec/models/release_spec.rb @@ -1,5 +1,16 @@ require 'rails_helper' RSpec.describe Release, type: :model do - pending "add some examples to (or delete) #{__FILE__}" + let(:release) { create(:release) } + + it { expect(release).to be_valid } + + describe 'associations' do + it { is_expected.to belong_to(:project) } + end + + describe 'validation' do + it { is_expected.to validate_presence_of(:project) } + it { is_expected.to validate_presence_of(:description) } + end end From 14518ba65a7727583445314917d466dabe4a6811 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 6 Nov 2015 15:48:18 +0100 Subject: [PATCH 039/140] Better english Signed-off-by: Dmitriy Zaporozhets --- app/views/projects/tags/new.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml index be8f2e6f70..e106be794f 100644 --- a/app/views/projects/tags/new.html.haml +++ b/app/views/projects/tags/new.html.haml @@ -41,7 +41,7 @@ = icon('compress') = render 'projects/notes/hints' - .help-block (Optional) You can add release notes to your tag. It will be stored in GitLab database and displayed on tags page + .help-block (Optional) You can add release notes to your tag. It will be stored in the GitLab database and shown on the tags page .form-actions = button_tag 'Create tag', class: 'btn btn-create', tabindex: 3 = link_to 'Cancel', namespace_project_tags_path(@project.namespace, @project), class: 'btn btn-cancel' From c5ec2a23a466f45127b6056693e1a473ae7e503e Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 6 Nov 2015 16:27:18 +0100 Subject: [PATCH 040/140] Small UI improvements to merge request page Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/blocks.scss | 4 ++++ app/assets/stylesheets/pages/diff.scss | 1 - app/helpers/diff_helper.rb | 4 ++-- app/views/projects/diffs/_diffs.html.haml | 2 +- app/views/projects/merge_requests/_discussion.html.haml | 2 +- app/views/projects/merge_requests/show/_commits.html.haml | 4 ++++ 6 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 8917c53b1f..1635df9c97 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -28,6 +28,10 @@ border-bottom: 1px solid $border-color; color: $gl-gray; + &.oneline-block { + line-height: 42px; + } + &.white { background-color: white; } diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index d9ef06dc6b..afd6fb7367 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -367,7 +367,6 @@ .inline-parallel-buttons { float: right; - margin-top: -5px; } // Mobile diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index e65e37211c..b889fb2897 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -137,7 +137,7 @@ module DiffHelper # Always use HTML to handle case where JSON diff rendered this button params_copy.delete(:format) - link_to url_for(params_copy), id: "inline-diff-btn", class: (params[:view] != 'parallel' ? 'btn btn-sm active' : 'btn btn-sm') do + link_to url_for(params_copy), id: "inline-diff-btn", class: (params[:view] != 'parallel' ? 'btn active' : 'btn') do 'Inline' end end @@ -148,7 +148,7 @@ module DiffHelper # Always use HTML to handle case where JSON diff rendered this button params_copy.delete(:format) - link_to url_for(params_copy), id: "parallel-diff-btn", class: (params[:view] == 'parallel' ? 'btn active btn-sm' : 'btn btn-sm') do + link_to url_for(params_copy), id: "parallel-diff-btn", class: (params[:view] == 'parallel' ? 'btn active' : 'btn') do 'Side-by-side' end end diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index 56b51f038b..e46bf1ab1e 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -3,7 +3,7 @@ - diff_files = safe_diff_files(diffs) -.gray-content-block.second-block +.gray-content-block.second-block.oneline-block .inline-parallel-buttons .btn-group = inline_diff_btn diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml index 38e66c3828..7e60782ff5 100644 --- a/app/views/projects/merge_requests/_discussion.html.haml +++ b/app/views/projects/merge_requests/_discussion.html.haml @@ -7,7 +7,7 @@ = render 'shared/show_aside' -.gray-content-block.second-block +.gray-content-block.second-block.oneline-block .row .col-md-9 .votes-holder.pull-right diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml index a71b181a6a..478054db51 100644 --- a/app/views/projects/merge_requests/show/_commits.html.haml +++ b/app/views/projects/merge_requests/show/_commits.html.haml @@ -1 +1,5 @@ +.gray-content-block.second-block.oneline-block + = icon("sort-amount-desc") + Most recent commits displayed first + = render "projects/commits/commits", project: @merge_request.project From 8f2561b193ad39f116655af0789798b45ad906c8 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 6 Nov 2015 08:04:02 -0800 Subject: [PATCH 041/140] Add spec for manual merge of merge request --- .../merge_requests/refresh_service_spec.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 227ac995ec..7ee4488521 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -62,6 +62,25 @@ describe MergeRequests::RefreshService do it { expect(@fork_merge_request.notes.last.note).to include('changed to merged') } end + context 'manual merge of source branch' do + before do + # Merge master -> feature branch + author = { email: 'test@gitlab.com', time: Time.now, name: "Me" } + commit_options = { message: 'Test message', committer: author, author: author } + master_commit = @project.repository.commit('master') + @project.repository.merge(@user, master_commit.id, 'feature', commit_options) + commit = @project.repository.commit('feature') + service.new(@project, @user).execute(@oldrev, commit.id, 'refs/heads/feature') + reload_mrs + end + + it { expect(@merge_request.notes.last.note).to include('changed to merged') } + it { expect(@merge_request).to be_merged } + it { expect(@merge_request.diffs.length).to be > 0 } + it { expect(@fork_merge_request).to be_merged } + it { expect(@fork_merge_request.notes.last.note).to include('changed to merged') } + end + context 'push to fork repo source branch' do let(:refresh_service) { service.new(@fork_project, @user) } before do From e34904370f7383853ed9e1f5392ff76defda9530 Mon Sep 17 00:00:00 2001 From: Baldinof Date: Fri, 6 Nov 2015 23:45:33 +0100 Subject: [PATCH 042/140] Merge button has color from CI status --- app/assets/stylesheets/pages/merge_requests.scss | 14 ++++++++++++++ .../merge_requests/widget/open/_accept.html.haml | 4 +++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index f0b3667acc..08e4bcdf52 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -19,6 +19,20 @@ .accept-merge-holder { .accept-action { display: inline-block; + + .accept_merge_request { + &.ci-pending, + &.ci-running { + @include btn-orange; + } + + &.ci-skipped, + &.ci-failed, + &.ci-canceled, + &.ci-error { + @include btn-red; + } + } } .accept-control { diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml index 613525437a..689247f318 100644 --- a/app/views/projects/merge_requests/widget/open/_accept.html.haml +++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml @@ -1,8 +1,10 @@ +- status_class = @merge_request.ci_commit ? " ci-#{@merge_request.ci_commit.status}" : nil + = form_for [:merge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-requires-input' } do |f| = hidden_field_tag :authenticity_token, form_authenticity_token .accept-merge-holder.clearfix.js-toggle-container .accept-action - = f.button class: "btn btn-create accept_merge_request" do + = f.button class: "btn btn-create accept_merge_request#{status_class}" do Accept Merge Request - if can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && !@merge_request.for_fork? .accept-control.checkbox From 78171d548158584fe19496fa5113052a9c3cc235 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Fri, 6 Nov 2015 18:25:41 -0800 Subject: [PATCH 043/140] Prevent people from adding the link back. --- app/views/layouts/notify.html.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml index f58b9bd6ba..a02dd95518 100644 --- a/app/views/layouts/notify.html.haml +++ b/app/views/layouts/notify.html.haml @@ -42,6 +42,7 @@ - else #{link_to "View it on GitLab", @target_url}. %br + -# Don't link the host is the line below, hone link in the email is easier to quickly click than two. You're receiving this email because of your account on #{Gitlab.config.gitlab.host}. If you'd like to receive fewer emails, you can adjust your notification settings. From b14198a5e7520ce7cf356006507db9b190e3afe3 Mon Sep 17 00:00:00 2001 From: Christian Speich Date: Sat, 7 Nov 2015 12:40:14 +0100 Subject: [PATCH 044/140] Hide tab-bar in login-box when only one tabs is shown. --- app/views/devise/shared/_signin_box.html.haml | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml index 41ad2c231d..9a1331b254 100644 --- a/app/views/devise/shared/_signin_box.html.haml +++ b/app/views/devise/shared/_signin_box.html.haml @@ -7,26 +7,34 @@ %h3 Sign in .login-body - if form_based_providers.any? - %ul.nav.nav-tabs + - if form_based_providers.count >= 2 || signin_enabled? + %ul.nav.nav-tabs + - if crowd_enabled? + %li.active + = link_to "Crowd", "#tab-crowd", 'data-toggle' => 'tab' + - @ldap_servers.each_with_index do |server, i| + %li{class: (:active if i.zero? && !crowd_enabled?)} + = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' + - if signin_enabled? + %li + = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' + .tab-content + - if crowd_enabled? + %div.tab-pane.active{id: "tab-crowd"} + = render 'devise/sessions/new_crowd' + - @ldap_servers.each_with_index do |server, i| + %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero? && !crowd_enabled?)} + = render 'devise/sessions/new_ldap', server: server + - if signin_enabled? + %div#tab-signin.tab-pane + = render 'devise/sessions/new_base' + - else - if crowd_enabled? - %li.active - = link_to "Crowd", "#tab-crowd", 'data-toggle' => 'tab' - - @ldap_servers.each_with_index do |server, i| - %li{class: (:active if i.zero? && !crowd_enabled?)} - = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' - - if signin_enabled? - %li - = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' - .tab-content - - if crowd_enabled? - %div.tab-pane.active{id: "tab-crowd"} - = render 'devise/sessions/new_crowd' - - @ldap_servers.each_with_index do |server, i| - %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero? && !crowd_enabled?)} - = render 'devise/sessions/new_ldap', server: server - - if signin_enabled? - %div#tab-signin.tab-pane - = render 'devise/sessions/new_base' + = render 'devise/sessions/new_crowd' + - elsif @ldap_servers.any? + = render 'devise/sessions/new_ldap', server: @ldap_servers.first + - elsif signin_enabled? + = render 'devise/sessions/new_base' - elsif signin_enabled? = render 'devise/sessions/new_base' From 920a2d974c15662ffac49146348c8d638a9db92b Mon Sep 17 00:00:00 2001 From: Jeroen van Baarsen Date: Wed, 28 Oct 2015 15:58:40 +0100 Subject: [PATCH 045/140] Fixed markdown issue in PROCESS.md --- PROCESS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PROCESS.md b/PROCESS.md index d42168a723..a4b0c83644 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -119,6 +119,6 @@ rebase with master to see if that solves the issue. ### Closing down the issue tracker on GitHub We are currently in the process of closing down the issue tracker on GitHub, to -prevent duplication with the [GitLab.com issue tracker][https://gitlab.com/gitlab-org/gitlab-ce/issues]. +prevent duplication with the GitLab.com issue tracker. Since this is an older issue I'll be closing this for now. If you think this is -still an issue I encourage you to open it on the [GitLab.com issue tracker][https://gitlab.com/gitlab-org/gitlab-ce/issues]. +still an issue I encourage you to open it on the \[GitLab.com issue tracker\](https://gitlab.com/gitlab-org/gitlab-ce/issues). From 4099751ce9b33fbafbfcfbf1506e091a20f7c034 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 7 Nov 2015 20:22:16 +0100 Subject: [PATCH 046/140] Render same markdown hint for issue, merge request, wiki and comment forms Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/markdown_area.scss | 1 + app/assets/stylesheets/pages/note_form.scss | 6 +++++- app/views/projects/wikis/_form.html.haml | 4 +--- app/views/shared/issuable/_form.html.haml | 9 +-------- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index ed0333d233..cc660529cb 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -106,6 +106,7 @@ } .markdown-area { + @include border-radius(0); background: #FFF; border: 1px solid #ddd; min-height: 140px; diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 4392f08942..268fc995aa 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -56,6 +56,10 @@ .note_text { width: 100%; } + + .comment-hints { + margin-top: -12px; + } } /* loading indicator */ @@ -168,7 +172,7 @@ color: #999; background: #FFF; padding: 7px; - margin-top: -11px; + margin-top: -7px; border: 1px solid $border-color; font-size: 13px; } diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 261d4a92d7..9c94c43e74 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -23,9 +23,7 @@ .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do = render 'projects/zen', f: f, attr: :content, classes: 'description form-control js-quick-submit' - .col-sm-12.hint - .pull-left Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'} - .pull-right Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. + = render 'projects/notes/hints' .clearfix .error-alert diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 594e54f404..0fc74d7d2b 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -27,14 +27,7 @@ = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do = render 'projects/zen', f: f, attr: :description, classes: 'description form-control js-quick-submit' - .col-sm-12.hint - .pull-left - Parsed with - #{link_to 'GitLab Flavored Markdown', help_page_path('markdown', 'markdown'), target: '_blank'}. - .pull-right - Attach files by dragging & dropping - or #{link_to 'selecting them', '#', class: 'markdown-selector' }. - + = render 'projects/notes/hints' .clearfix .error-alert %hr From b8cd6f9aae2eb0753c8f36284331aef6b4557bab Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 8 Nov 2015 18:00:31 +0200 Subject: [PATCH 047/140] Update piwik template --- app/views/layouts/_piwik.html.haml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/app/views/layouts/_piwik.html.haml b/app/views/layouts/_piwik.html.haml index 135e8daca2..259b4f7cdf 100644 --- a/app/views/layouts/_piwik.html.haml +++ b/app/views/layouts/_piwik.html.haml @@ -1,12 +1,14 @@ + :javascript var _paq = _paq || []; - _paq.push(["trackPageView"]); - _paq.push(["enableLinkTracking"]); - + _paq.push(['trackPageView']); + _paq.push(['enableLinkTracking']); (function() { - var u=(("https:" == document.location.protocol) ? "https" : "http") + "://#{extra_config.piwik_url}/"; - _paq.push(["setTrackerUrl", u+"piwik.php"]); - _paq.push(["setSiteId", "#{extra_config.piwik_site_id}"]); - var d=document, g=d.createElement("script"), s=d.getElementsByTagName("script")[0]; g.type="text/javascript"; - g.defer=true; g.async=true; g.src=u+"piwik.js"; s.parentNode.insertBefore(g,s); + var u="//#{extra_config.piwik_url}/"; + _paq.push(['setTrackerUrl', u+'piwik.php']); + _paq.push(['setSiteId', #{extra_config.piwik_site_id}]); + var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; + g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s); })(); + + From 2e5d32e07a75b98e57a0476a4cdb7a1ec41cadda Mon Sep 17 00:00:00 2001 From: Pirate Praveen Date: Sun, 8 Nov 2015 12:15:58 -0500 Subject: [PATCH 048/140] Switch to state_machines-activerecord (Fixes: #3374) --- Gemfile | 2 +- config/initializers/state_machine_patch.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 0bac897816..ee8b2a7a51 100644 --- a/Gemfile +++ b/Gemfile @@ -113,7 +113,7 @@ group :unicorn do end # State machine -gem "state_machine", '~> 1.2.0' +gem "state_machines-activerecord", '~> 0.3.0' # Run events after state machine commits gem 'after_commit_queue' diff --git a/config/initializers/state_machine_patch.rb b/config/initializers/state_machine_patch.rb index 72d010fa5d..51f0579436 100644 --- a/config/initializers/state_machine_patch.rb +++ b/config/initializers/state_machine_patch.rb @@ -1,6 +1,6 @@ # This is a patch to address the issue in https://github.com/pluginaweek/state_machine/issues/251 # where gem 'state_machine' was not working for Rails 4.1 -module StateMachine +module StateMachines module Integrations module ActiveModel public :around_validation From ac5b6c3b50b5220d3fc800508267b320fa7cf78b Mon Sep 17 00:00:00 2001 From: Christophe Poulette Date: Sun, 8 Nov 2015 19:29:22 +0100 Subject: [PATCH 049/140] Apply new design for project graphs page --- CHANGELOG | 1 + app/views/projects/graphs/_head.html.haml | 2 +- app/views/projects/graphs/ci.html.haml | 3 +++ app/views/projects/graphs/commits.html.haml | 10 +++++++--- app/views/projects/graphs/show.html.haml | 8 ++++++-- 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3a75f50e9a..6f6bc8a745 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -23,6 +23,7 @@ v 8.2.0 (unreleased) - Add "added", "modified" and "removed" properties to commit object in webhook - Rename "Back to" links to "Go to" because its not always a case it point to place user come from - Allow groups to appear in the search results if the group owner allows it + - New design for project graphs page v 8.1.3 - Spread out runner contacted_at updates diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml index bbfaf422a8..e0d06a14bf 100644 --- a/app/views/projects/graphs/_head.html.haml +++ b/app/views/projects/graphs/_head.html.haml @@ -1,4 +1,4 @@ -%ul.nav.nav-tabs +%ul.center-top-menu = nav_link(action: :show) do = link_to 'Contributors', namespace_project_graph_path = nav_link(action: :commits) do diff --git a/app/views/projects/graphs/ci.html.haml b/app/views/projects/graphs/ci.html.haml index 4f69cc64f7..b2dfe97938 100644 --- a/app/views/projects/graphs/ci.html.haml +++ b/app/views/projects/graphs/ci.html.haml @@ -1,6 +1,9 @@ - page_title "Continuous Integration", "Graphs" = render "header_title" = render 'head' +.gray-content-block + %ul.breadcrumb.repo-breadcrumb + = commits_breadcrumbs #charts.ci-charts = render 'projects/graphs/ci/builds' = render 'projects/graphs/ci/build_times' diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml index 112be875b6..a21d744865 100644 --- a/app/views/projects/graphs/commits.html.haml +++ b/app/views/projects/graphs/commits.html.haml @@ -1,8 +1,12 @@ - page_title "Commits", "Graphs" = render "header_title" -.tree-ref-holder - = render 'shared/ref_switcher', destination: 'graphs_commits' -= render 'head' += reder 'head' + +.gray-content-block + .tree-ref-holder + = render 'shared/ref_switcher', destination: 'graphs_commits' + %ul.breadcrumb.repo-breadcrumb + = commits_breadcrumbs %p.lead Commit statistics for diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index bd342911e4..6bbf15d05a 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -1,9 +1,13 @@ - page_title "Contributors", "Graphs" = render "header_title" -.tree-ref-holder - = render 'shared/ref_switcher', destination: 'graphs' = render 'head' +.gray-content-block + .tree-ref-holder + = render 'shared/ref_switcher', destination: 'graphs' + %ul.breadcrumb.repo-breadcrumb + = commits_breadcrumbs + .loading-graph .center %h3.page-title From b68a2e6f251cd0af7179eba5ed058fcaab009377 Mon Sep 17 00:00:00 2001 From: Christophe Poulette Date: Sun, 8 Nov 2015 20:15:03 +0100 Subject: [PATCH 050/140] Fix typo --- app/views/projects/graphs/commits.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml index a21d744865..838d3e4061 100644 --- a/app/views/projects/graphs/commits.html.haml +++ b/app/views/projects/graphs/commits.html.haml @@ -1,6 +1,6 @@ - page_title "Commits", "Graphs" = render "header_title" -= reder 'head' += redner 'head' .gray-content-block .tree-ref-holder From 4ac26c6463b53e2a02a43835e6a919e2bc023fb7 Mon Sep 17 00:00:00 2001 From: Christophe Poulette Date: Sun, 8 Nov 2015 20:31:55 +0100 Subject: [PATCH 051/140] Fix typo. --- app/views/projects/graphs/commits.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml index 838d3e4061..4e0c3e5b3d 100644 --- a/app/views/projects/graphs/commits.html.haml +++ b/app/views/projects/graphs/commits.html.haml @@ -1,6 +1,6 @@ - page_title "Commits", "Graphs" = render "header_title" -= redner 'head' += render 'head' .gray-content-block .tree-ref-holder From 4fda9ef8a522352886fad08524b4ad4054a7eb60 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 5 Nov 2015 16:22:37 +0100 Subject: [PATCH 052/140] Fix tests --- spec/factories/ci/projects.rb | 2 ++ spec/models/application_setting_spec.rb | 28 +++++++++++++++++-- .../ci/register_build_service_spec.rb | 4 +++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/spec/factories/ci/projects.rb b/spec/factories/ci/projects.rb index 111e1a8281..1183a19035 100644 --- a/spec/factories/ci/projects.rb +++ b/spec/factories/ci/projects.rb @@ -33,6 +33,8 @@ FactoryGirl.define do gl_project factory: :empty_project + shared_runners_enabled false + factory :ci_project do token 'iPWx6WM4lhHNedGfBpPJNP' end diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index de0b2ef4cd..f01fe8bd39 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -28,11 +28,11 @@ require 'spec_helper' describe ApplicationSetting, models: true do - it { expect(ApplicationSetting.create_from_defaults).to be_valid } + let(:setting) { ApplicationSetting.create_from_defaults } + + it { expect(setting).to be_valid } context 'restricted signup domains' do - let(:setting) { ApplicationSetting.create_from_defaults } - it 'set single domain' do setting.restricted_signup_domains_raw = 'example.com' expect(setting.restricted_signup_domains).to eq(['example.com']) @@ -53,4 +53,26 @@ describe ApplicationSetting, models: true do expect(setting.restricted_signup_domains).to eq(['example.com', '*.example.com']) end end + + context 'shared runners' do + let(:gl_project) { create(:empty_project) } + + before do + allow_any_instance_of(Project).to receive(:current_application_settings).and_return(setting) + end + + subject { gl_project.ensure_gitlab_ci_project.shared_runners_enabled } + + context 'enabled' do + before { setting.update_attributes(shared_runners_enabled: true) } + + it { is_expected.to be_truthy } + end + + context 'disabled' do + before { setting.update_attributes(shared_runners_enabled: false) } + + it { is_expected.to be_falsey } + end + end end diff --git a/spec/services/ci/register_build_service_spec.rb b/spec/services/ci/register_build_service_spec.rb index 781764627a..b370dfbe11 100644 --- a/spec/services/ci/register_build_service_spec.rb +++ b/spec/services/ci/register_build_service_spec.rb @@ -70,6 +70,10 @@ module Ci end context 'disallow shared runners' do + before do + gl_project.gitlab_ci_project.update(shared_runners_enabled: false) + end + context 'shared runner' do let(:build) { service.execute(shared_runner) } From 3727c9cab2ab48ca6aa2f5cfa3cd3126a02002b1 Mon Sep 17 00:00:00 2001 From: Sullivan SENECHAL Date: Wed, 21 Oct 2015 10:40:05 +0200 Subject: [PATCH 053/140] Add GitLabCI code coverage regex sample for PHPUnit --- app/views/projects/ci_settings/_form.html.haml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/projects/ci_settings/_form.html.haml b/app/views/projects/ci_settings/_form.html.haml index 20bdccc902..ee6b8885e2 100644 --- a/app/views/projects/ci_settings/_form.html.haml +++ b/app/views/projects/ci_settings/_form.html.haml @@ -103,8 +103,9 @@ %li pytest-cov (Python) - %code \d+\%\s*$ - - + %li + phpunit --coverage-text --colors=never (PHP) - + %code ^\s*Lines:\s*\d+.\d+\% %fieldset %legend Advanced settings From 0fb85939da482e50a7337d8762fb26e75bb16ce5 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 9 Nov 2015 14:12:05 +0100 Subject: [PATCH 054/140] Fix incoming email config defaults --- CHANGELOG | 1 + config/initializers/1_settings.rb | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2201221116..4951ae172c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -24,6 +24,7 @@ v 8.2.0 (unreleased) - Add "added", "modified" and "removed" properties to commit object in webhook - Rename "Back to" links to "Go to" because its not always a case it point to place user come from - Allow groups to appear in the search results if the group owner allows it + - Fix incoming email config defaults v 8.1.3 - Spread out runner contacted_at updates diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 64d73d7232..8192d727f2 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -193,8 +193,8 @@ Settings.gitlab_ci['builds_path'] = File.expand_path(Settings.gitlab_c Settings['incoming_email'] ||= Settingslogic.new({}) Settings.incoming_email['enabled'] = false if Settings.incoming_email['enabled'].nil? Settings.incoming_email['port'] = 143 if Settings.incoming_email['port'].nil? -Settings.incoming_email['ssl'] = 143 if Settings.incoming_email['ssl'].nil? -Settings.incoming_email['start_tls'] = 143 if Settings.incoming_email['start_tls'].nil? +Settings.incoming_email['ssl'] = false if Settings.incoming_email['ssl'].nil? +Settings.incoming_email['start_tls'] = false if Settings.incoming_email['start_tls'].nil? Settings.incoming_email['mailbox'] = "inbox" if Settings.incoming_email['mailbox'].nil? # From dec3e4ce64df5f71a7cba7734cada1baa79242cd Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 4 Nov 2015 19:13:19 +0100 Subject: [PATCH 055/140] Added Sherlock, a custom profiling tool for GitLab Sherlock will be a new GitLab specific tool for measuring the performance of Rails requests (and SideKiq jobs at some point). Some of the things that are currently tracked: * SQL queries along with their timings, backtraces and query plans (using "EXPLAIN ANALYZE" for PostgreSQL and regular "EXPLAIN" for MySQL) * Timings of application files (including views) on a per line basis * Some meta data such as the request method, path, total duration, etc More tracking (e.g. Rugged or gitlab-shell timings) might be added in the future. Sherlock will replace any existing tools we have used so far (e.g. active_record_query_trace and rack-mini-profiler), hence the corresponding Gems have been removed from the Gemfile. Sherlock can be enabled by starting Rails as following: ENABLE_SHERLOCK=1 bundle exec rails s Recorded transactions can be found at `/sherlock/transactions`. --- Gemfile | 4 +- Gemfile.lock | 11 +-- app/assets/stylesheets/pages/sherlock.scss | 33 +++++++ .../sherlock/application_controller.rb | 12 +++ .../sherlock/file_samples_controller.rb | 7 ++ .../sherlock/queries_controller.rb | 7 ++ .../sherlock/transactions_controller.rb | 19 ++++ .../sherlock/file_samples/show.html.haml | 55 +++++++++++ .../sherlock/queries/_backtrace.html.haml | 27 ++++++ app/views/sherlock/queries/_general.html.haml | 50 ++++++++++ app/views/sherlock/queries/show.html.haml | 26 +++++ .../transactions/_file_samples.html.haml | 22 +++++ .../sherlock/transactions/_general.html.haml | 33 +++++++ .../sherlock/transactions/_queries.html.haml | 24 +++++ .../sherlock/transactions/index.html.haml | 40 ++++++++ .../sherlock/transactions/show.html.haml | 36 +++++++ config/initializers/rack_profiler.rb | 10 -- config/initializers/sherlock.rb | 5 + config/locales/sherlock.en.yml | 36 +++++++ config/routes.rb | 13 +++ lib/gitlab/sherlock.rb | 20 ++++ lib/gitlab/sherlock/collection.rb | 42 ++++++++ lib/gitlab/sherlock/file_sample.rb | 27 ++++++ lib/gitlab/sherlock/line_profiler.rb | 60 ++++++++++++ lib/gitlab/sherlock/line_sample.rb | 20 ++++ lib/gitlab/sherlock/location.rb | 22 +++++ lib/gitlab/sherlock/middleware.rb | 36 +++++++ lib/gitlab/sherlock/query.rb | 96 +++++++++++++++++++ lib/gitlab/sherlock/transaction.rb | 85 ++++++++++++++++ 29 files changed, 855 insertions(+), 23 deletions(-) create mode 100644 app/assets/stylesheets/pages/sherlock.scss create mode 100644 app/controllers/sherlock/application_controller.rb create mode 100644 app/controllers/sherlock/file_samples_controller.rb create mode 100644 app/controllers/sherlock/queries_controller.rb create mode 100644 app/controllers/sherlock/transactions_controller.rb create mode 100644 app/views/sherlock/file_samples/show.html.haml create mode 100644 app/views/sherlock/queries/_backtrace.html.haml create mode 100644 app/views/sherlock/queries/_general.html.haml create mode 100644 app/views/sherlock/queries/show.html.haml create mode 100644 app/views/sherlock/transactions/_file_samples.html.haml create mode 100644 app/views/sherlock/transactions/_general.html.haml create mode 100644 app/views/sherlock/transactions/_queries.html.haml create mode 100644 app/views/sherlock/transactions/index.html.haml create mode 100644 app/views/sherlock/transactions/show.html.haml delete mode 100644 config/initializers/rack_profiler.rb create mode 100644 config/initializers/sherlock.rb create mode 100644 config/locales/sherlock.en.yml create mode 100644 lib/gitlab/sherlock.rb create mode 100644 lib/gitlab/sherlock/collection.rb create mode 100644 lib/gitlab/sherlock/file_sample.rb create mode 100644 lib/gitlab/sherlock/line_profiler.rb create mode 100644 lib/gitlab/sherlock/line_sample.rb create mode 100644 lib/gitlab/sherlock/location.rb create mode 100644 lib/gitlab/sherlock/middleware.rb create mode 100644 lib/gitlab/sherlock/query.rb create mode 100644 lib/gitlab/sherlock/transaction.rb diff --git a/Gemfile b/Gemfile index 0bac897816..b0a7c9b945 100644 --- a/Gemfile +++ b/Gemfile @@ -215,11 +215,9 @@ group :development do gem "annotate", "~> 2.6.0" gem "letter_opener", '~> 1.1.2' gem 'quiet_assets', '~> 1.0.2' - gem 'rack-mini-profiler', '~> 0.9.0', require: false gem 'rerun', '~> 0.10.0' gem 'bullet', require: false - gem 'active_record_query_trace', require: false - gem 'rack-lineprof', platform: :mri + gem 'rblineprof', platform: :mri, require: false # Better errors handler gem 'better_errors', '~> 1.0.1' diff --git a/Gemfile.lock b/Gemfile.lock index dce728baf1..c602d40671 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -17,7 +17,6 @@ GEM activesupport (= 4.1.12) builder (~> 3.1) erubis (~> 2.7.0) - active_record_query_trace (1.5) activemodel (4.1.12) activesupport (= 4.1.12) builder (~> 3.1) @@ -491,12 +490,6 @@ GEM rack-attack (4.3.0) rack rack-cors (0.4.0) - rack-lineprof (0.0.3) - rack (~> 1.5) - rblineprof (~> 0.3.6) - term-ansicolor (~> 1.3) - rack-mini-profiler (0.9.7) - rack (>= 1.1.3) rack-mount (0.8.3) rack (>= 1.0.0) rack-oauth2 (1.0.10) @@ -779,7 +772,6 @@ PLATFORMS DEPENDENCIES RedCloth (~> 4.2.9) ace-rails-ap (~> 2.0.1) - active_record_query_trace activerecord-deprecated_finders (~> 1.0.3) activerecord-session_store (~> 0.1.0) acts-as-taggable-on (~> 3.4) @@ -878,11 +870,10 @@ DEPENDENCIES quiet_assets (~> 1.0.2) rack-attack (~> 4.3.0) rack-cors (~> 0.4.0) - rack-lineprof - rack-mini-profiler (~> 0.9.0) rack-oauth2 (~> 1.0.5) rails (= 4.1.12) raphael-rails (~> 2.1.2) + rblineprof rdoc (~> 3.6) redcarpet (~> 3.3.3) redis-rails (~> 4.0.0) diff --git a/app/assets/stylesheets/pages/sherlock.scss b/app/assets/stylesheets/pages/sherlock.scss new file mode 100644 index 0000000000..92d84d9640 --- /dev/null +++ b/app/assets/stylesheets/pages/sherlock.scss @@ -0,0 +1,33 @@ +table .sherlock-code { + max-width: 700px; +} + +.sherlock-code { + pre { + word-wrap: normal; + } + + pre code { + white-space: pre; + } +} + +.sherlock-line-samples-table { + margin-bottom: 0px !important; + + thead tr th, + tbody tr td { + font-size: 13px !important; + text-align: right; + padding: 0px 10px !important; + } +} + +.sherlock-file-sample pre { + padding-top: 28px !important; +} + +.sherlock-line-samples-table .slow { + color: $red-light; + font-weight: bold; +} diff --git a/app/controllers/sherlock/application_controller.rb b/app/controllers/sherlock/application_controller.rb new file mode 100644 index 0000000000..682ca5e382 --- /dev/null +++ b/app/controllers/sherlock/application_controller.rb @@ -0,0 +1,12 @@ +module Sherlock + class ApplicationController < ::ApplicationController + before_action :find_transaction + + def find_transaction + if params[:transaction_id] + @transaction = Gitlab::Sherlock.collection. + find_transaction(params[:transaction_id]) + end + end + end +end diff --git a/app/controllers/sherlock/file_samples_controller.rb b/app/controllers/sherlock/file_samples_controller.rb new file mode 100644 index 0000000000..0c3bc10010 --- /dev/null +++ b/app/controllers/sherlock/file_samples_controller.rb @@ -0,0 +1,7 @@ +module Sherlock + class FileSamplesController < Sherlock::ApplicationController + def show + @file_sample = @transaction.find_file_sample(params[:id]) + end + end +end diff --git a/app/controllers/sherlock/queries_controller.rb b/app/controllers/sherlock/queries_controller.rb new file mode 100644 index 0000000000..63b26aab1a --- /dev/null +++ b/app/controllers/sherlock/queries_controller.rb @@ -0,0 +1,7 @@ +module Sherlock + class QueriesController < Sherlock::ApplicationController + def show + @query = @transaction.find_query(params[:id]) + end + end +end diff --git a/app/controllers/sherlock/transactions_controller.rb b/app/controllers/sherlock/transactions_controller.rb new file mode 100644 index 0000000000..ccc739da87 --- /dev/null +++ b/app/controllers/sherlock/transactions_controller.rb @@ -0,0 +1,19 @@ +module Sherlock + class TransactionsController < Sherlock::ApplicationController + def index + @transactions = Gitlab::Sherlock.collection.newest_first + end + + def show + @transaction = Gitlab::Sherlock.collection.find_transaction(params[:id]) + + render_404 unless @transaction + end + + def destroy_all + Gitlab::Sherlock.collection.clear + + redirect_to(:back) + end + end +end diff --git a/app/views/sherlock/file_samples/show.html.haml b/app/views/sherlock/file_samples/show.html.haml new file mode 100644 index 0000000000..cfd11e45b6 --- /dev/null +++ b/app/views/sherlock/file_samples/show.html.haml @@ -0,0 +1,55 @@ +- page_title t('sherlock.title'), t('sherlock.transaction'), + t('sherlock.file_sample') + +- header_title t('sherlock.title'), sherlock_transactions_path + +.gray-content-block + .pull-right + = link_to(sherlock_transaction_path(@transaction), class: 'btn') do + %i.fa.fa-arrow-left + = t('sherlock.transaction') + .oneline + = t('sherlock.file_sample') + = @file_sample.id + +.prepend-top-default + %p + %span.light + #{t('sherlock.time')}: + %strong + = @file_sample.duration.round(2) + = t('sherlock.milliseconds') + %p + %span.light + #{t('sherlock.events')}: + %strong + = @file_sample.events + +%article.file-holder + .file-title + %i.fa.fa-file-text-o.fa-fw + %strong + = @file_sample.file + .code.file-content.js-syntax-highlight + .line-numbers + %table.sherlock-line-samples-table + %thead + %tr + %th= t('sherlock.line_capitalized') + %th= t('sherlock.events') + %th= t('sherlock.time') + %th= t('sherlock.percent') + %tbody + - @file_sample.line_samples.each_with_index do |sample, index| + %tr{class: sample.majority_of?(@file_sample.duration) ? 'slow' : ''} + %td= index + 1 + %td= sample.events + %td + = sample.duration.round(2) + = t('sherlock.milliseconds') + %td + = sample.percentage_of(@file_sample.duration).round + = t('sherlock.percent') + + .sherlock-file-sample + = highlight(@file_sample.file, @file_sample.source) diff --git a/app/views/sherlock/queries/_backtrace.html.haml b/app/views/sherlock/queries/_backtrace.html.haml new file mode 100644 index 0000000000..5c9294c0ab --- /dev/null +++ b/app/views/sherlock/queries/_backtrace.html.haml @@ -0,0 +1,27 @@ +.prepend-top-default + .panel.panel-default + .panel-heading + %strong + = t('sherlock.application_backtrace') + %ul.well-list + - @query.application_backtrace.each do |location| + %li + = location.path + %small.light + = t('sherlock.line') + = location.line + + .panel.panel-default + .panel-heading + %strong + = t('sherlock.full_backtrace') + %ul.well-list + - @query.backtrace.each do |location| + %li + - if location.application? + %strong= location.path + - else + = location.path + %small.light + = t('sherlock.line') + = location.line diff --git a/app/views/sherlock/queries/_general.html.haml b/app/views/sherlock/queries/_general.html.haml new file mode 100644 index 0000000000..549b47430e --- /dev/null +++ b/app/views/sherlock/queries/_general.html.haml @@ -0,0 +1,50 @@ +.prepend-top-default + .panel.panel-default + .panel-heading + %strong + = t('sherlock.general') + %ul.well-list + %li + %span.light + #{t('sherlock.time')}: + %strong + = @query.duration.round(4) + = t('sherlock.milliseconds') + %li + %span.light + #{t('sherlock.origin')}: + %strong + = @query.last_application_frame.path + %small.light + = t('sherlock.line') + = @query.last_application_frame.line + + .panel.panel-default + .panel-heading + .pull-right + %button.js-clipboard-trigger.btn.btn-xs{title: t('sherlock.copy_to_clipboard'), type: :button} + %i.fa.fa-clipboard + %pre.hidden + = @query.formatted_query + %strong + = t('sherlock.query') + %ul.well-list + %li + .code.js-syntax-highlight.sherlock-code + :preserve + #{highlight("#{@query.id}.sql", @query.formatted_query)} + + .panel.panel-default + .panel-heading + .pull-right + %button.js-clipboard-trigger.btn.btn-xs{title: t('sherlock.copy_to_clipboard'), type: :button} + %i.fa.fa-clipboard + %pre.hidden + = @query.explain + %strong + = t('sherlock.query_plan') + %ul.well-list + %li + .code.js-syntax-highlight.sherlock-code + %pre + %code= @query.explain diff --git a/app/views/sherlock/queries/show.html.haml b/app/views/sherlock/queries/show.html.haml new file mode 100644 index 0000000000..4a84348ac8 --- /dev/null +++ b/app/views/sherlock/queries/show.html.haml @@ -0,0 +1,26 @@ +- page_title t('sherlock.title'), t('sherlock.transaction'), t('sherlock.query') +- header_title t('sherlock.title'), sherlock_transactions_path + +%ul.center-top-menu + %li.active + %a(href="#tab-general" data-toggle="tab") + = t('sherlock.general') + %li + %a(href="#tab-backtrace" data-toggle="tab") + = t('sherlock.backtrace') + +.gray-content-block + .pull-right + = link_to(sherlock_transaction_path(@transaction), class: 'btn') do + %i.fa.fa-arrow-left + = t('sherlock.transaction') + .oneline + = t('sherlock.query') + = @query.id + +.tab-content + .tab-pane.active#tab-general + = render(partial: 'general') + + .tab-pane#tab-backtrace + = render(partial: 'backtrace') diff --git a/app/views/sherlock/transactions/_file_samples.html.haml b/app/views/sherlock/transactions/_file_samples.html.haml new file mode 100644 index 0000000000..0afdbc8dff --- /dev/null +++ b/app/views/sherlock/transactions/_file_samples.html.haml @@ -0,0 +1,22 @@ +- if @transaction.file_samples.empty? + .nothing-here-block + = t('sherlock.no_file_samples') +- else + .table-holder + %table.table + %thead + %tr + %th= t('sherlock.time_inclusive') + %th= t('sherlock.path') + %th + %tbody + - @transaction.sorted_file_samples.each do |sample| + %tr + %td + = sample.duration.round(2) + = t('sherlock.milliseconds') + %td= sample.relative_path + %td + = link_to(t('sherlock.view'), + sherlock_transaction_file_sample_path(@transaction, sample), + class: 'btn btn-xs') diff --git a/app/views/sherlock/transactions/_general.html.haml b/app/views/sherlock/transactions/_general.html.haml new file mode 100644 index 0000000000..4287a0c320 --- /dev/null +++ b/app/views/sherlock/transactions/_general.html.haml @@ -0,0 +1,33 @@ +.prepend-top-default + .panel.panel-default + .panel-heading + %strong + = t('sherlock.general') + %ul.well-list + %li + %span.light + #{t('sherlock.id')}: + %strong + = @transaction.id + %li + %span.light + #{t('sherlock.type')}: + %strong + = @transaction.type + %li + %span.light + #{t('sherlock.path')}: + %strong + = @transaction.path + %li + %span.light + #{t('sherlock.time')}: + %strong + = @transaction.duration.round(2) + = t('sherlock.seconds') + %li + %span.light + #{t('sherlock.finished_at')}: + %strong + = time_ago_in_words(@transaction.finished_at) + = t('sherlock.ago') diff --git a/app/views/sherlock/transactions/_queries.html.haml b/app/views/sherlock/transactions/_queries.html.haml new file mode 100644 index 0000000000..b7e0162e80 --- /dev/null +++ b/app/views/sherlock/transactions/_queries.html.haml @@ -0,0 +1,24 @@ +- if @transaction.queries.empty? + .nothing-here-block + = t('sherlock.no_queries') +- else + .table-holder + %table.table#sherlock-queries + %thead + %tr + %th= t('sherlock.time') + %th= t('sherlock.query') + %td + %tbody + - @transaction.sorted_queries.each do |query| + %tr + %td + = query.duration.round(2) + = t('sherlock.milliseconds') + %td + .code.js-syntax-highlight.sherlock-code + = highlight("#{query.id}.sql", query.formatted_query) + %td + = link_to(t('sherlock.view'), + sherlock_transaction_query_path(@transaction, query), + class: 'btn btn-xs') diff --git a/app/views/sherlock/transactions/index.html.haml b/app/views/sherlock/transactions/index.html.haml new file mode 100644 index 0000000000..fb31131ba8 --- /dev/null +++ b/app/views/sherlock/transactions/index.html.haml @@ -0,0 +1,40 @@ +- page_title t('sherlock.title') +- header_title t('sherlock.title'), sherlock_transactions_path + +.gray-content-block + .pull-right + = link_to(destroy_all_sherlock_transactions_path, + class: 'btn btn-danger', + method: :delete) do + %i.fa.fa-trash + = t('sherlock.delete_all_transactions') + .oneline= t('sherlock.introduction') + +- if @transactions.empty? + .nothing-here-block= t('sherlock.no_transactions') +- else + .table-holder + %table.table + %thead + %tr + %th= t('sherlock.type') + %th= t('sherlock.path') + %th= t('sherlock.time') + %th= t('sherlock.queries') + %th= t('sherlock.finished_at') + %th + %tbody + - @transactions.each do |trans| + %tr + %td= trans.type + %td= trans.path + %td + = trans.duration.round(2) + = t('sherlock.seconds') + %td= trans.queries.length + %td + = time_ago_in_words(trans.finished_at) + = t('sherlock.ago') + %td + = link_to(sherlock_transaction_path(trans), class: 'btn btn-xs') do + = t('sherlock.view') diff --git a/app/views/sherlock/transactions/show.html.haml b/app/views/sherlock/transactions/show.html.haml new file mode 100644 index 0000000000..3c8ffb0664 --- /dev/null +++ b/app/views/sherlock/transactions/show.html.haml @@ -0,0 +1,36 @@ +- page_title t('sherlock.title'), t('sherlock.transaction') +- header_title t('sherlock.title'), sherlock_transactions_path + +%ul.center-top-menu + %li.active + %a(href="#tab-general" data-toggle="tab") + = t('sherlock.general') + %li + %a(href="#tab-queries" data-toggle="tab") + = t('sherlock.queries') + %span.badge + #{@transaction.queries.length} + %li + %a(href="#tab-file-samples" data-toggle="tab") + = t('sherlock.file_samples') + %span.badge + #{@transaction.file_samples.length} + +.gray-content-block + .pull-right + = link_to(sherlock_transactions_path, class: 'btn') do + %i.fa.fa-arrow-left + = t('sherlock.all_transactions') + .oneline + = t('sherlock.transaction') + = @transaction.id + +.tab-content + .tab-pane.active#tab-general + = render(partial: 'general') + + .tab-pane#tab-queries + = render(partial: 'queries') + + .tab-pane#tab-file-samples + = render(partial: 'file_samples') diff --git a/config/initializers/rack_profiler.rb b/config/initializers/rack_profiler.rb deleted file mode 100644 index 7710eeac45..0000000000 --- a/config/initializers/rack_profiler.rb +++ /dev/null @@ -1,10 +0,0 @@ -if Rails.env.development? - require 'rack-mini-profiler' - - # initialization is skipped so trigger it - Rack::MiniProfilerRails.initialize!(Gitlab::Application) - - Rack::MiniProfiler.config.position = 'right' - Rack::MiniProfiler.config.start_hidden = false - Rack::MiniProfiler.config.skip_paths << '/teaspoon' -end diff --git a/config/initializers/sherlock.rb b/config/initializers/sherlock.rb new file mode 100644 index 0000000000..42b0d78c85 --- /dev/null +++ b/config/initializers/sherlock.rb @@ -0,0 +1,5 @@ +if Gitlab::Sherlock.enabled? + Gitlab::Application.configure do |config| + config.middleware.use(Gitlab::Sherlock::Middleware) + end +end diff --git a/config/locales/sherlock.en.yml b/config/locales/sherlock.en.yml new file mode 100644 index 0000000000..5c146b172b --- /dev/null +++ b/config/locales/sherlock.en.yml @@ -0,0 +1,36 @@ +en: + sherlock: + title: Sherlock + delete_all_transactions: Delete All Transactions + introduction: > + Below is a list of all transactions recorded by Sherlock. Requests to + Sherlock's own routes are ignored. + no_transactions: No transactions to show + no_queries: No queries to show + no_file_samples: No file samples to show + all_transactions: All Transactions + transaction: Transaction + query: Query + file_sample: File Sample + type: Type + path: Path + time: Time + queries: Queries + finished_at: Finished at + ago: ago + view: View + seconds: seconds + milliseconds: ms + general: General + id: ID + time_inclusive: Time (inclusive) + backtrace: Backtrace + application_backtrace: Application Backtrace + full_backtrace: Full Backtrace + origin: Origin + line: line + line_capitalized: Line + copy_to_clipboard: Copy to clipboard + query_plan: Query Plan + events: Events + percent: '%' diff --git a/config/routes.rb b/config/routes.rb index 990a00e3d0..7d8a546a64 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,6 +2,19 @@ require 'sidekiq/web' require 'api/api' Gitlab::Application.routes.draw do + if Gitlab::Sherlock.enabled? + namespace :sherlock do + resources :transactions, only: [:index, :show] do + resources :queries, only: [:show] + resources :file_samples, only: [:show] + + collection do + delete :destroy_all + end + end + end + end + namespace :ci do # CI API Ci::API::API.logger Rails.logger diff --git a/lib/gitlab/sherlock.rb b/lib/gitlab/sherlock.rb new file mode 100644 index 0000000000..c4b35b24ce --- /dev/null +++ b/lib/gitlab/sherlock.rb @@ -0,0 +1,20 @@ +require 'securerandom' +require 'rblineprof' if RUBY_ENGINE == 'ruby' + +module Gitlab + module Sherlock + @collection = Collection.new + + class << self + attr_reader :collection + end + + def self.enabled? + Rails.env.development? && !!ENV['ENABLE_SHERLOCK'] + end + + def self.enable_line_profiler? + RUBY_ENGINE == 'ruby' + end + end +end diff --git a/lib/gitlab/sherlock/collection.rb b/lib/gitlab/sherlock/collection.rb new file mode 100644 index 0000000000..accdc6469b --- /dev/null +++ b/lib/gitlab/sherlock/collection.rb @@ -0,0 +1,42 @@ +module Gitlab + module Sherlock + class Collection + include Enumerable + + def initialize + @transactions = [] + @mutex = Mutex.new + end + + def add(transaction) + synchronize { @transactions << transaction } + end + + alias_method :<<, :add + + def each(&block) + synchronize { @transactions.each(&block) } + end + + def clear + synchronize { @transactions.clear } + end + + def empty? + synchronize { @transactions.empty? } + end + + def find_transaction(id) + find { |trans| trans.id == id } + end + + def newest_first + sort { |a, b| b.finished_at <=> a.finished_at } + end + + def synchronize(&block) + @mutex.synchronize(&block) + end + end + end +end diff --git a/lib/gitlab/sherlock/file_sample.rb b/lib/gitlab/sherlock/file_sample.rb new file mode 100644 index 0000000000..7a220de9ab --- /dev/null +++ b/lib/gitlab/sherlock/file_sample.rb @@ -0,0 +1,27 @@ +module Gitlab + module Sherlock + class FileSample + attr_reader :id, :file, :line_samples, :events, :duration + + def initialize(file, line_samples, duration, events) + @id = SecureRandom.uuid + @file = file + @line_samples = line_samples + @duration = duration + @events = events + end + + def relative_path + @relative_path ||= @file.gsub(/^#{Rails.root.to_s}\/?/, '') + end + + def to_param + @id + end + + def source + @source ||= File.read(@file) + end + end + end +end diff --git a/lib/gitlab/sherlock/line_profiler.rb b/lib/gitlab/sherlock/line_profiler.rb new file mode 100644 index 0000000000..a191b1e646 --- /dev/null +++ b/lib/gitlab/sherlock/line_profiler.rb @@ -0,0 +1,60 @@ +module Gitlab + module Sherlock + class LineProfiler + # The minimum amount of time that has to be spent in a file for it to be + # included in a list of samples. + MINIMUM_DURATION = 10.0 + + def profile(&block) + if RUBY_ENGINE == 'ruby' + profile_mri(&block) + else + raise NotImplementedError, + 'Line profiling is not supported on this platform' + end + end + + def profile_mri + retval = nil + samples = lineprof(/^#{Rails.root.to_s}/) { retval = yield } + + file_samples = aggregate_rblineprof(samples) + + [retval, file_samples] + end + + # Returns an Array of file samples based on the output of rblineprof. + def aggregate_rblineprof(lineprof_stats) + samples = [] + + lineprof_stats.each do |(file, stats)| + source_lines = File.read(file).each_line.to_a + line_samples = [] + + total_duration = microsec_to_millisec(stats[0][0]) + total_events = stats[0][2] + + next if total_duration <= MINIMUM_DURATION + + stats[1..-1].each_with_index do |data, index| + next unless source_lines[index] + + duration = microsec_to_millisec(data[0]) + events = data[2] + + line_samples << LineSample.new(duration, events) + end + + samples << FileSample. + new(file, line_samples, total_duration, total_events) + end + + samples + end + + def microsec_to_millisec(microsec) + microsec / 1000.0 + end + end + end +end diff --git a/lib/gitlab/sherlock/line_sample.rb b/lib/gitlab/sherlock/line_sample.rb new file mode 100644 index 0000000000..38df7a88e4 --- /dev/null +++ b/lib/gitlab/sherlock/line_sample.rb @@ -0,0 +1,20 @@ +module Gitlab + module Sherlock + class LineSample + attr_reader :duration, :events + + def initialize(duration, events) + @duration = duration + @events = events + end + + def percentage_of(total_duration) + (duration.to_f / total_duration) * 100.0 + end + + def majority_of?(total_duration) + percentage_of(total_duration) >= 30 + end + end + end +end diff --git a/lib/gitlab/sherlock/location.rb b/lib/gitlab/sherlock/location.rb new file mode 100644 index 0000000000..8c0b77dce1 --- /dev/null +++ b/lib/gitlab/sherlock/location.rb @@ -0,0 +1,22 @@ +module Gitlab + module Sherlock + class Location + attr_reader :path, :line + + SHERLOCK_DIR = File.dirname(__FILE__) + + def self.from_ruby_location(location) + new(location.path, location.lineno) + end + + def initialize(path, line) + @path = path + @line = line + end + + def application? + @path.start_with?(Rails.root.to_s) && !path.start_with?(SHERLOCK_DIR) + end + end + end +end diff --git a/lib/gitlab/sherlock/middleware.rb b/lib/gitlab/sherlock/middleware.rb new file mode 100644 index 0000000000..fca7be858e --- /dev/null +++ b/lib/gitlab/sherlock/middleware.rb @@ -0,0 +1,36 @@ +module Gitlab + module Sherlock + # Rack middleware used for tracking request metrics. + class Middleware + CONTENT_TYPES = /text\/html|application\/json/i + + IGNORE_PATHS = %r{^/sherlock} + + def initialize(app) + @app = app + end + + def call(env) + if instrument?(env) + call_with_instrumentation(env) + else + @app.call(env) + end + end + + def call_with_instrumentation(env) + trans = Transaction.new(env['REQUEST_METHOD'], env['REQUEST_URI']) + retval = trans.run { @app.call(env) } + + Sherlock.collection.add(trans) + + retval + end + + def instrument?(env) + !!(env['HTTP_ACCEPT'] =~ CONTENT_TYPES && + env['REQUEST_URI'] !~ IGNORE_PATHS) + end + end + end +end diff --git a/lib/gitlab/sherlock/query.rb b/lib/gitlab/sherlock/query.rb new file mode 100644 index 0000000000..af76e6fd2b --- /dev/null +++ b/lib/gitlab/sherlock/query.rb @@ -0,0 +1,96 @@ +module Gitlab + module Sherlock + class Query + attr_reader :id, :query, :started_at, :finished_at, :backtrace + + PREFIX_NEWLINE = / + \s+(FROM + |(LEFT|RIGHT)?INNER\s+JOIN + |(LEFT|RIGHT)?OUTER\s+JOIN + |WHERE + |AND + |GROUP\s+BY + |ORDER\s+BY + |LIMIT + |OFFSET)\s+ + /ix + + def self.new_with_bindings(query, bindings, started_at, finished_at) + bindings.each_with_index do |(column, value), index| + quoted_value = ActiveRecord::Base.connection.quote(value) + + query = query.gsub("$#{index + 1}", quoted_value) + end + + new(query, started_at, finished_at) + end + + def initialize(query, started_at, finished_at) + @id = SecureRandom.uuid + @query = query + @started_at = started_at + @finished_at = finished_at + @backtrace = caller_locations.map do |loc| + Location.from_ruby_location(loc) + end + + unless @query.end_with?(';') + @query += ';' + end + end + + def duration + @duration ||= (@finished_at - @started_at) * 1000.0 + end + + def to_param + @id + end + + def formatted_query + @formatted_query ||= format_sql(@query) + end + + def last_application_frame + @last_application_frame ||= @backtrace.find(&:application?) + end + + def application_backtrace + @application_backtrace ||= @backtrace.select(&:application?) + end + + def explain + unless @explain + ActiveRecord::Base.connection.transaction do + @explain = raw_explain(@query).values.flatten.join("\n") + + # Roll back any queries that mutate data so we don't mess up + # anything when running explain on an INSERT, UPDATE, DELETE, etc. + raise ActiveRecord::Rollback + end + end + + @explain + end + + private + + def raw_explain(query) + if Gitlab::Database.postgresql? + explain = "EXPLAIN ANALYZE #{query};" + else + explain = "EXPLAIN #{query};" + end + + ActiveRecord::Base.connection.execute(explain) + end + + def format_sql(query) + query.each_line. + map { |line| line.strip }. + join("\n"). + gsub(PREFIX_NEWLINE) { "\n#{$1} " } + end + end + end +end diff --git a/lib/gitlab/sherlock/transaction.rb b/lib/gitlab/sherlock/transaction.rb new file mode 100644 index 0000000000..5cb3e86aa4 --- /dev/null +++ b/lib/gitlab/sherlock/transaction.rb @@ -0,0 +1,85 @@ +module Gitlab + module Sherlock + class Transaction + attr_reader :id, :type, :path, :queries, :file_samples, :started_at, + :finished_at + + def initialize(type, path) + @id = SecureRandom.uuid + @type = type + @path = path + @duration = 0 + @queries = [] + @file_samples = [] + @started_at = nil + @finished_at = nil + @thread = Thread.current + end + + def run + @started_at = Time.now + + subscriber = subscribe_to_active_record + + retval = profile_lines { yield } + + @finished_at = Time.now + + ActiveSupport::Notifications.unsubscribe(subscriber) + + retval + end + + def duration + @started_at && @finished_at ? @finished_at - @started_at : 0 + end + + def to_param + @id + end + + def sorted_queries + @queries.sort { |a, b| b.duration <=> a.duration } + end + + def sorted_file_samples + @file_samples.sort { |a, b| b.duration <=> a.duration } + end + + def find_query(id) + @queries.find { |query| query.id == id } + end + + def find_file_sample(id) + @file_samples.find { |sample| sample.id == id } + end + + def track_query(query, bindings, start, finish) + @queries << Query.new_with_bindings(query, bindings, start, finish) + end + + def profile_lines + retval = nil + + if Sherlock.enable_line_profiler? + retval, @file_samples = LineProfiler.new.profile { yield } + else + retval = yield + end + + retval + end + + def subscribe_to_active_record + ActiveSupport::Notifications.subscribe('sql.active_record') do |_, start, finish, _, data| + # In case somebody uses a multi-threaded server locally (e.g. Puma) we + # _only_ want to track queries that originate from the transaction + # thread. + next unless Thread.current == @thread + + track_query(data[:sql].strip, data[:binds], start, finish) + end + end + end + end +end From 265ef867fff165643784640d837579ce4fcc2207 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 5 Nov 2015 18:01:05 +0100 Subject: [PATCH 056/140] Added specs and source documentation for Sherlock --- lib/gitlab/sherlock/collection.rb | 7 + lib/gitlab/sherlock/file_sample.rb | 4 + lib/gitlab/sherlock/line_profiler.rb | 38 +++- lib/gitlab/sherlock/line_sample.rb | 16 ++ lib/gitlab/sherlock/location.rb | 4 + lib/gitlab/sherlock/middleware.rb | 7 +- lib/gitlab/sherlock/query.rb | 24 ++- lib/gitlab/sherlock/transaction.rb | 29 ++- spec/lib/gitlab/sherlock/collection_spec.rb | 82 +++++++++ spec/lib/gitlab/sherlock/file_sample_spec.rb | 54 ++++++ .../lib/gitlab/sherlock/line_profiler_spec.rb | 73 ++++++++ spec/lib/gitlab/sherlock/line_sample_spec.rb | 33 ++++ spec/lib/gitlab/sherlock/location_spec.rb | 40 +++++ spec/lib/gitlab/sherlock/middleware_spec.rb | 79 +++++++++ spec/lib/gitlab/sherlock/query_spec.rb | 113 ++++++++++++ spec/lib/gitlab/sherlock/transaction_spec.rb | 165 ++++++++++++++++++ 16 files changed, 757 insertions(+), 11 deletions(-) create mode 100644 spec/lib/gitlab/sherlock/collection_spec.rb create mode 100644 spec/lib/gitlab/sherlock/file_sample_spec.rb create mode 100644 spec/lib/gitlab/sherlock/line_profiler_spec.rb create mode 100644 spec/lib/gitlab/sherlock/line_sample_spec.rb create mode 100644 spec/lib/gitlab/sherlock/location_spec.rb create mode 100644 spec/lib/gitlab/sherlock/middleware_spec.rb create mode 100644 spec/lib/gitlab/sherlock/query_spec.rb create mode 100644 spec/lib/gitlab/sherlock/transaction_spec.rb diff --git a/lib/gitlab/sherlock/collection.rb b/lib/gitlab/sherlock/collection.rb index accdc6469b..66bd625852 100644 --- a/lib/gitlab/sherlock/collection.rb +++ b/lib/gitlab/sherlock/collection.rb @@ -1,5 +1,10 @@ module Gitlab module Sherlock + # A collection of transactions recorded by Sherlock. + # + # Method calls for this class are synchronized using a mutex to allow + # sharing of a single Collection instance between threads (e.g. when using + # Puma as a webserver). class Collection include Enumerable @@ -34,6 +39,8 @@ module Gitlab sort { |a, b| b.finished_at <=> a.finished_at } end + private + def synchronize(&block) @mutex.synchronize(&block) end diff --git a/lib/gitlab/sherlock/file_sample.rb b/lib/gitlab/sherlock/file_sample.rb index 7a220de9ab..8a3e1a5e5b 100644 --- a/lib/gitlab/sherlock/file_sample.rb +++ b/lib/gitlab/sherlock/file_sample.rb @@ -3,6 +3,10 @@ module Gitlab class FileSample attr_reader :id, :file, :line_samples, :events, :duration + # file - The full path to the file this sample belongs to. + # line_samples - An array of LineSample objects. + # duration - The total execution time in milliseconds. + # events - The total amount of events. def initialize(file, line_samples, duration, events) @id = SecureRandom.uuid @file = file diff --git a/lib/gitlab/sherlock/line_profiler.rb b/lib/gitlab/sherlock/line_profiler.rb index a191b1e646..152749dcc3 100644 --- a/lib/gitlab/sherlock/line_profiler.rb +++ b/lib/gitlab/sherlock/line_profiler.rb @@ -1,12 +1,36 @@ module Gitlab module Sherlock + # Class for profiling code on a per line basis. + # + # The LineProfiler class can be used to profile code on per line basis + # without littering your code with Ruby implementation specific profiling + # methods. + # + # This profiler only includes samples taking longer than a given threshold + # and those that occur in the actual application (e.g. files from Gems are + # ignored). class LineProfiler # The minimum amount of time that has to be spent in a file for it to be # included in a list of samples. MINIMUM_DURATION = 10.0 + # Profiles the given block. + # + # Example: + # + # profiler = LineProfiler.new + # + # retval, samples = profiler.profile do + # "cats are amazing" + # end + # + # retval # => "cats are amazing" + # samples # => [#, ...] + # + # Returns an Array containing the block's return value and an Array of + # FileSample objects. def profile(&block) - if RUBY_ENGINE == 'ruby' + if mri? profile_mri(&block) else raise NotImplementedError, @@ -14,6 +38,7 @@ module Gitlab end end + # Profiles the given block using rblineprof (MRI only). def profile_mri retval = nil samples = lineprof(/^#{Rails.root.to_s}/) { retval = yield } @@ -24,6 +49,11 @@ module Gitlab end # Returns an Array of file samples based on the output of rblineprof. + # + # lineprof_stats - A Hash containing rblineprof statistics on a per file + # basis. + # + # Returns an Array of FileSample objects. def aggregate_rblineprof(lineprof_stats) samples = [] @@ -52,9 +82,15 @@ module Gitlab samples end + private + def microsec_to_millisec(microsec) microsec / 1000.0 end + + def mri? + RUBY_ENGINE == 'ruby' + end end end end diff --git a/lib/gitlab/sherlock/line_sample.rb b/lib/gitlab/sherlock/line_sample.rb index 38df7a88e4..eb1948eb6d 100644 --- a/lib/gitlab/sherlock/line_sample.rb +++ b/lib/gitlab/sherlock/line_sample.rb @@ -3,15 +3,31 @@ module Gitlab class LineSample attr_reader :duration, :events + # duration - The execution time in milliseconds. + # events - The amount of events. def initialize(duration, events) @duration = duration @events = events end + # Returns the sample duration percentage relative to the given duration. + # + # Example: + # + # sample.duration # => 150 + # sample.percentage_of(1500) # => 10.0 + # + # total_duration - The total duration to compare with. + # + # Returns a float def percentage_of(total_duration) (duration.to_f / total_duration) * 100.0 end + # Returns true if the current sample takes up the majority of the given + # duration. + # + # total_duration - The total duration to compare with. def majority_of?(total_duration) percentage_of(total_duration) >= 30 end diff --git a/lib/gitlab/sherlock/location.rb b/lib/gitlab/sherlock/location.rb index 8c0b77dce1..5ac265618a 100644 --- a/lib/gitlab/sherlock/location.rb +++ b/lib/gitlab/sherlock/location.rb @@ -5,15 +5,19 @@ module Gitlab SHERLOCK_DIR = File.dirname(__FILE__) + # Creates a new Location from a `Thread::Backtrace::Location`. def self.from_ruby_location(location) new(location.path, location.lineno) end + # path - The full path of the frame as a String. + # line - The line number of the frame as a Fixnum. def initialize(path, line) @path = path @line = line end + # Returns true if the current frame originated from the application. def application? @path.start_with?(Rails.root.to_s) && !path.start_with?(SHERLOCK_DIR) end diff --git a/lib/gitlab/sherlock/middleware.rb b/lib/gitlab/sherlock/middleware.rb index fca7be858e..687332fc5f 100644 --- a/lib/gitlab/sherlock/middleware.rb +++ b/lib/gitlab/sherlock/middleware.rb @@ -10,6 +10,7 @@ module Gitlab @app = app end + # env - A Hash containing Rack environment details. def call(env) if instrument?(env) call_with_instrumentation(env) @@ -19,7 +20,7 @@ module Gitlab end def call_with_instrumentation(env) - trans = Transaction.new(env['REQUEST_METHOD'], env['REQUEST_URI']) + trans = transaction_from_env(env) retval = trans.run { @app.call(env) } Sherlock.collection.add(trans) @@ -31,6 +32,10 @@ module Gitlab !!(env['HTTP_ACCEPT'] =~ CONTENT_TYPES && env['REQUEST_URI'] !~ IGNORE_PATHS) end + + def transaction_from_env(env) + Transaction.new(env['REQUEST_METHOD'], env['REQUEST_URI']) + end end end end diff --git a/lib/gitlab/sherlock/query.rb b/lib/gitlab/sherlock/query.rb index af76e6fd2b..4917c4ae2a 100644 --- a/lib/gitlab/sherlock/query.rb +++ b/lib/gitlab/sherlock/query.rb @@ -3,6 +3,7 @@ module Gitlab class Query attr_reader :id, :query, :started_at, :finished_at, :backtrace + # SQL identifiers that should be prefixed with newlines. PREFIX_NEWLINE = / \s+(FROM |(LEFT|RIGHT)?INNER\s+JOIN @@ -12,11 +13,20 @@ module Gitlab |GROUP\s+BY |ORDER\s+BY |LIMIT - |OFFSET)\s+ - /ix + |OFFSET)\s+/ix # Vim indent breaks when this is on a newline :< + # Creates a new Query using a String and a separate Array of bindings. + # + # query - A String containing a SQL query, optionally with numeric + # placeholders (`$1`, `$2`, etc). + # + # bindings - An Array of ActiveRecord columns and their values. + # started_at - The start time of the query as a Time-like object. + # finished_at - The completion time of the query as a Time-like object. + # + # Returns a new Query object. def self.new_with_bindings(query, bindings, started_at, finished_at) - bindings.each_with_index do |(column, value), index| + bindings.each_with_index do |(_, value), index| quoted_value = ActiveRecord::Base.connection.quote(value) query = query.gsub("$#{index + 1}", quoted_value) @@ -25,6 +35,9 @@ module Gitlab new(query, started_at, finished_at) end + # query - The SQL query as a String (without placeholders). + # started_at - The start time of the query as a Time-like object. + # finished_at - The completion time of the query as a Time-like object. def initialize(query, started_at, finished_at) @id = SecureRandom.uuid @query = query @@ -39,6 +52,7 @@ module Gitlab end end + # Returns the query duration in milliseconds. def duration @duration ||= (@finished_at - @started_at) * 1000.0 end @@ -47,18 +61,22 @@ module Gitlab @id end + # Returns a human readable version of the query. def formatted_query @formatted_query ||= format_sql(@query) end + # Returns the last application frame of the backtrace. def last_application_frame @last_application_frame ||= @backtrace.find(&:application?) end + # Returns an Array of application frames (excluding Gems and the likes). def application_backtrace @application_backtrace ||= @backtrace.select(&:application?) end + # Returns the query plan as a String. def explain unless @explain ActiveRecord::Base.connection.transaction do diff --git a/lib/gitlab/sherlock/transaction.rb b/lib/gitlab/sherlock/transaction.rb index 5cb3e86aa4..4641f15ee3 100644 --- a/lib/gitlab/sherlock/transaction.rb +++ b/lib/gitlab/sherlock/transaction.rb @@ -4,11 +4,12 @@ module Gitlab attr_reader :id, :type, :path, :queries, :file_samples, :started_at, :finished_at + # type - The type of transaction (e.g. "GET", "POST", etc) + # path - The path of the transaction (e.g. the HTTP request path) def initialize(type, path) @id = SecureRandom.uuid @type = type @path = path - @duration = 0 @queries = [] @file_samples = [] @started_at = nil @@ -16,6 +17,7 @@ module Gitlab @thread = Thread.current end + # Runs the transaction and returns the block's return value. def run @started_at = Time.now @@ -30,34 +32,43 @@ module Gitlab retval end + # Returns the duration in seconds. def duration - @started_at && @finished_at ? @finished_at - @started_at : 0 + @duration ||= started_at && finished_at ? finished_at - started_at : 0 end def to_param @id end + # Returns the queries sorted in descending order by their durations. def sorted_queries @queries.sort { |a, b| b.duration <=> a.duration } end + # Returns the file samples sorted in descending order by their durations. def sorted_file_samples @file_samples.sort { |a, b| b.duration <=> a.duration } end + # Finds a query by the given ID. + # + # id - The query ID as a String. + # + # Returns a Query object if one could be found, nil otherwise. def find_query(id) @queries.find { |query| query.id == id } end + # Finds a file sample by the given ID. + # + # id - The query ID as a String. + # + # Returns a FileSample object if one could be found, nil otherwise. def find_file_sample(id) @file_samples.find { |sample| sample.id == id } end - def track_query(query, bindings, start, finish) - @queries << Query.new_with_bindings(query, bindings, start, finish) - end - def profile_lines retval = nil @@ -70,6 +81,12 @@ module Gitlab retval end + private + + def track_query(query, bindings, start, finish) + @queries << Query.new_with_bindings(query, bindings, start, finish) + end + def subscribe_to_active_record ActiveSupport::Notifications.subscribe('sql.active_record') do |_, start, finish, _, data| # In case somebody uses a multi-threaded server locally (e.g. Puma) we diff --git a/spec/lib/gitlab/sherlock/collection_spec.rb b/spec/lib/gitlab/sherlock/collection_spec.rb new file mode 100644 index 0000000000..a8a9d6fc7b --- /dev/null +++ b/spec/lib/gitlab/sherlock/collection_spec.rb @@ -0,0 +1,82 @@ +require 'spec_helper' + +describe Gitlab::Sherlock::Collection do + let(:collection) { described_class.new } + + let(:transaction) do + Gitlab::Sherlock::Transaction.new('POST', '/cat_pictures') + end + + describe '#add' do + it 'adds a new transaction' do + collection.add(transaction) + + expect(collection).to_not be_empty + end + + it 'is aliased as <<' do + collection << transaction + + expect(collection).to_not be_empty + end + end + + describe '#each' do + it 'iterates over every transaction' do + collection.add(transaction) + + expect { |b| collection.each(&b) }.to yield_with_args(transaction) + end + end + + describe '#clear' do + it 'removes all transactions' do + collection.add(transaction) + + collection.clear + + expect(collection).to be_empty + end + end + + describe '#empty?' do + it 'returns true for an empty collection' do + expect(collection).to be_empty + end + + it 'returns false for a collection with a transaction' do + collection.add(transaction) + + expect(collection).to_not be_empty + end + end + + describe '#find_transaction' do + it 'returns the transaction for the given ID' do + collection.add(transaction) + + expect(collection.find_transaction(transaction.id)).to eq(transaction) + end + + it 'returns nil when no transaction could be found' do + collection.add(transaction) + + expect(collection.find_transaction('cats')).to be_nil + end + end + + describe '#newest_first' do + it 'returns transactions sorted from new to old' do + trans1 = Gitlab::Sherlock::Transaction.new('POST', '/cat_pictures') + trans2 = Gitlab::Sherlock::Transaction.new('POST', '/more_cat_pictures') + + allow(trans1).to receive(:finished_at).and_return(Time.utc(2015, 1, 1)) + allow(trans2).to receive(:finished_at).and_return(Time.utc(2015, 1, 2)) + + collection.add(trans1) + collection.add(trans2) + + expect(collection.newest_first).to eq([trans2, trans1]) + end + end +end diff --git a/spec/lib/gitlab/sherlock/file_sample_spec.rb b/spec/lib/gitlab/sherlock/file_sample_spec.rb new file mode 100644 index 0000000000..f05a59f56f --- /dev/null +++ b/spec/lib/gitlab/sherlock/file_sample_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe Gitlab::Sherlock::FileSample do + let(:sample) { described_class.new(__FILE__, [], 150.4, 2) } + + describe '#id' do + it 'returns the ID' do + expect(sample.id).to be_an_instance_of(String) + end + end + + describe '#file' do + it 'returns the file path' do + expect(sample.file).to eq(__FILE__) + end + end + + describe '#line_samples' do + it 'returns the line samples' do + expect(sample.line_samples).to eq([]) + end + end + + describe '#events' do + it 'returns the total number of events' do + expect(sample.events).to eq(2) + end + end + + describe '#duration' do + it 'returns the total execution time' do + expect(sample.duration).to eq(150.4) + end + end + + describe '#relative_path' do + it 'returns the relative path' do + expect(sample.relative_path). + to eq('spec/lib/gitlab/sherlock/file_sample_spec.rb') + end + end + + describe '#to_param' do + it 'returns the sample ID' do + expect(sample.to_param).to eq(sample.id) + end + end + + describe '#source' do + it 'returns the contents of the file' do + expect(sample.source).to eq(File.read(__FILE__)) + end + end +end diff --git a/spec/lib/gitlab/sherlock/line_profiler_spec.rb b/spec/lib/gitlab/sherlock/line_profiler_spec.rb new file mode 100644 index 0000000000..8f2e129971 --- /dev/null +++ b/spec/lib/gitlab/sherlock/line_profiler_spec.rb @@ -0,0 +1,73 @@ +require 'spec_helper' + +describe Gitlab::Sherlock::LineProfiler do + let(:profiler) { described_class.new } + + describe '#profile' do + it 'runs the profiler when using MRI' do + allow(profiler).to receive(:mri?).and_return(true) + allow(profiler).to receive(:profile_mri) + + profiler.profile { 'cats' } + end + + it 'raises NotImplementedError when profiling an unsupported platform' do + allow(profiler).to receive(:mri?).and_return(false) + + expect { profiler.profile { 'cats' } }.to raise_error(NotImplementedError) + end + end + + describe '#profile_mri' do + it 'returns an Array containing the return value and profiling samples' do + allow(profiler).to receive(:lineprof). + and_yield. + and_return({ __FILE__ => [[0, 0, 0, 0]] }) + + retval, samples = profiler.profile_mri { 42 } + + expect(retval).to eq(42) + expect(samples).to eq([]) + end + end + + describe '#aggregate_rblineprof' do + let(:raw_samples) do + { __FILE__ => [[30000, 30000, 5, 0], [15000, 15000, 4, 0]] } + end + + it 'returns an Array of FileSample objects' do + samples = profiler.aggregate_rblineprof(raw_samples) + + expect(samples).to be_an_instance_of(Array) + expect(samples[0]).to be_an_instance_of(Gitlab::Sherlock::FileSample) + end + + describe 'the first FileSample object' do + let(:file_sample) do + profiler.aggregate_rblineprof(raw_samples)[0] + end + + it 'uses the correct file path' do + expect(file_sample.file).to eq(__FILE__) + end + + it 'contains a list of line samples' do + line_sample = file_sample.line_samples[0] + + expect(line_sample).to be_an_instance_of(Gitlab::Sherlock::LineSample) + + expect(line_sample.duration).to eq(15.0) + expect(line_sample.events).to eq(4) + end + + it 'contains the total file execution time' do + expect(file_sample.duration).to eq(30.0) + end + + it 'contains the total amount of file events' do + expect(file_sample.events).to eq(5) + end + end + end +end diff --git a/spec/lib/gitlab/sherlock/line_sample_spec.rb b/spec/lib/gitlab/sherlock/line_sample_spec.rb new file mode 100644 index 0000000000..5f02f6a321 --- /dev/null +++ b/spec/lib/gitlab/sherlock/line_sample_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe Gitlab::Sherlock::LineSample do + let(:sample) { described_class.new(150.0, 4) } + + describe '#duration' do + it 'returns the duration' do + expect(sample.duration).to eq(150.0) + end + end + + describe '#events' do + it 'returns the amount of events' do + expect(sample.events).to eq(4) + end + end + + describe '#percentage_of' do + it 'returns the percentage of 1500.0' do + expect(sample.percentage_of(1500.0)).to be_within(0.1).of(10.0) + end + end + + describe '#majority_of' do + it 'returns true if the sample takes up the majority of the given duration' do + expect(sample.majority_of?(500.0)).to eq(true) + end + + it "returns false if the sample doesn't take up the majority of the given duration" do + expect(sample.majority_of?(1500.0)).to eq(false) + end + end +end diff --git a/spec/lib/gitlab/sherlock/location_spec.rb b/spec/lib/gitlab/sherlock/location_spec.rb new file mode 100644 index 0000000000..b295a624b3 --- /dev/null +++ b/spec/lib/gitlab/sherlock/location_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe Gitlab::Sherlock::Location do + let(:location) { described_class.new(__FILE__, 1) } + + describe 'from_ruby_location' do + it 'creates a Location from a Thread::Backtrace::Location' do + input = caller_locations[0] + output = described_class.from_ruby_location(input) + + expect(output).to be_an_instance_of(described_class) + expect(output.path).to eq(input.path) + expect(output.line).to eq(input.lineno) + end + end + + describe '#path' do + it 'returns the file path' do + expect(location.path).to eq(__FILE__) + end + end + + describe '#line' do + it 'returns the line number' do + expect(location.line).to eq(1) + end + end + + describe '#application?' do + it 'returns true for an application frame' do + expect(location.application?).to eq(true) + end + + it 'returns false for a non application frame' do + loc = described_class.new('/tmp/cats.rb', 1) + + expect(loc.application?).to eq(false) + end + end +end diff --git a/spec/lib/gitlab/sherlock/middleware_spec.rb b/spec/lib/gitlab/sherlock/middleware_spec.rb new file mode 100644 index 0000000000..aa74fc53a7 --- /dev/null +++ b/spec/lib/gitlab/sherlock/middleware_spec.rb @@ -0,0 +1,79 @@ +require 'spec_helper' + +describe Gitlab::Sherlock::Middleware do + let(:app) { double(:app) } + let(:middleware) { described_class.new(app) } + + describe '#call' do + describe 'when instrumentation is enabled' do + it 'instruments a request' do + allow(middleware).to receive(:instrument?).and_return(true) + allow(middleware).to receive(:call_with_instrumentation) + + middleware.call({}) + end + end + + describe 'when instrumentation is disabled' do + it "doesn't instrument a request" do + allow(middleware).to receive(:instrument).and_return(false) + allow(app).to receive(:call) + + middleware.call({}) + end + end + end + + describe '#call_with_instrumentation' do + it 'instruments a request' do + trans = double(:transaction) + retval = 'cats are amazing' + env = {} + + allow(app).to receive(:call).with(env).and_return(retval) + allow(middleware).to receive(:transaction_from_env).and_return(trans) + allow(trans).to receive(:run).and_yield.and_return(retval) + allow(Gitlab::Sherlock.collection).to receive(:add).with(trans) + + middleware.call_with_instrumentation(env) + end + end + + describe '#instrument?' do + it 'returns false for a text/css request' do + env = { 'HTTP_ACCEPT' => 'text/css', 'REQUEST_URI' => '/' } + + expect(middleware.instrument?(env)).to eq(false) + end + + it 'returns false for a request to a Sherlock route' do + env = { + 'HTTP_ACCEPT' => 'text/html', + 'REQUEST_URI' => '/sherlock/transactions' + } + + expect(middleware.instrument?(env)).to eq(false) + end + + it 'returns true for a request that should be instrumented' do + env = { + 'HTTP_ACCEPT' => 'text/html', + 'REQUEST_URI' => '/cats' + } + + expect(middleware.instrument?(env)).to eq(true) + end + end + + describe '#transaction_from_env' do + it 'returns a Transaction' do + env = { + 'HTTP_ACCEPT' => 'text/html', + 'REQUEST_URI' => '/cats' + } + + expect(middleware.transaction_from_env(env)). + to be_an_instance_of(Gitlab::Sherlock::Transaction) + end + end +end diff --git a/spec/lib/gitlab/sherlock/query_spec.rb b/spec/lib/gitlab/sherlock/query_spec.rb new file mode 100644 index 0000000000..b15a125a40 --- /dev/null +++ b/spec/lib/gitlab/sherlock/query_spec.rb @@ -0,0 +1,113 @@ +require 'spec_helper' + +describe Gitlab::Sherlock::Query do + let(:started_at) { Time.utc(2015, 1, 1) } + let(:finished_at) { started_at + 5 } + + let(:query) do + described_class.new('SELECT COUNT(*) FROM users', started_at, finished_at) + end + + describe 'new_with_bindings' do + it 'returns a Query' do + sql = 'SELECT COUNT(*) FROM users WHERE id = $1' + bindings = [[double(:column), 10]] + + query = described_class. + new_with_bindings(sql, bindings, started_at, finished_at) + + expect(query.query).to eq('SELECT COUNT(*) FROM users WHERE id = 10;') + end + end + + describe '#id' do + it 'returns a String' do + expect(query.id).to be_an_instance_of(String) + end + end + + describe '#query' do + it 'returns the query with a trailing semi-colon' do + expect(query.query).to eq('SELECT COUNT(*) FROM users;') + end + end + + describe '#started_at' do + it 'returns the start time' do + expect(query.started_at).to eq(started_at) + end + end + + describe '#finished_at' do + it 'returns the completion time' do + expect(query.finished_at).to eq(finished_at) + end + end + + describe '#backtrace' do + it 'returns the backtrace' do + expect(query.backtrace).to be_an_instance_of(Array) + end + end + + describe '#duration' do + it 'returns the duration in milliseconds' do + expect(query.duration).to be_within(0.1).of(5000.0) + end + end + + describe '#to_param' do + it 'returns the query ID' do + expect(query.to_param).to eq(query.id) + end + end + + describe '#formatted_query' do + it 'returns a formatted version of the query' do + expect(query.formatted_query).to eq(<<-EOF.strip) +SELECT COUNT(*) +FROM users; + EOF + end + end + + describe '#last_application_frame' do + it 'returns the last application frame' do + frame = query.last_application_frame + + expect(frame).to be_an_instance_of(Gitlab::Sherlock::Location) + expect(frame.path).to eq(__FILE__) + end + end + + describe '#application_backtrace' do + it 'returns an Array of application frames' do + frames = query.application_backtrace + + expect(frames).to be_an_instance_of(Array) + expect(frames).to_not be_empty + + frames.each do |frame| + expect(frame.path).to start_with(Rails.root.to_s) + end + end + end + + describe '#explain' do + it 'returns the query plan as a String' do + lines = [ + ['Aggregate (cost=123 rows=1)'], + [' -> Index Only Scan using index_cats_are_amazing'] + ] + + result = double(:result, :values => lines) + + allow(query).to receive(:raw_explain).and_return(result) + + expect(query.explain).to eq(<<-EOF.strip) +Aggregate (cost=123 rows=1) + -> Index Only Scan using index_cats_are_amazing + EOF + end + end +end diff --git a/spec/lib/gitlab/sherlock/transaction_spec.rb b/spec/lib/gitlab/sherlock/transaction_spec.rb new file mode 100644 index 0000000000..bb4ff42e6e --- /dev/null +++ b/spec/lib/gitlab/sherlock/transaction_spec.rb @@ -0,0 +1,165 @@ +require 'spec_helper' + +describe Gitlab::Sherlock::Transaction do + let(:transaction) { described_class.new('POST', '/cat_pictures') } + + describe '#id' do + it 'returns the transaction ID' do + expect(transaction.id).to be_an_instance_of(String) + end + end + + describe '#type' do + it 'returns the type' do + expect(transaction.type).to eq('POST') + end + end + + describe '#path' do + it 'returns the path' do + expect(transaction.path).to eq('/cat_pictures') + end + end + + describe '#queries' do + it 'returns an Array of queries' do + expect(transaction.queries).to be_an_instance_of(Array) + end + end + + describe '#file_samples' do + it 'returns an Array of file samples' do + expect(transaction.file_samples).to be_an_instance_of(Array) + end + end + + describe '#started_at' do + it 'returns the start time' do + allow(transaction).to receive(:profile_lines).and_yield + + transaction.run { 'cats are amazing' } + + expect(transaction.started_at).to be_an_instance_of(Time) + end + end + + describe '#finished_at' do + it 'returns the completion time' do + allow(transaction).to receive(:profile_lines).and_yield + + transaction.run { 'cats are amazing' } + + expect(transaction.finished_at).to be_an_instance_of(Time) + end + end + + describe '#run' do + it 'runs the transaction' do + allow(transaction).to receive(:profile_lines).and_yield + + retval = transaction.run { 'cats are amazing' } + + expect(retval).to eq('cats are amazing') + end + end + + describe '#duration' do + it 'returns the duration in seconds' do + start_time = Time.now + + allow(transaction).to receive(:started_at).and_return(start_time) + allow(transaction).to receive(:finished_at).and_return(start_time + 5) + + expect(transaction.duration).to be_within(0.1).of(5.0) + end + end + + describe '#to_param' do + it 'returns the transaction ID' do + expect(transaction.to_param).to eq(transaction.id) + end + end + + describe '#sorted_queries' do + it 'returns the queries in descending order' do + start_time = Time.now + + query1 = Gitlab::Sherlock::Query.new('SELECT 1', start_time, start_time) + + query2 = Gitlab::Sherlock::Query. + new('SELECT 2', start_time, start_time + 5) + + transaction.queries << query1 + transaction.queries << query2 + + expect(transaction.sorted_queries).to eq([query2, query1]) + end + end + + describe '#sorted_file_samples' do + it 'returns the file samples in descending order' do + sample1 = Gitlab::Sherlock::FileSample.new(__FILE__, [], 10.0, 1) + sample2 = Gitlab::Sherlock::FileSample.new(__FILE__, [], 15.0, 1) + + transaction.file_samples << sample1 + transaction.file_samples << sample2 + + expect(transaction.sorted_file_samples).to eq([sample2, sample1]) + end + end + + describe '#find_query' do + it 'returns a Query when found' do + query = Gitlab::Sherlock::Query.new('SELECT 1', Time.now, Time.now) + + transaction.queries << query + + expect(transaction.find_query(query.id)).to eq(query) + end + + it 'returns nil when no query could be found' do + expect(transaction.find_query('cats')).to be_nil + end + end + + describe '#find_file_sample' do + it 'returns a FileSample when found' do + sample = Gitlab::Sherlock::FileSample.new(__FILE__, [], 10.0, 1) + + transaction.file_samples << sample + + expect(transaction.find_file_sample(sample.id)).to eq(sample) + end + + it 'returns nil when no file sample could be found' do + expect(transaction.find_file_sample('cats')).to be_nil + end + end + + describe '#profile_lines' do + describe 'when line profiling is enabled' do + it 'yields the block using the line profiler' do + allow(Gitlab::Sherlock).to receive(:enable_line_profiler?). + and_return(true) + + allow_any_instance_of(Gitlab::Sherlock::LineProfiler). + to receive(:profile).and_return('cats are amazing', []) + + retval = transaction.profile_lines { 'cats are amazing' } + + expect(retval).to eq('cats are amazing') + end + end + + describe 'when line profiling is disabled' do + it 'yields the block' do + allow(Gitlab::Sherlock).to receive(:enable_line_profiler?). + and_return(false) + + retval = transaction.profile_lines { 'cats are amazing' } + + expect(retval).to eq('cats are amazing') + end + end + end +end From 126a2428cd7fb5b2766da10373f72a370aa68ba5 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 5 Nov 2015 18:06:05 +0100 Subject: [PATCH 057/140] Updated profiling guides for Sherlock --- doc/development/profiling.md | 45 +++++++----------------------------- 1 file changed, 8 insertions(+), 37 deletions(-) diff --git a/doc/development/profiling.md b/doc/development/profiling.md index 80c86ef921..e244ad4e88 100644 --- a/doc/development/profiling.md +++ b/doc/development/profiling.md @@ -4,11 +4,15 @@ To make it easier to track down performance problems GitLab comes with a set of profiling tools, some of these are available by default while others need to be explicitly enabled. -## rack-mini-profiler +## Sherlock -This Gem is enabled by default in development only. It allows you to see the -timings of the various components that made up a web request (e.g. the SQL -queries executed and their execution timings). +Sherlock is a custom profiling tool built into GitLab. Sherlock is _only_ +available when running GitLab in development mode _and_ when setting the +environment variable `ENABLE_SHERLOCK` to a non empty value. For example: + + ENABLE_SHERLOCK=1 bundle exec rails s + +Recorded transactions can be found by navigating to `/sherlock/transactions`. ## Bullet @@ -21,36 +25,3 @@ starting GitLab. For example: Bullet will log query problems to both the Rails log as well as the Chrome console. - -## ActiveRecord Query Trace - -This Gem adds backtraces for every ActiveRecord query in the Rails console. This -can be useful to track down where a query was executed. Because this Gem adds -quite a bit of noise (5-10 extra lines per ActiveRecord query) it's disabled by -default. To use this Gem you'll need to set `ENABLE_QUERY_TRACE` to a non empty -file before starting GitLab. For example: - - ENABLE_QUERY_TRACE=true bundle exec rails s - -## rack-lineprof - -This is a Gem that can trace the execution time of code on a per line basis. -Because this Gem can add quite a bit of overhead it's disabled by default. To -enable it, set the environment variable `ENABLE_LINEPROF` to a non-empty value. -For example: - - ENABLE_LINEPROF=true bundle exec rails s - -Once enabled you'll need to add a query string parameter to a request to -actually profile code execution. The name of the parameter is `lineprof` and -should be set to a regular expression (minus the starting/ending slash) used to -select what files to profile. To profile all files containing "foo" somewhere in -the path you'd use the following parameter: - - ?lineprof=foo - -Or when filtering for files containing "foo" and "bar" in their path: - - ?lineprof=foo|bar - -Once set the profiling output will be displayed in your terminal. From db46d49c0ed7e34a9679b0475480306d92b0de31 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 9 Nov 2015 11:21:50 +0100 Subject: [PATCH 058/140] Fixed Hash key style in Sherlock::Query spec --- spec/lib/gitlab/sherlock/query_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/gitlab/sherlock/query_spec.rb b/spec/lib/gitlab/sherlock/query_spec.rb index b15a125a40..a9afef5dc1 100644 --- a/spec/lib/gitlab/sherlock/query_spec.rb +++ b/spec/lib/gitlab/sherlock/query_spec.rb @@ -100,7 +100,7 @@ FROM users; [' -> Index Only Scan using index_cats_are_amazing'] ] - result = double(:result, :values => lines) + result = double(:result, values: lines) allow(query).to receive(:raw_explain).and_return(result) From cdaa97443e89a08d857a244e6e8ab0235db9746d Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 9 Nov 2015 11:30:57 +0100 Subject: [PATCH 059/140] Added navigation link to Sherlock --- app/views/layouts/header/_default.html.haml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index c31b1cbe9a..c08a7b8074 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -21,6 +21,11 @@ %li = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do = icon('plus fw') + - if Gitlab::Sherlock.enabled? + %li + = link_to sherlock_transactions_path, title: 'Sherlock Transactions', + data: {toggle: 'tooltip', placement: 'bottom'} do + = icon('tachometer fw') %li = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom'} do = icon('sign-out') From 7b5fd8742e6112491f61f27dcca2d8e441cc33a1 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 9 Nov 2015 12:36:01 +0100 Subject: [PATCH 060/140] Track the amount of times views are rendered --- .../transactions/_file_samples.html.haml | 2 + config/locales/sherlock.en.yml | 1 + lib/gitlab/sherlock/transaction.rb | 57 ++++++++++++++----- spec/lib/gitlab/sherlock/transaction_spec.rb | 57 +++++++++++++++++++ 4 files changed, 103 insertions(+), 14 deletions(-) diff --git a/app/views/sherlock/transactions/_file_samples.html.haml b/app/views/sherlock/transactions/_file_samples.html.haml index 0afdbc8dff..4349c9b7ac 100644 --- a/app/views/sherlock/transactions/_file_samples.html.haml +++ b/app/views/sherlock/transactions/_file_samples.html.haml @@ -7,6 +7,7 @@ %thead %tr %th= t('sherlock.time_inclusive') + %th= t('sherlock.count') %th= t('sherlock.path') %th %tbody @@ -15,6 +16,7 @@ %td = sample.duration.round(2) = t('sherlock.milliseconds') + %td= @transaction.view_counts.fetch(sample.file, 1) %td= sample.relative_path %td = link_to(t('sherlock.view'), diff --git a/config/locales/sherlock.en.yml b/config/locales/sherlock.en.yml index 5c146b172b..683b09dc32 100644 --- a/config/locales/sherlock.en.yml +++ b/config/locales/sherlock.en.yml @@ -34,3 +34,4 @@ en: query_plan: Query Plan events: Events percent: '%' + count: Count diff --git a/lib/gitlab/sherlock/transaction.rb b/lib/gitlab/sherlock/transaction.rb index 4641f15ee3..d87a4c9bb4 100644 --- a/lib/gitlab/sherlock/transaction.rb +++ b/lib/gitlab/sherlock/transaction.rb @@ -2,7 +2,7 @@ module Gitlab module Sherlock class Transaction attr_reader :id, :type, :path, :queries, :file_samples, :started_at, - :finished_at + :finished_at, :view_counts # type - The type of transaction (e.g. "GET", "POST", etc) # path - The path of the transaction (e.g. the HTTP request path) @@ -15,20 +15,19 @@ module Gitlab @started_at = nil @finished_at = nil @thread = Thread.current + @view_counts = Hash.new(0) end # Runs the transaction and returns the block's return value. def run @started_at = Time.now - subscriber = subscribe_to_active_record - - retval = profile_lines { yield } + retval = with_subscriptions do + profile_lines { yield } + end @finished_at = Time.now - ActiveSupport::Notifications.unsubscribe(subscriber) - retval end @@ -81,21 +80,51 @@ module Gitlab retval end + def subscribe_to_active_record + ActiveSupport::Notifications.subscribe('sql.active_record') do |_, start, finish, _, data| + next unless same_thread? + + track_query(data[:sql].strip, data[:binds], start, finish) + end + end + + def subscribe_to_action_view + regex = /render_(template|partial)\.action_view/ + + ActiveSupport::Notifications.subscribe(regex) do |_, start, finish, _, data| + next unless same_thread? + + track_view(data[:identifier]) + end + end + private def track_query(query, bindings, start, finish) @queries << Query.new_with_bindings(query, bindings, start, finish) end - def subscribe_to_active_record - ActiveSupport::Notifications.subscribe('sql.active_record') do |_, start, finish, _, data| - # In case somebody uses a multi-threaded server locally (e.g. Puma) we - # _only_ want to track queries that originate from the transaction - # thread. - next unless Thread.current == @thread + def track_view(path) + @view_counts[path] += 1 + end - track_query(data[:sql].strip, data[:binds], start, finish) - end + def with_subscriptions + ar_subscriber = subscribe_to_active_record + av_subscriber = subscribe_to_action_view + + retval = yield + + ActiveSupport::Notifications.unsubscribe(ar_subscriber) + ActiveSupport::Notifications.unsubscribe(av_subscriber) + + retval + end + + # In case somebody uses a multi-threaded server locally (e.g. Puma) we + # _only_ want to track notifications that originate from the transaction + # thread. + def same_thread? + Thread.current == @thread end end end diff --git a/spec/lib/gitlab/sherlock/transaction_spec.rb b/spec/lib/gitlab/sherlock/transaction_spec.rb index bb4ff42e6e..bb49fb65cf 100644 --- a/spec/lib/gitlab/sherlock/transaction_spec.rb +++ b/spec/lib/gitlab/sherlock/transaction_spec.rb @@ -53,6 +53,16 @@ describe Gitlab::Sherlock::Transaction do end end + describe '#view_counts' do + it 'returns a Hash' do + expect(transaction.view_counts).to be_an_instance_of(Hash) + end + + it 'sets the default value of a key to 0' do + expect(transaction.view_counts['cats.rb']).to be_zero + end + end + describe '#run' do it 'runs the transaction' do allow(transaction).to receive(:profile_lines).and_yield @@ -162,4 +172,51 @@ describe Gitlab::Sherlock::Transaction do end end end + + describe '#subscribe_to_active_record' do + let(:subscription) { transaction.subscribe_to_active_record } + let(:time) { Time.now } + let(:query_data) { { sql: 'SELECT 1', binds: [] } } + + after do + ActiveSupport::Notifications.unsubscribe(subscription) + end + + it 'tracks executed queries' do + expect(transaction).to receive(:track_query). + with('SELECT 1', [], time, time) + + subscription.publish('test', time, time, nil, query_data) + end + + it 'only tracks queries triggered from the transaction thread' do + expect(transaction).to_not receive(:track_query) + + Thread.new { subscription.publish('test', time, time, nil, query_data) }. + join + end + end + + describe '#subscribe_to_action_view' do + let(:subscription) { transaction.subscribe_to_action_view } + let(:time) { Time.now } + let(:view_data) { { identifier: 'foo.rb' } } + + after do + ActiveSupport::Notifications.unsubscribe(subscription) + end + + it 'tracks rendered views' do + expect(transaction).to receive(:track_view).with('foo.rb') + + subscription.publish('test', time, time, nil, view_data) + end + + it 'only tracks views rendered from the transaction thread' do + expect(transaction).to_not receive(:track_view) + + Thread.new { subscription.publish('test', time, time, nil, view_data) }. + join + end + end end From 7f9f07023bbe4393620998d1be46cc65d836d5c8 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 9 Nov 2015 12:42:28 +0100 Subject: [PATCH 061/140] Truncate transaction paths to 70 characters This ensures that long URLs don't completely mess up the layout of the table. --- app/views/sherlock/transactions/index.html.haml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/sherlock/transactions/index.html.haml b/app/views/sherlock/transactions/index.html.haml index fb31131ba8..010e1a2a90 100644 --- a/app/views/sherlock/transactions/index.html.haml +++ b/app/views/sherlock/transactions/index.html.haml @@ -27,7 +27,9 @@ - @transactions.each do |trans| %tr %td= trans.type - %td= trans.path + %td + %span{title: trans.path} + = truncate(trans.path, length: 70) %td = trans.duration.round(2) = t('sherlock.seconds') From 68843a53e6cfe5493ad46dac58b469dd969a0edf Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 9 Nov 2015 14:29:50 +0100 Subject: [PATCH 062/140] Added changelog entry for Sherlock --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 19f288d0c7..217dc4e004 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.2.0 (unreleased) + - Added a GitLab specific profiling tool called "Sherlock" (see GitLab CE merge request #1749) - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) - Force update refs/merge-requests/X/head upon a push to the source branch of a merge request (Stan Hu) - Improved performance of finding users by one of their Email addresses From e357e4fbab79b0d834590b623644476a2ef980f5 Mon Sep 17 00:00:00 2001 From: Pirate Praveen Date: Mon, 9 Nov 2015 08:34:25 -0500 Subject: [PATCH 063/140] remove state_machine_patch.rb --- config/initializers/state_machine_patch.rb | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 config/initializers/state_machine_patch.rb diff --git a/config/initializers/state_machine_patch.rb b/config/initializers/state_machine_patch.rb deleted file mode 100644 index 51f0579436..0000000000 --- a/config/initializers/state_machine_patch.rb +++ /dev/null @@ -1,9 +0,0 @@ -# This is a patch to address the issue in https://github.com/pluginaweek/state_machine/issues/251 -# where gem 'state_machine' was not working for Rails 4.1 -module StateMachines - module Integrations - module ActiveModel - public :around_validation - end - end -end From ddd3dd72fec5d31fc76023e10f824cc079f65b63 Mon Sep 17 00:00:00 2001 From: Pirate Praveen Date: Mon, 9 Nov 2015 09:11:42 -0500 Subject: [PATCH 064/140] update Gemfile.lock --- Gemfile.lock | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index dce728baf1..21cbf854c1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -692,7 +692,13 @@ GEM activesupport (>= 3.0) sprockets (>= 2.8, < 4.0) stamp (0.6.0) - state_machine (1.2.0) + state_machines (0.4.0) + state_machines-activemodel (0.3.0) + activemodel (~> 4.1) + state_machines (>= 0.4.0) + state_machines-activerecord (0.3.0) + activerecord (~> 4.1) + state_machines-activemodel (>= 0.3.0) stringex (2.5.2) systemu (2.6.5) task_list (1.0.2) @@ -913,7 +919,7 @@ DEPENDENCIES spring-commands-teaspoon (~> 0.0.2) sprockets (~> 2.12.3) stamp (~> 0.6.0) - state_machine (~> 1.2.0) + state_machines-activerecord (~> 0.3.0) task_list (~> 1.0.2) teaspoon (~> 1.0.0) teaspoon-jasmine (~> 2.2.0) From b67fdfff3c245538ee5a5e9360a2613b76ebada5 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 9 Nov 2015 15:30:50 +0100 Subject: [PATCH 065/140] Refactor release code a bit Signed-off-by: Dmitriy Zaporozhets --- app/controllers/projects/releases_controller.rb | 8 +++++--- app/controllers/projects/tags_controller.rb | 8 -------- app/models/release.rb | 2 +- app/services/create_tag_service.rb | 8 ++++++-- app/services/delete_tag_service.rb | 4 +++- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb index f69a4bc729..0825a4311c 100644 --- a/app/controllers/projects/releases_controller.rb +++ b/app/controllers/projects/releases_controller.rb @@ -10,9 +10,7 @@ class Projects::ReleasesController < Projects::ApplicationController end def update - description = params[:release][:description] - release.update_attributes(description: description) - release.save + release.update_attributes(release_params) redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name) end @@ -26,4 +24,8 @@ class Projects::ReleasesController < Projects::ApplicationController def release @release ||= @project.releases.find_or_initialize_by(tag: @tag.name) end + + def release_params + params.require(:release).permit(:description) + end end diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 670f5d3067..f512f01dc7 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -24,12 +24,6 @@ class Projects::TagsController < Projects::ApplicationController if result[:status] == :success @tag = result[:tag] - if params[:release_description] - release = @project.releases.find_or_initialize_by(tag: @tag.name) - release.update_attributes(description: params[:release_description]) - release.save - end - redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name) else @error = result[:message] @@ -39,8 +33,6 @@ class Projects::TagsController < Projects::ApplicationController def destroy DeleteTagService.new(project, current_user).execute(params[:id]) - release = project.releases.find_by(tag: params[:id]) - release.destroy if release redirect_to namespace_project_tags_path(@project.namespace, @project) end diff --git a/app/models/release.rb b/app/models/release.rb index 05647839e8..e196b84eb1 100644 --- a/app/models/release.rb +++ b/app/models/release.rb @@ -1,5 +1,5 @@ class Release < ActiveRecord::Base belongs_to :project - validates :description, :project, presence: true + validates :description, :project, :tag, presence: true end diff --git a/app/services/create_tag_service.rb b/app/services/create_tag_service.rb index 1a7318048b..9917119fce 100644 --- a/app/services/create_tag_service.rb +++ b/app/services/create_tag_service.rb @@ -1,7 +1,7 @@ require_relative 'base_service' class CreateTagService < BaseService - def execute(tag_name, ref, message) + def execute(tag_name, ref, message, release_description = nil) valid_tag = Gitlab::GitRefValidator.validate(tag_name) if valid_tag == false return error('Tag name invalid') @@ -19,8 +19,12 @@ class CreateTagService < BaseService new_tag = repository.find_tag(tag_name) if new_tag - push_data = create_push_data(project, current_user, new_tag) + if release_description + release = project.releases.find_or_initialize_by(tag: tag_name) + release.update_attributes(description: release_description) + end + push_data = create_push_data(project, current_user, new_tag) EventCreateService.new.push(project, current_user, push_data) project.execute_hooks(push_data.dup, :tag_push_hooks) project.execute_services(push_data.dup, :tag_push_hooks) diff --git a/app/services/delete_tag_service.rb b/app/services/delete_tag_service.rb index 0c83640113..de3352a675 100644 --- a/app/services/delete_tag_service.rb +++ b/app/services/delete_tag_service.rb @@ -11,8 +11,10 @@ class DeleteTagService < BaseService end if repository.rm_tag(tag_name) + release = project.releases.find_by(tag: tag_name) + release.destroy if release + push_data = build_push_data(tag) - EventCreateService.new.push(project, current_user, push_data) project.execute_hooks(push_data.dup, :tag_push_hooks) project.execute_services(push_data.dup, :tag_push_hooks) From b7619dad52504f8fc61bfb3b42e7f8bcc42dc06d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 9 Nov 2015 15:39:18 +0100 Subject: [PATCH 066/140] Add missing param and title for tag Signed-off-by: Dmitriy Zaporozhets --- app/controllers/projects/tags_controller.rb | 2 +- app/views/projects/releases/edit.html.haml | 1 + app/views/projects/tags/show.html.haml | 7 ++++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index f512f01dc7..cb39c2b878 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -19,7 +19,7 @@ class Projects::TagsController < Projects::ApplicationController def create result = CreateTagService.new(@project, current_user). - execute(params[:tag_name], params[:ref], params[:message]) + execute(params[:tag_name], params[:ref], params[:message], params[:release_description]) if result[:status] == :success @tag = result[:tag] diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml index 7874141634..e7db09cdaa 100644 --- a/app/views/projects/releases/edit.html.haml +++ b/app/views/projects/releases/edit.html.haml @@ -1,3 +1,4 @@ +- page_title "Edit", @tag.name, "Tags" = render "projects/commits/header_title" = render "projects/commits/head" diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml index f95ae9edc4..ebe3718afc 100644 --- a/app/views/projects/tags/show.html.haml +++ b/app/views/projects/tags/show.html.haml @@ -4,8 +4,9 @@ .gray-content-block .pull-right - = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn', title: 'Edit release notes' do - = icon("pencil") + - if can?(current_user, :push_code, @project) + = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn', title: 'Edit release notes' do + = icon("pencil") = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped', title: 'Browse source code' do = icon('files-o') = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped', title: 'Browse commits' do @@ -35,4 +36,4 @@ = preserve do = markdown @release.description - else - This tag has no release notes yet. Press edit button to add one + This tag has no release notes. From 73cf0f1647806a4ce064707c6f1f416181de48ef Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 9 Nov 2015 15:54:13 +0100 Subject: [PATCH 067/140] Only load rblineprof when actually needed This ensures the application can still boot when the "development" group is not available. --- lib/gitlab/sherlock.rb | 1 - lib/gitlab/sherlock/line_profiler.rb | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/sherlock.rb b/lib/gitlab/sherlock.rb index c4b35b24ce..6360527a7a 100644 --- a/lib/gitlab/sherlock.rb +++ b/lib/gitlab/sherlock.rb @@ -1,5 +1,4 @@ require 'securerandom' -require 'rblineprof' if RUBY_ENGINE == 'ruby' module Gitlab module Sherlock diff --git a/lib/gitlab/sherlock/line_profiler.rb b/lib/gitlab/sherlock/line_profiler.rb index 152749dcc3..aa1468bff6 100644 --- a/lib/gitlab/sherlock/line_profiler.rb +++ b/lib/gitlab/sherlock/line_profiler.rb @@ -40,6 +40,8 @@ module Gitlab # Profiles the given block using rblineprof (MRI only). def profile_mri + require 'rblineprof' + retval = nil samples = lineprof(/^#{Rails.root.to_s}/) { retval = yield } From 4017a4fcbcb0ed1beb56718cf586c38bbb951527 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 9 Nov 2015 04:40:29 -0800 Subject: [PATCH 068/140] Upgrade gitlab_git to 7.2.20 and rugged to 0.23.3. Switch back to github-linguist --- CHANGELOG | 1 + Gemfile | 10 +++------- Gemfile.lock | 25 +++++++++++++------------ 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 217dc4e004..afb3d1086b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.2.0 (unreleased) - Added a GitLab specific profiling tool called "Sherlock" (see GitLab CE merge request #1749) + - Upgrade gitlab_git to 7.2.20 and rugged to 0.23.3 (Stan Hu) - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) - Force update refs/merge-requests/X/head upon a push to the source branch of a merge request (Stan Hu) - Improved performance of finding users by one of their Email addresses diff --git a/Gemfile b/Gemfile index b0a7c9b945..51848621b6 100644 --- a/Gemfile +++ b/Gemfile @@ -40,7 +40,7 @@ gem "browser", '~> 1.0.0' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", '~> 7.2.19' +gem "gitlab_git", '~> 7.2.20' # LDAP Auth # GitLab fork with several improvements to original library. For full list of changes @@ -51,11 +51,7 @@ gem 'gitlab_omniauth-ldap', '~> 1.2.1', require: "omniauth-ldap" gem 'gollum-lib', '~> 4.0.2' # Language detection -# GitLab fork of linguist does not require pygments/python dependency. -# New version of original gem also dropped pygments support but it has strict -# dependency to unstable rugged version. We have internal issue for replacing -# fork with original gem when we meet on same rugged version - https://dev.gitlab.org/gitlab/gitlabhq/issues/2052. -gem "gitlab-linguist", "~> 3.0.1", require: "linguist" +gem "github-linguist", "~> 4.7.0", require: "linguist" # API gem 'grape', '~> 0.6.1' @@ -185,7 +181,7 @@ gem 'ace-rails-ap', '~> 2.0.1' gem 'mousetrap-rails', '~> 1.4.6' # Detect and convert string character encoding -gem 'charlock_holmes', '~> 0.6.9.4' +gem 'charlock_holmes', '~> 0.7.3' gem "sass-rails", '~> 4.0.5' gem "coffee-rails", '~> 4.1.0' diff --git a/Gemfile.lock b/Gemfile.lock index c602d40671..d358e23630 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -175,7 +175,7 @@ GEM activesupport (>= 3.2) equalizer (0.0.11) erubis (2.7.0) - escape_utils (0.2.4) + escape_utils (1.1.0) eventmachine (1.0.8) excon (0.45.4) execjs (2.6.0) @@ -266,6 +266,11 @@ GEM json get_process_mem (0.2.0) gherkin-ruby (0.3.2) + github-linguist (4.7.0) + charlock_holmes (~> 0.7.3) + escape_utils (~> 1.1.0) + mime-types (>= 1.19) + rugged (>= 0.23.0b) github-markup (1.3.3) gitlab-flowdock-git-hook (1.0.1) flowdock (~> 0.7) @@ -276,17 +281,13 @@ GEM diff-lcs (~> 1.1) mime-types (~> 1.15) posix-spawn (~> 0.3) - gitlab-linguist (3.0.1) - charlock_holmes (~> 0.6.6) - escape_utils (~> 0.2.4) - mime-types (~> 1.19) gitlab_emoji (0.1.1) gemojione (~> 2.0) - gitlab_git (7.2.19) + gitlab_git (7.2.20) activesupport (~> 4.0) - charlock_holmes (~> 0.6) - gitlab-linguist (~> 3.0) - rugged (~> 0.22.2) + charlock_holmes (~> 0.7.3) + github-linguist (~> 4.7.0) + rugged (~> 0.23.3) gitlab_meta (7.0) gitlab_omniauth-ldap (1.2.1) net-ldap (~> 0.9) @@ -794,7 +795,7 @@ DEPENDENCIES capybara (~> 2.4.0) capybara-screenshot (~> 1.0.0) carrierwave (~> 0.9.0) - charlock_holmes (~> 0.6.9.4) + charlock_holmes (~> 0.7.3) coffee-rails (~> 4.1.0) colored (~> 1.2) colorize (~> 0.5.8) @@ -819,11 +820,11 @@ DEPENDENCIES foreman fuubar (~> 2.0.0) gemnasium-gitlab-service (~> 0.2) + github-linguist (~> 4.7.0) github-markup (~> 1.3.1) gitlab-flowdock-git-hook (~> 1.0.1) - gitlab-linguist (~> 3.0.1) gitlab_emoji (~> 0.1) - gitlab_git (~> 7.2.19) + gitlab_git (~> 7.2.20) gitlab_meta (= 7.0) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.0.2) From f526d43432447fe8de84ffc247b65e987ddfde84 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 9 Nov 2015 13:26:29 -0500 Subject: [PATCH 069/140] Update CHANGELOG [ci skip] --- CHANGELOG | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 78ce4f3bfd..4c0e9aa0fb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,8 +2,6 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.2.0 (unreleased) - Added a GitLab specific profiling tool called "Sherlock" (see GitLab CE merge request #1749) - - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) - - Force update refs/merge-requests/X/head upon a push to the source branch of a merge request (Stan Hu) - Improved performance of finding users by one of their Email addresses - Improved performance of replacing references in comments - Show last project commit to default branch on project home page @@ -19,7 +17,6 @@ v 8.2.0 (unreleased) - Enable shared runners to all new projects - Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu) - Remove deprecated CI events from project settings page - - Use issue editor as cross reference comment author when issue is edited with a new mention. - [API] Add ability to fetch the commit ID of the last commit that actually touched a file - Add "New file" link to dropdown on project page - Include commit logs in project search @@ -29,9 +26,14 @@ v 8.2.0 (unreleased) - New design for project graphs page - Fix incoming email config defaults +v 8.1.4 + - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) + - Prevent redirect loop when home_page_url is set to the root URL + v 8.1.3 + - Force update refs/merge-requests/X/head upon a push to the source branch of a merge request (Stan Hu) - Spread out runner contacted_at updates - - New design for user profile page + - Use issue editor as cross reference comment author when issue is edited with a new mention - Add Facebook authentication v 8.1.1 From 9822bce467b19e7b04e3e3c97bb1bcd9e4074161 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 9 Nov 2015 14:24:26 -0500 Subject: [PATCH 070/140] Update monthly release issue template [ci skip] --- doc/release/monthly.md | 86 +++++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 35 deletions(-) diff --git a/doc/release/monthly.md b/doc/release/monthly.md index bd8a67d1d8..4925816daa 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -25,68 +25,84 @@ If the release is falling behind immediately warn the team. ## Create an overall issue and follow it -Create issue for GitLab CE project(internal). Name it "Release x.x.x" for easier searching. -Replace the dates with actual dates based on the number of workdays before the release. -All steps from issue template are explained below +Create an issue in the GitLab CE project. Name it "Release x.x" and tag it with +the `release` label for easier searching. Replace the dates with actual dates +based on the number of workdays before the release. All steps from issue +template are explained below: ``` -Xth: (7 working days before the 22nd) +### Xth: (7 working days before the 22nd) -- [ ] Triage the omnibus-gitlab milestone +- [ ] Triage the [Omnibus milestone] -Xth: (6 working days before the 22nd) +### Xth: (6 working days before the 22nd) -- [ ] Merge CE master in to EE master via merge request (#LINK) +- [ ] Merge CE `master` into EE `master` via merge request (#LINK) - [ ] Determine QA person and notify this person - [ ] Check the tasks in [how to rc1 guide](https://dev.gitlab.org/gitlab/gitlabhq/blob/master/doc/release/howto_rc1.md) and delegate tasks if necessary -- [ ] Create CE, EE, CI RC1 versions (#LINK) -- [ ] Build RC1 packages (EE first) (#LINK) +- [ ] Create CE and EE RC1 versions (#LINK) +- [ ] Build RC1 packages -Xth: (5 working days before the 22nd) +### Xth: (5 working days before the 22nd) - [ ] Do QA and fix anything coming out of it (#LINK) -- [ ] Close the omnibus-gitlab milestone -- [ ] Prepare the blog post (#LINK) +- [ ] Close the [Omnibus milestone] +- [ ] Prepare the [blog post] -Xth: (4 working days before the 22nd) +### Xth: (4 working days before the 22nd) -- [ ] Update GitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package) -- [ ] Update ci.gitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package) -- [ ] Create regression issues (CE, CI) (#LINK) -- [ ] Tweet about rc1 (#LINK), proposed text: +- [ ] Update GitLab.com with RC1 +- [ ] Create the regression issue in the CE issue tracker: -> GitLab x.x.0.rc1 is available https://packages.gitlab.com/gitlab/unstable Use at your own risk. Please link regressions issues from LINK_TO_REGRESSION_ISSUE + > This is a meta issue to index possible regressions in this monthly release + > and any patch versions. + > + > Please do not raise or discuss issues directly in this issue but link to + > issues that might warrant a patch release. If there is a Merge Request + > that fixes the issue, please link to that as well. + > + > Please only post one regression issue and/or merge request per comment. + > Comments will be updated by the release manager as they are addressed. -Xth: (3 working days before the 22nd) +- [ ] Tweet about RC1 release: -- [ ] Merge CE stable branch into EE stable branch + > GitLab x.y.0.rc1 is available: https://packages.gitlab.com/gitlab/unstable + > Use at your own risk. Please link regressions issues from + > LINK_TO_REGRESSION_ISSUE -Xth: (2 working days before the 22nd) +### Xth: (3 working days before the 22nd) -- [ ] Check that everyone is mentioned on the blog post using `@all` (the reviewer should have done this one working day ago) -- [ ] Check that MVP is added to the mvp page (source/mvp/index.html in www-gitlab-com) +- [ ] Merge `x-y-stable` into `x-y-stable-ee` +- [ ] Check that everyone is mentioned on the [blog post] using `@all` -Xth: (1 working day before the 22nd) +### Xth: (2 working days before the 22nd) -- [ ] Merge CE stable into EE stable -- [ ] Create CE, EE, CI release candidates (#LINK) (hopefully final ones with the same commit as the release tomorrow) +- [ ] Check that MVP is added to the [MVP page] + +### Xth: (1 working day before the 22nd) + +- [ ] Merge `x-y-stable` into `x-y-stable-ee` +- [ ] Create CE and EE release candidates - [ ] Create Omnibus tags and build packages for the latest release candidates -- [ ] Update GitLab.com with the latest RC (#LINK) -- [ ] Update ci.gitLab.com with the latest RC (#LINK) +- [ ] Update GitLab.com with the latest RC -22nd before 1200 CET: +### 22nd before 1200 CET: Release before 1200 CET / 2AM PST, to make sure the majority of our users get the new version on the 22nd and there is sufficient time in the European workday to quickly fix any issues. -- [ ] Merge CE stable into EE stable (#LINK) -- [ ] Create the 'x.y.0' tag with the [release tools](https://dev.gitlab.org/gitlab/release-tools) (#LINK) +- [ ] Merge `x-y-stable` into `x-y-stable-ee` +- [ ] Create the 'x.y.0' tag with the [release tools](https://dev.gitlab.org/gitlab/release-tools) - [ ] Create the 'x.y.0' version on version.gitlab.com -- [ ] Try to do before 1100 CET: Create and push omnibus tags for x.y.0 (will auto-release the packages) (#LINK) -- [ ] Try to do before 1200 CET: Publish the release blog post (#LINK) -- [ ] Tweet about the release (blog post) (#LINK) -- [ ] Schedule a second tweet of the release announcement with the same text at 1800 CET / 8AM PST +- [ ] Try to do before 1100 CET: Create and push Omnibus tags for x.y.0 (will auto-release the packages) +- [ ] Try to do before 1200 CET: Publish the release [blog post] +- [ ] Tweet about the release +- [ ] Schedule a second Tweet of the release announcement with the same text at 1800 CET / 8AM PST + +[Omnibus milestone]: LINK_TO_OMNIBUS_MILESTONE +[blog post]: LINK_TO_WIP_BLOG_POST +[MVP page]: https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/source/mvp/index.html ``` - - - From 746e49fee9a28f509f115074d9985830de45513d Mon Sep 17 00:00:00 2001 From: Anton Baklanov Date: Tue, 3 Nov 2015 16:44:14 +0200 Subject: [PATCH 071/140] Display target branch on MR list when it is different from project's default --- CHANGELOG | 1 + .../projects/merge_requests_controller.rb | 1 + .../merge_requests/_merge_request.html.haml | 5 +++++ features/project/merge_requests.feature | 9 +++++++++ features/steps/project/merge_requests.rb | 20 +++++++++++++++++++ 5 files changed, 36 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 44c78ed62c..fa0b361459 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -28,6 +28,7 @@ v 8.2.0 (unreleased) - Allow groups to appear in the search results if the group owner allows it - New design for project graphs page - Fix incoming email config defaults + - MR target branch is now visible on a list view when it is different from project's default one v 8.1.4 - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 16c4238662..b0788a2d07 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -31,6 +31,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController end @merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE) + @merge_requests = @merge_requests.preload(:target_project) respond_to do |format| format.html diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 300a371529..c5234c0618 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -41,6 +41,11 @@ %span %i.fa.fa-clock-o = merge_request.milestone.title + - if merge_request.target_project.default_branch != merge_request.target_branch +   + %span + %i.fa.fa-code-fork + = merge_request.target_branch - if merge_request.tasks? %span.task-status = merge_request.task_status diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index f423c3ba54..6cd081c868 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -16,6 +16,15 @@ Feature: Project Merge Requests When I visit project "Shop" merge requests page Then I should see merge request "Bug NS-05" with CI status + Scenario: I should not see target branch name when it is project's default branch + Then I should see "Bug NS-04" in merge requests + And I should not see "master" branch + + Scenario: I should see target branch when it is different from default + Given project "Shop" have "Bug NS-06" open merge request + When I visit project "Shop" merge requests page + Then I should see "other_branch" branch + Scenario: I should see rejected merge requests Given I click link "Closed" Then I should see "Feature NS-03" in merge requests diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 92ec14d0d7..d5f2c4209a 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -40,6 +40,14 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps expect(page).to have_content "Bug NS-04" end + step 'I should not see "master" branch' do + expect(page).not_to have_content "master" + end + + step 'I should see "other_branch" branch' do + expect(page).to have_content "other_branch" + end + step 'I should see "Bug NS-04" in merge requests' do expect(page).to have_content "Bug NS-04" end @@ -93,6 +101,18 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ) end + step 'project "Shop" have "Bug NS-06" open merge request' do + create(:merge_request, + title: "Bug NS-06", + source_project: project, + target_project: project, + source_branch: 'fix', + target_branch: 'other_branch', + author: project.users.first, + description: "# Description header" + ) + end + step 'project "Shop" have "Bug NS-05" open merge request with diffs inside' do create(:merge_request_with_diffs, title: "Bug NS-05", From f3bf0fe7aca0672c406141ff88fffe623e507bdc Mon Sep 17 00:00:00 2001 From: Marco Vito Moscaritolo Date: Tue, 10 Nov 2015 11:25:28 +0100 Subject: [PATCH 072/140] Fix duplicate entry for 8.1.0 release Fix duplicate entry for 8.1.0 release and remove unreleased tag. --- CHANGELOG | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d11076f484..843589eacf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -56,8 +56,6 @@ v 8.1.0 - Redirect to a default path if HTTP_REFERER is not set (Stan Hu) - Adds ability to create directories using the web editor (Ben Ford) - Cleanup stuck CI builds - -v 8.1.0 (unreleased) - Send an email to admin email when a user is reported for spam (Jonathan Rochkind) - Show notifications button when user is member of group rather than project (Grzegorz Bizon) - Fix bug preventing mentioned issued from being closed when MR is merged using fast-forward merge. From 1b7a2fc5363d7e2334f5c48940e8eca9d88354a6 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 10 Nov 2015 11:49:38 +0100 Subject: [PATCH 073/140] Improve Continuous Integration graphs page * fix commit duration graph * make graphs responsive * fix wrong padding * add a bit of explanation to colors Signed-off-by: Dmitriy Zaporozhets --- app/views/projects/graphs/ci.html.haml | 16 +++++-- .../projects/graphs/ci/_build_times.haml | 17 +++---- app/views/projects/graphs/ci/_builds.haml | 48 +++++++++++-------- app/views/projects/graphs/ci/_overall.haml | 16 +++---- app/views/projects/graphs/commits.html.haml | 2 +- app/views/projects/graphs/show.html.haml | 2 +- lib/ci/charts.rb | 3 +- 7 files changed, 60 insertions(+), 44 deletions(-) diff --git a/app/views/projects/graphs/ci.html.haml b/app/views/projects/graphs/ci.html.haml index b2dfe97938..6fa77cc10c 100644 --- a/app/views/projects/graphs/ci.html.haml +++ b/app/views/projects/graphs/ci.html.haml @@ -1,10 +1,16 @@ - page_title "Continuous Integration", "Graphs" = render "header_title" = render 'head' -.gray-content-block - %ul.breadcrumb.repo-breadcrumb - = commits_breadcrumbs +.gray-content-block.append-bottom-default + .oneline + A collection of graphs for Continuous Integration + #charts.ci-charts + .row + .col-md-6 + = render 'projects/graphs/ci/overall' + .col-md-6 + = render 'projects/graphs/ci/build_times' + + %hr = render 'projects/graphs/ci/builds' - = render 'projects/graphs/ci/build_times' -= render 'projects/graphs/ci/overall' diff --git a/app/views/projects/graphs/ci/_build_times.haml b/app/views/projects/graphs/ci/_build_times.haml index c3c2f57241..c58223fd39 100644 --- a/app/views/projects/graphs/ci/_build_times.haml +++ b/app/views/projects/graphs/ci/_build_times.haml @@ -1,21 +1,22 @@ -%fieldset - %legend +%div + %p.light Commit duration in minutes for last 30 commits - %canvas#build_timesChart.padded{width: 800, height: 300} + %canvas#build_timesChart{height: 200} :javascript var data = { labels : #{@charts[:build_times].labels.to_json}, datasets : [ { - fillColor : "#4A3", - strokeColor : "rgba(151,187,205,1)", - pointColor : "rgba(151,187,205,1)", - pointStrokeColor : "#fff", + fillColor : "rgba(220,220,220,0.5)", + strokeColor : "rgba(220,220,220,1)", + barStrokeWidth: 1, + barValueSpacing: 1, + barDatasetSpacing: 1, data : #{@charts[:build_times].build_times.to_json} } ] } var ctx = $("#build_timesChart").get(0).getContext("2d"); - new Chart(ctx).Line(data,{"scaleOverlay": true}); + new Chart(ctx).Bar(data,{"scaleOverlay": true, responsive: true, maintainAspectRatio: false}); diff --git a/app/views/projects/graphs/ci/_builds.haml b/app/views/projects/graphs/ci/_builds.haml index 1b0039fb83..8424745540 100644 --- a/app/views/projects/graphs/ci/_builds.haml +++ b/app/views/projects/graphs/ci/_builds.haml @@ -1,20 +1,30 @@ -%fieldset - %legend - Builds chart for last week +%h4 Build charts +%p +   + %span.cgreen + = icon("circle") + success +   + %span.cgray + = icon("circle") + all + +.prepend-top-default + %p.light + Builds for last week (#{date_from_to(Date.today - 7.days, Date.today)}) + %canvas#weekChart{height: 200} - %canvas#weekChart.padded{width: 800, height: 200} - -%fieldset - %legend +.prepend-top-default + %p.light Builds chart for last month (#{date_from_to(Date.today - 30.days, Date.today)}) + %canvas#monthChart{height: 200} - %canvas#monthChart.padded{width: 800, height: 300} - -%fieldset - %legend Builds chart for last year - %canvas#yearChart.padded{width: 800, height: 400} +.prepend-top-default + %p.light + Builds chart for last year + %canvas#yearChart.padded{height: 250} - [:week, :month, :year].each do |scope| :javascript @@ -22,20 +32,20 @@ labels : #{@charts[scope].labels.to_json}, datasets : [ { - fillColor : "rgba(220,220,220,0.5)", - strokeColor : "rgba(220,220,220,1)", - pointColor : "rgba(220,220,220,1)", + fillColor : "#7f8fa4", + strokeColor : "#7f8fa4", + pointColor : "#7f8fa4", pointStrokeColor : "#EEE", data : #{@charts[scope].total.to_json} }, { - fillColor : "#4A3", - strokeColor : "rgba(151,187,205,1)", - pointColor : "rgba(151,187,205,1)", + fillColor : "#44aa22", + strokeColor : "#44aa22", + pointColor : "#44aa22", pointStrokeColor : "#fff", data : #{@charts[scope].success.to_json} } ] } var ctx = $("##{scope}Chart").get(0).getContext("2d"); - new Chart(ctx).Line(data,{"scaleOverlay": true}); + new Chart(ctx).Line(data,{"scaleOverlay": true, responsive: true, maintainAspectRatio: false}); diff --git a/app/views/projects/graphs/ci/_overall.haml b/app/views/projects/graphs/ci/_overall.haml index 9550d71947..cf4285a267 100644 --- a/app/views/projects/graphs/ci/_overall.haml +++ b/app/views/projects/graphs/ci/_overall.haml @@ -1,22 +1,20 @@ - ci_project = @project.gitlab_ci_project -%fieldset - %legend Overall - %p +%h4 Overall stats +%ul + %li Total: %strong= pluralize ci_project.builds.count(:all), 'build' - %p + %li Successful: %strong= pluralize ci_project.builds.success.count(:all), 'build' - %p + %li Failed: %strong= pluralize ci_project.builds.failed.count(:all), 'build' - - %p + %li Success ratio: %strong #{success_ratio(ci_project.builds.success, ci_project.builds.failed)}% - - %p + %li Commits covered: %strong = ci_project.commits.count(:all) diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml index 4e0c3e5b3d..c03790aea0 100644 --- a/app/views/projects/graphs/commits.html.haml +++ b/app/views/projects/graphs/commits.html.haml @@ -2,7 +2,7 @@ = render "header_title" = render 'head' -.gray-content-block +.gray-content-block.append-bottom-default .tree-ref-holder = render 'shared/ref_switcher', destination: 'graphs_commits' %ul.breadcrumb.repo-breadcrumb diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index 6bbf15d05a..84ee843d9b 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -2,7 +2,7 @@ = render "header_title" = render 'head' -.gray-content-block +.gray-content-block.append-bottom-default .tree-ref-holder = render 'shared/ref_switcher', destination: 'graphs' %ul.breadcrumb.repo-breadcrumb diff --git a/lib/ci/charts.rb b/lib/ci/charts.rb index 915a4f526a..5ff7407c6f 100644 --- a/lib/ci/charts.rb +++ b/lib/ci/charts.rb @@ -60,7 +60,8 @@ module Ci class BuildTime < Chart def collect - commits = project.commits.joins(:builds).where("#{Ci::Build.table_name}.finished_at is NOT NULL AND #{Ci::Build.table_name}.started_at is NOT NULL").last(30) + commits = project.commits.last(30) + commits.each do |commit| @labels << commit.short_sha @build_times << (commit.duration / 60) From b1a1cadcc2056540e4eb5a6b48c87b83656633be Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 10 Nov 2015 11:51:58 +0100 Subject: [PATCH 074/140] Add changelog item Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index d11076f484..fe521e0f14 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -28,6 +28,7 @@ v 8.2.0 (unreleased) - Allow groups to appear in the search results if the group owner allows it - New design for project graphs page - Fix incoming email config defaults + - Improve Continuous Integration graphs page v 8.1.4 - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) From e8d97eb118ecbfe2a77abf2dcd56d7ab1cfac62a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 10 Nov 2015 12:01:29 +0100 Subject: [PATCH 075/140] Send build name and stage in CI notification e-mail --- CHANGELOG | 1 + app/views/ci/notify/build_fail_email.html.haml | 4 ++++ app/views/ci/notify/build_fail_email.text.erb | 2 ++ app/views/ci/notify/build_success_email.html.haml | 4 ++++ app/views/ci/notify/build_success_email.text.erb | 2 ++ 5 files changed, 13 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index d11076f484..19850b58f7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -16,6 +16,7 @@ v 8.2.0 (unreleased) - Fix: Inability to reply to code comments in the MR view, if the MR comes from a fork - Use git follow flag for commits page when retrieve history for file or directory - Show merge request CI status on merge requests index page + - Send build name and stage in CI notification e-mail - Extend yml syntax for only and except to support specifying repository path - Enable shared runners to all new projects - Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu) diff --git a/app/views/ci/notify/build_fail_email.html.haml b/app/views/ci/notify/build_fail_email.html.haml index cefb75040e..b0aaea8907 100644 --- a/app/views/ci/notify/build_fail_email.html.haml +++ b/app/views/ci/notify/build_fail_email.html.haml @@ -12,6 +12,10 @@ Author: #{@build.commit.git_author_name} %p Branch: #{@build.ref} +%p + Stage: #{@build.stage} +%p + Job: #{@build.name} %p Message: #{@build.commit.git_commit_message} diff --git a/app/views/ci/notify/build_fail_email.text.erb b/app/views/ci/notify/build_fail_email.text.erb index 6de5dc10f1..17a3b9b1d3 100644 --- a/app/views/ci/notify/build_fail_email.text.erb +++ b/app/views/ci/notify/build_fail_email.text.erb @@ -4,6 +4,8 @@ Status: <%= @build.status %> Commit: <%= @build.commit.short_sha %> Author: <%= @build.commit.git_author_name %> Branch: <%= @build.ref %> +Stage: <%= @build.stage %> +Job: <%= @build.name %> Message: <%= @build.commit.git_commit_message %> Url: <%= namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build) %> diff --git a/app/views/ci/notify/build_success_email.html.haml b/app/views/ci/notify/build_success_email.html.haml index 617b88f734..24c439e50e 100644 --- a/app/views/ci/notify/build_success_email.html.haml +++ b/app/views/ci/notify/build_success_email.html.haml @@ -13,6 +13,10 @@ Author: #{@build.commit.git_author_name} %p Branch: #{@build.ref} +%p + Stage: #{@build.stage} +%p + Job: #{@build.name} %p Message: #{@build.commit.git_commit_message} diff --git a/app/views/ci/notify/build_success_email.text.erb b/app/views/ci/notify/build_success_email.text.erb index d0a43ae1c1..bc8b978c3d 100644 --- a/app/views/ci/notify/build_success_email.text.erb +++ b/app/views/ci/notify/build_success_email.text.erb @@ -4,6 +4,8 @@ Status: <%= @build.status %> Commit: <%= @build.commit.short_sha %> Author: <%= @build.commit.git_author_name %> Branch: <%= @build.ref %> +Stage: <%= @build.stage %> +Job: <%= @build.name %> Message: <%= @build.commit.git_commit_message %> Url: <%= namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build) %> From d024db0cc816d03063f889a6d3d570f70e8e896c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 10 Nov 2015 12:14:32 +0100 Subject: [PATCH 076/140] Remove deprecated dumped yaml file generated from previous job definitions --- CHANGELOG | 1 + app/controllers/ci/projects_controller.rb | 4 ---- app/views/projects/ci_settings/edit.html.haml | 19 ------------------- config/routes.rb | 1 - 4 files changed, 1 insertion(+), 24 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d11076f484..2d866a5c6c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -27,6 +27,7 @@ v 8.2.0 (unreleased) - Rename "Back to" links to "Go to" because its not always a case it point to place user come from - Allow groups to appear in the search results if the group owner allows it - New design for project graphs page + - Remove deprecated dumped yaml file generated from previous job definitions - Fix incoming email config defaults v 8.1.4 diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb index 809b44387b..8406399fb6 100644 --- a/app/controllers/ci/projects_controller.rb +++ b/app/controllers/ci/projects_controller.rb @@ -26,10 +26,6 @@ module Ci redirect_to namespace_project_runners_path(project.gl_project.namespace, project.gl_project) end - def dumped_yaml - send_data @project.generated_yaml_config, filename: '.gitlab-ci.yml' - end - protected def project diff --git a/app/views/projects/ci_settings/edit.html.haml b/app/views/projects/ci_settings/edit.html.haml index 665556f5c2..acc912d459 100644 --- a/app/views/projects/ci_settings/edit.html.haml +++ b/app/views/projects/ci_settings/edit.html.haml @@ -1,25 +1,6 @@ - page_title "CI Settings" -- if @ci_project.generated_yaml_config - %p.alert.alert-danger - CI Jobs are deprecated now, you can #{link_to "download", dumped_yaml_ci_project_path(@ci_project)} - or - %a.preview-yml{:href => "#yaml-content", "data-toggle" => "modal"} preview - yaml file which is based on your old jobs. - Put this file to the root of your project and name it .gitlab-ci.yml - if no_runners_for_project?(@ci_project) = render 'no_runners' = render 'form' - -- if @ci_project.generated_yaml_config - #yaml-content.modal.fade{"aria-hidden" => "true", "aria-labelledby" => ".gitlab-ci.yml", :role => "dialog", :tabindex => "-1"} - .modal-dialog - .modal-content - .modal-header - %button.close{"aria-hidden" => "true", "data-dismiss" => "modal", :type => "button"} × - %h4.modal-title Content of .gitlab-ci.yml - .modal-body - = text_area_tag :yaml, @ci_project.generated_yaml_config, size: "70x25", class: "form-control" - .modal-footer - %button.btn.btn-default{"data-dismiss" => "modal", :type => "button"} Close diff --git a/config/routes.rb b/config/routes.rb index 2028ea938e..dfc1c571ce 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -32,7 +32,6 @@ Gitlab::Application.routes.draw do get :status, to: 'projects#badge' get :integration post :toggle_shared_runners - get :dumped_yaml end resources :runner_projects, only: [:create, :destroy] From 18cb430f7983ea557cf2308f5ea7c0af8b79a7b5 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Tue, 10 Nov 2015 19:17:37 +0800 Subject: [PATCH 077/140] Replace CoffeeScript block into JavaScript in Views. For example view: shared/issuable/_context CoffeeScript: 190ms JavaScript: 19.7ms --- app/views/admin/labels/_form.html.haml | 4 +- app/views/ci/lints/show.html.haml | 16 +++--- .../groups/group_members/index.html.haml | 9 ++-- app/views/help/_shortcuts.html.haml | 8 +-- app/views/import/bitbucket/status.html.haml | 4 +- .../import/fogbugz/new_user_map.html.haml | 6 +-- app/views/import/fogbugz/status.html.haml | 4 +- app/views/import/github/status.html.haml | 4 +- app/views/import/gitlab/status.html.haml | 4 +- app/views/import/gitorious/status.html.haml | 4 +- app/views/import/google_code/status.html.haml | 4 +- app/views/layouts/_search.html.haml | 4 +- app/views/projects/_activity.html.haml | 4 +- app/views/projects/blob/_new_dir.html.haml | 2 +- app/views/projects/blob/_upload.html.haml | 6 +-- app/views/projects/buttons/_star.html.haml | 12 +++-- .../projects/commit/_commit_box.html.haml | 4 +- app/views/projects/graphs/commits.html.haml | 52 +++++++++---------- app/views/projects/graphs/show.html.haml | 21 ++++---- .../merge_requests/_new_compare.html.haml | 17 +++--- .../merge_requests/widget/_heading.html.haml | 7 +-- .../merge_requests/widget/_merged.html.haml | 27 +++++----- .../widget/open/_accept.html.haml | 11 ++-- .../widget/open/_check.html.haml | 8 +-- app/views/projects/new.html.haml | 14 ++--- .../projects/project_members/index.html.haml | 9 ++-- app/views/shared/issuable/_context.html.haml | 6 +-- app/views/shared/issuable/_filter.html.haml | 12 ++--- app/views/shared/projects/_list.html.haml | 4 +- app/views/users/show.html.haml | 4 +- 30 files changed, 155 insertions(+), 136 deletions(-) diff --git a/app/views/admin/labels/_form.html.haml b/app/views/admin/labels/_form.html.haml index ad58a3837f..a5ace4e7a3 100644 --- a/app/views/admin/labels/_form.html.haml +++ b/app/views/admin/labels/_form.html.haml @@ -31,5 +31,5 @@ = f.submit 'Save', class: 'btn btn-save js-save-button' = link_to "Cancel", admin_labels_path, class: 'btn btn-cancel' -:coffeescript - new Labels +:javascript + new Labels(); diff --git a/app/views/ci/lints/show.html.haml b/app/views/ci/lints/show.html.haml index a9b954771c..fb9057e488 100644 --- a/app/views/ci/lints/show.html.haml +++ b/app/views/ci/lints/show.html.haml @@ -11,15 +11,17 @@ .controls.pull-left.prepend-top-10 = submit_tag "Validate", class: 'btn btn-success submit-yml' - + %p.text-center.loading %i.fa.fa-refresh.fa-spin .results.prepend-top-20 -:coffeescript - $(".loading").hide() - $('form').bind 'ajax:beforeSend', -> - $(".loading").show() - $('form').bind 'ajax:complete', -> - $(".loading").hide() +:javascript + $(".loading").hide(); + $('form').bind('ajax:beforeSend', function() { + $(".loading").show(); + }); + $('form').bind('ajax:complete', function() { + $(".loading").hide(); + }); diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index fee4b0052b..15d289471c 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -36,7 +36,8 @@ = paginate @members, theme: 'gitlab' -:coffeescript - $('form.member-search-form').on 'submit', (event) -> - event.preventDefault() - Turbolinks.visit @.action + '?' + $(@).serialize() +:javascript + $('form.member-search-form').on('submit', function(event) { + event.preventDefault(); + Turbolinks.visit(this.action + '?' + $(this).serialize()); + }); diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 67349fcbd7..7e801b5332 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -222,8 +222,8 @@ :javascript - $('.js-more-help-button').click(function(e){ - $(this).remove() - $('.hidden-shortcut').show() - e.preventDefault() + $('.js-more-help-button').click(function (e) { + $(this).remove()l + $('.hidden-shortcut').show(); + e.preventDefault(); }); diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml index 30bcdb8682..1f09a27e2d 100644 --- a/app/views/import/bitbucket/status.html.haml +++ b/app/views/import/bitbucket/status.html.haml @@ -66,5 +66,5 @@ again. -:coffeescript - new ImporterStatus("#{jobs_import_bitbucket_path}", "#{import_bitbucket_path}") +:javascript + new ImporterStatus("#{jobs_import_bitbucket_path}", "#{import_bitbucket_path}"); diff --git a/app/views/import/fogbugz/new_user_map.html.haml b/app/views/import/fogbugz/new_user_map.html.haml index a701e49ac5..bc3c90294e 100644 --- a/app/views/import/fogbugz/new_user_map.html.haml +++ b/app/views/import/fogbugz/new_user_map.html.haml @@ -22,7 +22,7 @@ %strong Map a FogBugz account ID to a GitLab user %p Selecting a GitLab user will add a link to the GitLab user in the descriptions - of issues and comments (e.g. "By @johnsmith"). It will also + of issues and comments (e.g. "By @johnsmith"). It will also associate and/or assign these issues and comments with the selected user. .table-holder @@ -46,5 +46,5 @@ .form-actions = submit_tag 'Continue to the next step', class: 'btn btn-create' -:coffeescript - new UsersSelect() +:javascript + new UsersSelect(); diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml index beca6ab142..b902006597 100644 --- a/app/views/import/fogbugz/status.html.haml +++ b/app/views/import/fogbugz/status.html.haml @@ -48,5 +48,5 @@ %td.import-actions.job-status = button_tag "Import", class: "btn js-add-to-import" -:coffeescript - new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}") +:javascript + new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}"); diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml index 0669b05adc..0699321c8c 100644 --- a/app/views/import/github/status.html.haml +++ b/app/views/import/github/status.html.haml @@ -43,5 +43,5 @@ %td.import-actions.job-status = button_tag "Import", class: "btn js-add-to-import" -:coffeescript - new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}") +:javascript + new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}"); diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml index 3bc85059e7..f4a2b33af2 100644 --- a/app/views/import/gitlab/status.html.haml +++ b/app/views/import/gitlab/status.html.haml @@ -43,5 +43,5 @@ %td.import-actions.job-status = button_tag "Import", class: "btn js-add-to-import" -:coffeescript - new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}") +:javascript + new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}"); diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml index 2e3a535737..71752d21ef 100644 --- a/app/views/import/gitorious/status.html.haml +++ b/app/views/import/gitorious/status.html.haml @@ -43,5 +43,5 @@ %td.import-actions.job-status = button_tag "Import", class: "btn js-add-to-import" -:coffeescript - new ImporterStatus("#{jobs_import_gitorious_path}", "#{import_gitorious_path}") +:javascript + new ImporterStatus("#{jobs_import_gitorious_path}", "#{import_gitorious_path}"); diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml index c5af06edf8..8c64fd27e6 100644 --- a/app/views/import/google_code/status.html.haml +++ b/app/views/import/google_code/status.html.haml @@ -67,5 +67,5 @@ = link_to "import flow", new_import_google_code_path again. -:coffeescript - new ImporterStatus("#{jobs_import_google_code_path}", "#{import_google_code_path}") +:javascript + new ImporterStatus("#{jobs_import_google_code_path}", "#{import_google_code_path}"); diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index d1aa8f6246..a44f5762a6 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -25,6 +25,6 @@ :javascript $('.search-input').on('keyup', function(e) { if (e.keyCode == 27) { - $('.search-input').blur() + $('.search-input').blur(); } - }) + }); diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml index 012858f70b..101880bd10 100644 --- a/app/views/projects/_activity.html.haml +++ b/app/views/projects/_activity.html.haml @@ -8,5 +8,5 @@ .content_list{:"data-href" => activity_project_path(@project)} = spinner -:coffeescript - new Activities() +:javascript + new Activities(); diff --git a/app/views/projects/blob/_new_dir.html.haml b/app/views/projects/blob/_new_dir.html.haml index cb1567a2e6..a0fc8bbd75 100644 --- a/app/views/projects/blob/_new_dir.html.haml +++ b/app/views/projects/blob/_new_dir.html.haml @@ -21,5 +21,5 @@ = submit_tag "Create directory", class: 'btn btn-primary btn-create' = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" -:coffeescript +:javascript disableButtonIfAnyEmptyField($("#dir-create-form"), ".form-control", ".btn-create"); diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml index e27f170752..a1c54e731f 100644 --- a/app/views/projects/blob/_upload.html.haml +++ b/app/views/projects/blob/_upload.html.haml @@ -26,6 +26,6 @@ = button_tag button_title, class: 'btn btn-small btn-primary btn-upload-file', id: 'submit-all' = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" -:coffeescript - disableButtonIfEmptyField $('.blob-file-upload-form-js').find('#commit_message'), '.btn-upload-file' - new BlobFileDropzone($('.blob-file-upload-form-js'), '#{method}') +:javascript + disableButtonIfEmptyField($('.blob-file-upload-form-js').find('#commit_message'), '.btn-upload-file'); + new BlobFileDropzone($('.blob-file-upload-form-js'), '#{method}'); diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml index 3501dddefb..0658390203 100644 --- a/app/views/projects/buttons/_star.html.haml +++ b/app/views/projects/buttons/_star.html.haml @@ -4,11 +4,13 @@ %span.count = @project.star_count - :coffeescript - $('.project-home-panel .toggle-star').on 'ajax:success', (e, data, status, xhr) -> - $(@).replaceWith(data.html) - .on 'ajax:error', (e, xhr, status, error) -> - new Flash('Star toggle failed. Try again later.', 'alert') + :javascript + $('.project-home-panel .toggle-star').on('ajax:success', function (e, data, status, xhr) { + $(this).replaceWith(data.html); + }) + .on('ajax:error', function (e, xhr, status, error) { + new Flash('Star toggle failed. Try again later.', 'alert'); + }); - else = link_to new_user_session_path, class: 'btn has_tooltip star-btn', title: 'You must sign in to star a project' do diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index a6458b8486..776768537d 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -55,5 +55,5 @@ %pre.commit-description = preserve(gfm(escape_once(@commit.description))) -:coffeescript - $(".commit-info-row.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}") +:javascript + $(".commit-info-row.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}"); diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml index 4e0c3e5b3d..eb33da7a5b 100644 --- a/app/views/projects/graphs/commits.html.haml +++ b/app/views/projects/graphs/commits.html.haml @@ -49,26 +49,24 @@ Commits per weekday %canvas#weekday-chart -:coffeescript - responsiveChart = (selector, data) -> - options = { "scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2, maintainAspectRatio: false } +:javascript + var responsiveChart = function (selector, data) { + var options = { "scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2, maintainAspectRatio: false }; + // get selector by context + var ctx = selector.get(0).getContext("2d"); + // pointing parent container to make chart.js inherit its width + var container = $(selector).parent(); + var generateChart = function() { + selector.attr('width', $(container).width()); + return new Chart(ctx).Bar(data, options); + }; + // enabling auto-resizing + $(window).resize(generateChart); + return generateChart(); + }; - # get selector by context - ctx = selector.get(0).getContext("2d") - # pointing parent container to make chart.js inherit its width - container = $(selector).parent() - - generateChart = -> - selector.attr('width', $(container).width()) - new Chart(ctx).Bar(data, options) - - # enabling auto-resizing - $(window).resize( generateChart ) - - generateChart() - - chartData = (keys, values) -> - data = { + var chartData = function (keys, values) { + var data = { labels : keys, datasets : [{ fillColor : "rgba(220,220,220,0.5)", @@ -78,13 +76,15 @@ barDatasetSpacing: 1, data : values }] - } + }; + return data; + }; - hourData = chartData(#{@commits_per_time.keys.to_json}, #{@commits_per_time.values.to_json}) - responsiveChart($('#hour-chart'), hourData) + var hourData = chartData(#{@commits_per_time.keys.to_json}, #{@commits_per_time.values.to_json}); + responsiveChart($('#hour-chart'), hourData); - dayData = chartData(#{@commits_per_week_days.keys.to_json}, #{@commits_per_week_days.values.to_json}) - responsiveChart($('#weekday-chart'), dayData) + var dayData = chartData(#{@commits_per_week_days.keys.to_json}, #{@commits_per_week_days.values.to_json}); + responsiveChart($('#weekday-chart'), dayData); - monthData = chartData(#{@commits_per_month.keys.to_json}, #{@commits_per_month.values.to_json}) - responsiveChart($('#month-chart'), monthData) + var monthData = chartData(#{@commits_per_month.keys.to_json}, #{@commits_per_month.values.to_json}); + responsiveChart($('#month-chart'), monthData); diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index 6bbf15d05a..9e1feadeb2 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -28,18 +28,21 @@ -:coffeescript - $.ajax +:javascript + $.ajax({ type: "GET", url: location.href, - success: (data) -> - graph = new ContributorsStatGraph() - graph.init(data) + dataType: "json", + success: function (data) { + var graph = new ContributorsStatGraph(); + graph.init(data); - $("#brush_change").change -> - graph.change_date_header() - graph.redraw_authors() + $("#brush_change").change(function(){ + graph.change_date_header(); + graph.redraw_authors(); + }); $(".stat-graph").fadeIn(); $(".loading-graph").hide(); - dataType: "json" + } + }); diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index 452006162d..d9eff1f932 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -77,12 +77,13 @@ }); -:coffeescript - - $(".merge-request-form").on 'submit', -> - if $("#merge_request_source_branch").val() is "" or $('#merge_request_target_branch').val() is "" - $(".mr-compare-errors").html("You must select source and target branch to proceed") - $(".mr-compare-errors").fadeIn() - event.preventDefault() - return +:javascript + $(".merge-request-form").on('submit', function () { + if ($("#merge_request_source_branch").val() === "" || $('#merge_request_target_branch').val() === "") { + $(".mr-compare-errors").html("You must select source and target branch to proceed"); + $(".mr-compare-errors").fadeIn(); + event.preventDefault(); + return; + } + }); diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index a3551516bf..ba5ad22bca 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -38,6 +38,7 @@ = icon("times-circle") Could not connect to the CI server. Please check your settings and try again. - :coffeescript - $ -> - merge_request_widget.getCiStatus() + :javascript + $(function() { + merge_request_widget.getCiStatus(); + }); diff --git a/app/views/projects/merge_requests/widget/_merged.html.haml b/app/views/projects/merge_requests/widget/_merged.html.haml index f223f687de..a788fcea23 100644 --- a/app/views/projects/merge_requests/widget/_merged.html.haml +++ b/app/views/projects/merge_requests/widget/_merged.html.haml @@ -15,7 +15,7 @@ - elsif can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) .remove_source_branch_widget - %p + %p = succeed '.' do The changes were merged into %span.label-branch= @merge_request.target_branch @@ -25,7 +25,7 @@ Remove Source Branch .remove_source_branch_widget.failed.hide - %p + %p Failed to remove source branch '#{@merge_request.source_branch}'. .remove_source_branch_in_progress.hide @@ -33,17 +33,20 @@ = icon('spinner spin') Removing source branch '#{@merge_request.source_branch}'. Please wait. This page will be automatically reload. - :coffeescript - $('.remove_source_branch').on 'click', -> - $('.remove_source_branch_widget').hide() - $('.remove_source_branch_in_progress').show() + :javascript + $('.remove_source_branch').on('click', function() { + $('.remove_source_branch_widget').hide(); + $('.remove_source_branch_in_progress').show(); + }); - $(".remove_source_branch").on "ajax:success", (e, data, status, xhr) -> - location.reload() + $(".remove_source_branch").on("ajax:success", function (e, data, status, xhr) { + location.reload(); + }); - $(".remove_source_branch").on "ajax:error", (e, data, status, xhr) -> - $('.remove_source_branch_widget').hide() - $('.remove_source_branch_in_progress').hide() - $('.remove_source_branch_widget.failed').show() + $(".remove_source_branch").on("ajax:error", function (e, data, status, xhr) { + $('.remove_source_branch_widget').hide(); + $('.remove_source_branch_in_progress').hide(); + $('.remove_source_branch_widget.failed').show(); + }); diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml index 689247f318..9b31014b58 100644 --- a/app/views/projects/merge_requests/widget/open/_accept.html.haml +++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml @@ -20,8 +20,9 @@ text: @merge_request.merge_commit_message, rows: 14, hint: true - :coffeescript - $('.accept-mr-form').on 'ajax:before', -> - btn = $('.accept_merge_request') - btn.disable() - btn.html(" Merge in progress") + :javascript + $('.accept-mr-form').on('ajax:before', function() { + var btn = $('.accept_merge_request'); + btn.disable(); + btn.html(" Merge in progress"); + }); diff --git a/app/views/projects/merge_requests/widget/open/_check.html.haml b/app/views/projects/merge_requests/widget/open/_check.html.haml index b6b8974297..e16878ba51 100644 --- a/app/views/projects/merge_requests/widget/open/_check.html.haml +++ b/app/views/projects/merge_requests/widget/open/_check.html.haml @@ -2,6 +2,8 @@ = icon("spinner spin") Checking ability to merge automatically… -:coffeescript - $ -> - merge_request_widget.getMergeStatus() +:javascript + $(function() { + merge_request_widget.getMergeStatus(); + }); + diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index daab2326bc..a02c12f06a 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -124,9 +124,11 @@ Creating project & repository. %p Please wait a moment, this page will automatically refresh when ready. -:coffeescript - $('.how_to_import_link').bind 'click', (e) -> - e.preventDefault() - import_modal = $(this).next(".modal").show() - $('.modal-header .close').bind 'click', -> - $(".modal").hide() +:javascript + $('.how_to_import_link').bind('click', function (e) { + e.preventDefault(); + var import_modal = $(this).next(".modal").show(); + }); + $('.modal-header .close').bind('click', function() { + $(".modal").hide(); + }); diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 82809bec5b..9fc4be583c 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -29,7 +29,8 @@ - if @group = render "group_members", members: @group_members -:coffeescript - $('form.member-search-form').on 'submit', (event) -> - event.preventDefault() - Turbolinks.visit @.action + '?' + $(@).serialize() +:javascript + $('form.member-search-form').on('submit', function (event) { + event.preventDefault(); + Turbolinks.visit(this.action + '?' + $(this).serialize()); + }); diff --git a/app/views/shared/issuable/_context.html.haml b/app/views/shared/issuable/_context.html.haml index cba18c1456..be66256c7b 100644 --- a/app/views/shared/issuable/_context.html.haml +++ b/app/views/shared/issuable/_context.html.haml @@ -45,6 +45,6 @@ .description-block.subscribed{class: ( 'hidden' unless subscribed )} You're receiving notifications because you're subscribed to this thread. -:coffeescript - new Subscription("#{toggle_subscription_path(issuable)}") - new IssuableContext() +:javascript + new Subscription("#{toggle_subscription_path(issuable)}"); + new IssuableContext(); diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 0e4e9c0987..d1231438ee 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -60,9 +60,9 @@ = hidden_field_tag :state_event, params[:state_event] = button_tag "Update issues", class: "btn update_selected_issues btn-save" -:coffeescript - new UsersSelect() - - $('form.filter-form').on 'submit', (event) -> - event.preventDefault() - Turbolinks.visit @.action + '&' + $(@).serialize() +:javascript + new UsersSelect(); + $('form.filter-form').on('submit', function (event) { + event.preventDefault(); + Turbolinks.visit(this.action + '&' + $(this).serialize()); + }); diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml index 357cfd6a37..e5ffe1e29a 100644 --- a/app/views/shared/projects/_list.html.haml +++ b/app/views/shared/projects/_list.html.haml @@ -17,5 +17,5 @@ = link_to '#', class: 'js-expand' do Show all -:coffeescript - new ProjectsList() +:javascript + new ProjectsList(); diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 5a15c6c244..3099241218 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -115,5 +115,5 @@ projects: @projects.sort_by(&:star_count).reverse, projects_limit: 10, stars: true, avatar: true -:coffeescript - $(".user-calendar").load("#{user_calendar_path}") +:javascript + $(".user-calendar").load("#{user_calendar_path}"); From d0e3e823a2dd56260550aec648b0cbfae64543ae Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 12 Oct 2015 23:47:32 +0200 Subject: [PATCH 078/140] Implement Build Artifacts - Offloads uploading to GitLab Workhorse - Use /authorize request for fast uploading - Added backup recipes for artifacts - Support download acceleration using X-Sendfile --- .gitignore | 3 + CHANGELOG | 1 + Gemfile | 2 +- Gemfile.lock | 6 +- .../admin/application_settings_controller.rb | 1 + app/controllers/projects/builds_controller.rb | 27 +++ app/models/ability.rb | 1 + app/models/application_setting.rb | 1 + app/models/ci/build.rb | 17 ++ app/models/commit_status.rb | 4 + app/uploaders/artifact_uploader.rb | 50 +++++ .../application_settings/_form.html.haml | 5 + app/views/projects/builds/show.html.haml | 3 + .../commit_statuses/_commit_status.html.haml | 3 + config/initializers/1_settings.rb | 1 + config/routes.rb | 1 + ...1013092124_add_artifacts_file_to_builds.rb | 5 + ..._artifacts_size_to_application_settings.rb | 5 + db/schema.rb | 4 +- doc/ci/yaml/README.md | 15 ++ doc/install/installation.md | 5 + doc/raketasks/backup_restore.md | 3 +- lib/api/helpers.rb | 44 +++++ lib/backup/artifacts.rb | 13 ++ lib/backup/manager.rb | 2 +- lib/ci/api/api.rb | 1 + lib/ci/api/builds.rb | 102 ++++++++++ lib/ci/api/entities.rb | 7 + lib/ci/api/helpers.rb | 11 ++ lib/ci/gitlab_ci_yaml_processor.rb | 9 +- lib/file_streamer.rb | 16 ++ lib/gitlab/current_settings.rb | 1 + lib/support/nginx/gitlab | 16 ++ lib/support/nginx/gitlab-ssl | 16 ++ lib/tasks/gitlab/backup.rake | 21 ++ lib/uploaded_file.rb | 37 ++++ shared/artifacts/.gitkeep | 0 shared/tmp/artifacts-cache/.gitkeep | 0 shared/tmp/artifacts-uploads/.gitkeep | 0 spec/features/builds_spec.rb | 21 ++ spec/features/commits_spec.rb | 16 +- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 38 ++++ spec/models/build_spec.rb | 15 ++ spec/requests/api/users_spec.rb | 15 +- spec/requests/ci/api/builds_spec.rb | 186 +++++++++++++++++- spec/tasks/gitlab/backup_rake_spec.rb | 14 +- 46 files changed, 740 insertions(+), 24 deletions(-) create mode 100644 app/uploaders/artifact_uploader.rb create mode 100644 db/migrate/20151013092124_add_artifacts_file_to_builds.rb create mode 100644 db/migrate/20151109100728_add_max_artifacts_size_to_application_settings.rb create mode 100644 lib/backup/artifacts.rb create mode 100644 lib/file_streamer.rb create mode 100644 lib/uploaded_file.rb create mode 100644 shared/artifacts/.gitkeep create mode 100644 shared/tmp/artifacts-cache/.gitkeep create mode 100644 shared/tmp/artifacts-uploads/.gitkeep diff --git a/.gitignore b/.gitignore index 73bde4cc76..fd137a5047 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,9 @@ nohup.out public/assets/ public/uploads.* public/uploads/ +shared/artifacts/ +shared/tmp/artifacts-uploads/ +shared/tmp/artifacts-cache/ rails_best_practices_output.html /tags tmp/ diff --git a/CHANGELOG b/CHANGELOG index d11076f484..e54e7329ca 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -95,6 +95,7 @@ v 8.1.0 (unreleased) - Show CI status on Your projects page and Starred projects page - Remove "Continuous Integration" page from dashboard - Add notes and SSL verification entries to hook APIs (Ben Boeckel) + - Added build artifacts - Fix grammar in admin area "labels" .nothing-here-block when no labels exist. - Move CI runners page to project settings area - Move CI variables page to project settings area diff --git a/Gemfile b/Gemfile index 51848621b6..49e35e873b 100644 --- a/Gemfile +++ b/Gemfile @@ -54,7 +54,7 @@ gem 'gollum-lib', '~> 4.0.2' gem "github-linguist", "~> 4.7.0", require: "linguist" # API -gem 'grape', '~> 0.6.1' +gem 'grape', '~> 0.13.0' gem 'grape-entity', '~> 0.4.2' gem 'rack-cors', '~> 0.4.0', require: 'rack/cors' diff --git a/Gemfile.lock b/Gemfile.lock index d358e23630..310694ff6a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -306,10 +306,10 @@ GEM gon (5.0.4) actionpack (>= 2.3.0) json - grape (0.6.1) + grape (0.13.0) activesupport builder - hashie (>= 1.2.0) + hashie (>= 2.1.0) multi_json (>= 1.3.2) multi_xml (>= 0.5.2) rack (>= 1.3.0) @@ -829,7 +829,7 @@ DEPENDENCIES gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.0.2) gon (~> 5.0.0) - grape (~> 0.6.1) + grape (~> 0.13.0) grape-entity (~> 0.4.2) haml-rails (~> 0.9.0) hipchat (~> 1.5.0) diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 3d9c59050f..a9bcfc7456 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -58,6 +58,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :admin_notification_email, :user_oauth_applications, :shared_runners_enabled, + :max_artifacts_size, restricted_visibility_levels: [], import_sources: [] ) diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index 953f30e7c0..4638f77b88 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -3,6 +3,7 @@ class Projects::BuildsController < Projects::ApplicationController before_action :build, except: [:index, :cancel_all] before_action :authorize_manage_builds!, except: [:index, :show, :status] + before_action :authorize_download_build_artifacts!, only: [:download] layout "project" @@ -51,6 +52,18 @@ class Projects::BuildsController < Projects::ApplicationController redirect_to build_path(build) end + def download + unless artifacts_file.file_storage? + return redirect_to artifacts_file.url + end + + unless artifacts_file.exists? + return not_found! + end + + send_file artifacts_file.path, disposition: 'attachment' + end + def status render json: @build.to_json(only: [:status, :id, :sha, :coverage], methods: :sha) end @@ -67,6 +80,10 @@ class Projects::BuildsController < Projects::ApplicationController @build ||= ci_project.builds.unscoped.find_by!(id: params[:id]) end + def artifacts_file + build.artifacts_file + end + def build_path(build) namespace_project_build_path(build.gl_project.namespace, build.gl_project, build) end @@ -76,4 +93,14 @@ class Projects::BuildsController < Projects::ApplicationController return page_404 end end + + def authorize_download_build_artifacts! + unless can?(current_user, :download_build_artifacts, @project) + if current_user.nil? + return authenticate_user! + else + return render_404 + end + end + end end diff --git a/app/models/ability.rb b/app/models/ability.rb index b72178fa12..5ae28d5133 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -154,6 +154,7 @@ class Ability :create_merge_request, :create_wiki, :manage_builds, + :download_build_artifacts, :push_code ] end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 266045f7af..fa7cf2464a 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -89,6 +89,7 @@ class ApplicationSetting < ActiveRecord::Base restricted_signup_domains: Settings.gitlab['restricted_signup_domains'], import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'], shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], + max_artifacts_size: Settings.gitlab_ci['max_artifacts_size'], ) end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 7f185ae7cc..0ec7e21032 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -39,6 +39,8 @@ module Ci scope :ignore_failures, ->() { where(allow_failure: false) } scope :similar, ->(build) { where(ref: build.ref, tag: build.tag, trigger_request_id: build.trigger_request_id) } + mount_uploader :artifacts_file, ArtifactUploader + acts_as_taggable # To prevent db load megabytes of data from trace @@ -217,6 +219,14 @@ module Ci "#{dir_to_trace}/#{id}.log" end + def token + project.token + end + + def valid_token? token + project.valid_token? token + end + def target_url Gitlab::Application.routes.url_helpers. namespace_project_build_url(gl_project.namespace, gl_project, self) @@ -248,6 +258,13 @@ module Ci pending? && !any_runners_online? end + def download_url + if artifacts_file.exists? + Gitlab::Application.routes.url_helpers. + download_namespace_project_build_path(gl_project.namespace, gl_project, self) + end + end + private def yaml_variables diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 7d54d83974..d346c5d35d 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -92,4 +92,8 @@ class CommitStatus < ActiveRecord::Base def show_warning? false end + + def download_url + nil + end end diff --git a/app/uploaders/artifact_uploader.rb b/app/uploaders/artifact_uploader.rb new file mode 100644 index 0000000000..848e0bcbde --- /dev/null +++ b/app/uploaders/artifact_uploader.rb @@ -0,0 +1,50 @@ +# encoding: utf-8 +class ArtifactUploader < CarrierWave::Uploader::Base + storage :file + + attr_accessor :build, :field + + def self.artifacts_path + File.expand_path('shared/artifacts/', Rails.root) + end + + def self.artifacts_upload_path + File.expand_path('shared/tmp/artifacts-uploads/', Rails.root) + end + + def self.artifacts_cache_path + File.expand_path('shared/tmp/artifacts-cache/', Rails.root) + end + + def initialize(build, field) + @build, @field = build, field + end + + def artifacts_path + File.join(build.created_at.utc.strftime('%Y_%m'), build.project.id.to_s, build.id.to_s) + end + + def store_dir + File.join(ArtifactUploader.artifacts_path, artifacts_path) + end + + def cache_dir + File.join(ArtifactUploader.artifacts_cache_path, artifacts_path) + end + + def file_storage? + self.class.storage == CarrierWave::Storage::File + end + + def exists? + file.try(:exists?) + end + + def move_to_cache + true + end + + def move_to_store + true + end +end diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 7253218c2e..ddaf0e0e8f 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -139,5 +139,10 @@ = f.check_box :shared_runners_enabled Enable shared runners for a new projects + .form-group + = f.label :max_artifacts_size, 'Maximum artifacts size (MB)', class: 'control-label col-sm-2' + .col-sm-10 + = f.number_field :max_artifacts_size, class: 'form-control' + .form-actions = f.submit 'Save', class: 'btn btn-primary' diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 3374d5432a..7661452e6e 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -87,6 +87,9 @@ Test coverage %h1 #{@build.coverage}% + - if current_user && can?(current_user, :download_build_artifacts, @project) && @build.download_url + .build-widget.center + = link_to "Download artifacts", @build.download_url, class: 'btn btn-sm btn-primary' .build-widget %h4.title diff --git a/app/views/projects/commit_statuses/_commit_status.html.haml b/app/views/projects/commit_statuses/_commit_status.html.haml index c255559b88..9a0e7bff3f 100644 --- a/app/views/projects/commit_statuses/_commit_status.html.haml +++ b/app/views/projects/commit_statuses/_commit_status.html.haml @@ -61,6 +61,9 @@ %td .pull-right + - if current_user && can?(current_user, :download_build_artifacts, @project) && commit_status.download_url + = link_to commit_status.download_url, title: 'Download artifacts' do + %i.fa.fa-download - if current_user && can?(current_user, :manage_builds, commit_status.gl_project) - if commit_status.active? - if commit_status.cancel_url diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 8192d727f2..b39e263e39 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -186,6 +186,7 @@ Settings.gitlab_ci['all_broken_builds'] = true if Settings.gitlab_ci['all_br Settings.gitlab_ci['add_pusher'] = false if Settings.gitlab_ci['add_pusher'].nil? Settings.gitlab_ci['url'] ||= Settings.send(:build_gitlab_ci_url) Settings.gitlab_ci['builds_path'] = File.expand_path(Settings.gitlab_ci['builds_path'] || "builds/", Rails.root) +Settings.gitlab_ci['max_artifacts_size'] ||= 100 # # Reply by email diff --git a/config/routes.rb b/config/routes.rb index 2028ea938e..c892c034f0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -611,6 +611,7 @@ Gitlab::Application.routes.draw do member do get :status post :cancel + get :download post :retry end end diff --git a/db/migrate/20151013092124_add_artifacts_file_to_builds.rb b/db/migrate/20151013092124_add_artifacts_file_to_builds.rb new file mode 100644 index 0000000000..5a299f7b26 --- /dev/null +++ b/db/migrate/20151013092124_add_artifacts_file_to_builds.rb @@ -0,0 +1,5 @@ +class AddArtifactsFileToBuilds < ActiveRecord::Migration + def change + add_column :ci_builds, :artifacts_file, :text + end +end diff --git a/db/migrate/20151109100728_add_max_artifacts_size_to_application_settings.rb b/db/migrate/20151109100728_add_max_artifacts_size_to_application_settings.rb new file mode 100644 index 0000000000..01d8c0f043 --- /dev/null +++ b/db/migrate/20151109100728_add_max_artifacts_size_to_application_settings.rb @@ -0,0 +1,5 @@ +class AddMaxArtifactsSizeToApplicationSettings < ActiveRecord::Migration + def change + add_column :application_settings, :max_artifacts_size, :integer, default: 100, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 116c0c8d97..f631d73f33 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20151105094515) do +ActiveRecord::Schema.define(version: 20151109100728) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -48,6 +48,7 @@ ActiveRecord::Schema.define(version: 20151105094515) do t.text "help_page_text" t.string "admin_notification_email" t.boolean "shared_runners_enabled", default: true, null: false + t.integer "max_artifacts_size", default: 100, null: false end create_table "audit_events", force: true do |t| @@ -108,6 +109,7 @@ ActiveRecord::Schema.define(version: 20151105094515) do t.string "type" t.string "target_url" t.string "description" + t.text "artifacts_file" end add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index d117a2969b..d8504aca86 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -141,6 +141,7 @@ job_name: | tags | optional | Defines a list of tags which are used to select runner | | allow_failure | optional | Allow build to fail. Failed build doesn't contribute to commit status | | when | optional | Define when to run build. Can be `on_success`, `on_failure` or `always` | +| artifacts | optional | Define list build artifacts | ### script `script` is a shell script which is executed by runner. The shell script is prepended with `before_script`. @@ -258,6 +259,20 @@ The above script will: 1. Execute `cleanup_build` only when the `build` failed, 2. Always execute `cleanup` as the last step in pipeline. +### artifacts +`artifacts` is used to specify list of files and directories which should be attached to build after success. + +``` +artifacts: +- binaries/ +- .config +``` + +The above definition will archive all files in `binaries/` and `.config`. +The artifacts will be send after the build success to GitLab and will be accessible in GitLab interface to download. + +This feature requires GitLab Runner v 0.7.0. + ## Validate the .gitlab-ci.yml Each instance of GitLab CI has an embedded debug tool called Lint. You can find the link to the Lint in the project's settings page or use short url `/lint`. diff --git a/doc/install/installation.md b/doc/install/installation.md index f17477a321..0ece8bd931 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -246,6 +246,11 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da # Change the permissions of the directory where CI build traces are stored sudo chmod -R u+rwX builds/ + # Change the permissions of the directory where CI artifacts are stored + sudo chmod -R u+rwX shared/artifacts/ + sudo chmod -R u+rwX shared/tmp/artifacts-uploads/ + sudo chmod -R u+rwX shared/tmp/artifacts-cache/ + # Copy the example Unicorn config sudo -u git -H cp config/unicorn.rb.example config/unicorn.rb diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 606532a6fb..1a5442cdac 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -29,7 +29,8 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` Also you can choose what should be backed up by adding environment variable SKIP. Available options: db, -uploads (attachments), repositories, builds(CI build output logs). Use a comma to specify several options at the same time. +uploads (attachments), repositories, builds(CI build output logs), artifacts (CI build artifacts). +Use a comma to specify several options at the same time. ``` sudo gitlab-rake gitlab:backup:create SKIP=db,uploads diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 652bdf9b27..b980cd8391 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -133,6 +133,12 @@ module API authorize! :admin_project, user_project end + def require_gitlab_workhorse! + unless headers['Gitlab-Git-Http-Server'].present? || headers['GitLab-Git-HTTP-Server'].present? + forbidden!('Request should be executed via GitLab Workhorse') + end + end + def can?(object, action, subject) abilities.allowed?(object, action, subject) end @@ -234,6 +240,10 @@ module API render_api_error!(message || '409 Conflict', 409) end + def file_to_large! + render_api_error!('413 Request Entity Too Large', 413) + end + def render_validation_error!(model) if model.errors.any? render_api_error!(model.errors.messages || '400 Bad Request', 400) @@ -282,6 +292,40 @@ module API end end + # file helpers + + def uploaded_file!(uploads_path) + required_attributes! [:file] + + # sanitize file paths + # this requires for all paths to exist + uploads_path = File.realpath(uploads_path) + file_path = File.realpath(params[:file]) + bad_request!('Bad file path') unless file_path.start_with?(uploads_path) + + UploadedFile.new( + file_path, + params[:filename], + params[:filetype] || 'application/octet-stream', + ) + end + + def present_file!(path, filename, content_type = 'application/octet-stream') + filename ||= File.basename(path) + header['Content-Disposition'] = "attachment; filename=#{filename}" + header['Content-Transfer-Encoding'] = 'binary' + content_type content_type + + # Support download acceleration + case headers['X-Sendfile-Type'] + when 'X-Sendfile' + header['X-Sendfile'] = path + body + else + file FileStreamer.new(path) + end + end + private def add_pagination_headers(paginated, per_page) diff --git a/lib/backup/artifacts.rb b/lib/backup/artifacts.rb new file mode 100644 index 0000000000..51fa3867e6 --- /dev/null +++ b/lib/backup/artifacts.rb @@ -0,0 +1,13 @@ +require 'backup/files' + +module Backup + class Artifacts < Files + def initialize + super('artifacts', ArtifactUploader.artifacts_path) + end + + def create_files_dir + Dir.mkdir(app_files_dir, 0700) + end + end +end diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index f011fd03de..9e15d5411a 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -150,7 +150,7 @@ module Backup private def backup_contents - folders_to_backup + ["uploads.tar.gz", "builds.tar.gz", "backup_information.yml"] + folders_to_backup + ["uploads.tar.gz", "builds.tar.gz", "artifacts.tar.gz", "backup_information.yml"] end def folders_to_backup diff --git a/lib/ci/api/api.rb b/lib/ci/api/api.rb index 0a4cbf69b6..07e68216d7 100644 --- a/lib/ci/api/api.rb +++ b/lib/ci/api/api.rb @@ -27,6 +27,7 @@ module Ci helpers Helpers helpers ::API::Helpers + helpers Gitlab::CurrentSettings mount Builds mount Commits diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb index 83ca1e6481..622849c4b1 100644 --- a/lib/ci/api/builds.rb +++ b/lib/ci/api/builds.rb @@ -47,6 +47,108 @@ module Ci build.drop end end + + # Authorize artifacts uploading for build - Runners only + # + # Parameters: + # id (required) - The ID of a build + # token (required) - The build authorization token + # size (optional) - the size of uploaded file + # Example Request: + # POST /builds/:id/artifacts/authorize + post ":id/artifacts/authorize" do + require_gitlab_workhorse! + build = Ci::Build.find_by_id(params[:id]) + not_found! unless build + authenticate_build_token!(build) + forbidden!('build is not running') unless build.running? + + if params[:filesize] + file_size = params[:filesize].to_i + file_to_large! unless file_size < max_artifacts_size + end + + status 200 + { temp_path: ArtifactUploader.artifacts_upload_path } + end + + # Upload artifacts to build - Runners only + # + # Parameters: + # id (required) - The ID of a build + # token (required) - The build authorization token + # Headers: + # Content-Type - File content type + # Content-Disposition - File media type and real name + # BUILD-TOKEN (required) - The build authorization token, the same as token + # Body: + # The file content + # + # Parameters (set by GitLab Workhorse): + # file - path to locally stored body (generated by Workhorse) + # filename - real filename as send in Content-Disposition + # filetype - real content type as send in Content-Type + # filesize - real file size as send in Content-Length + # Example Request: + # POST /builds/:id/artifacts + post ":id/artifacts" do + require_gitlab_workhorse! + build = Ci::Build.find_by_id(params[:id]) + not_found! unless build + authenticate_build_token!(build) + forbidden!('build is not running') unless build.running? + + file = uploaded_file!(ArtifactUploader.artifacts_upload_path) + file_to_large! unless file.size < max_artifacts_size + + if build.update_attributes(artifacts_file: file) + present build, with: Entities::Build + else + render_validation_error!(build) + end + end + + # Download the artifacts file from build - Runners only + # + # Parameters: + # id (required) - The ID of a build + # token (required) - The build authorization token + # Headers: + # BUILD-TOKEN (required) - The build authorization token, the same as token + # Example Request: + # GET /builds/:id/artifacts + get ":id/artifacts" do + build = Ci::Build.find_by_id(params[:id]) + not_found! unless build + authenticate_build_token!(build) + artifacts_file = build.artifacts_file + + unless artifacts_file.file_storage? + return redirect_to build.artifacts_file.url + end + + unless artifacts_file.exists? + not_found! + end + + present_file!(artifacts_file.path, artifacts_file.filename) + end + + # Remove the artifacts file from build + # + # Parameters: + # id (required) - The ID of a build + # token (required) - The build authorization token + # Headers: + # BUILD-TOKEN (required) - The build authorization token, the same as token + # Example Request: + # DELETE /builds/:id/artifacts + delete ":id/artifacts" do + build = Ci::Build.find_by_id(params[:id]) + not_found! unless build + authenticate_build_token!(build) + build.remove_artifacts_file! + end end end end diff --git a/lib/ci/api/entities.rb b/lib/ci/api/entities.rb index b80c0b8b27..750f421872 100644 --- a/lib/ci/api/entities.rb +++ b/lib/ci/api/entities.rb @@ -11,10 +11,16 @@ module Ci expose :builds end + class ArtifactFile < Grape::Entity + expose :filename, :size + end + class Build < Grape::Entity expose :id, :commands, :ref, :sha, :status, :project_id, :repo_url, :before_sha, :allow_git_fetch, :project_name + expose :name, :token, :stage + expose :options do |model| model.options end @@ -24,6 +30,7 @@ module Ci end expose :variables + expose :artifacts_file, using: ArtifactFile end class Runner < Grape::Entity diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb index 7e4986b6af..0250233375 100644 --- a/lib/ci/api/helpers.rb +++ b/lib/ci/api/helpers.rb @@ -1,6 +1,8 @@ module Ci module API module Helpers + BUILD_TOKEN_HEADER = "HTTP_BUILD_TOKEN" + BUILD_TOKEN_PARAM = :token UPDATE_RUNNER_EVERY = 60 def authenticate_runners! @@ -15,6 +17,11 @@ module Ci forbidden! unless project.valid_token?(params[:project_token]) end + def authenticate_build_token!(build) + token = (params[BUILD_TOKEN_PARAM] || env[BUILD_TOKEN_HEADER]).to_s + forbidden! unless token && build.valid_token?(token) + end + def update_runner_last_contact # Use a random threshold to prevent beating DB updates contacted_at_max_age = UPDATE_RUNNER_EVERY + Random.rand(UPDATE_RUNNER_EVERY) @@ -32,6 +39,10 @@ module Ci info = attributes_for_keys(["name", "version", "revision", "platform", "architecture"], params["info"]) current_runner.update(info) end + + def max_artifacts_size + current_application_settings.max_artifacts_size.megabytes.to_i + end end end end diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 0f57a4f53a..6f9af5388c 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -5,7 +5,7 @@ module Ci DEFAULT_STAGES = %w(build test deploy) DEFAULT_STAGE = 'test' ALLOWED_YAML_KEYS = [:before_script, :image, :services, :types, :stages, :variables] - ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, :allow_failure, :type, :stage, :when] + ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, :allow_failure, :type, :stage, :when, :artifacts] attr_reader :before_script, :image, :services, :variables, :path @@ -77,7 +77,8 @@ module Ci when: job[:when] || 'on_success', options: { image: job[:image] || @image, - services: job[:services] || @services + services: job[:services] || @services, + artifacts: job[:artifacts] }.compact } end @@ -159,6 +160,10 @@ module Ci raise ValidationError, "#{name} job: except parameter should be an array of strings" end + if job[:artifacts] && !validate_array_of_strings(job[:artifacts]) + raise ValidationError, "#{name}: artifacts parameter should be an array of strings" + end + if job[:allow_failure] && !job[:allow_failure].in?([true, false]) raise ValidationError, "#{name} job: allow_failure parameter should be an boolean" end diff --git a/lib/file_streamer.rb b/lib/file_streamer.rb new file mode 100644 index 0000000000..4e3c6d3c77 --- /dev/null +++ b/lib/file_streamer.rb @@ -0,0 +1,16 @@ +class FileStreamer #:nodoc: + attr_reader :to_path + + def initialize(path) + @to_path = path + end + + # Stream the file's contents if Rack::Sendfile isn't present. + def each + File.open(to_path, 'rb') do |file| + while chunk = file.read(16384) + yield chunk + end + end + end +end diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index cd84afa31d..2d3e32d953 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -25,6 +25,7 @@ module Gitlab session_expire_delay: Settings.gitlab['session_expire_delay'], import_sources: Settings.gitlab['import_sources'], shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], + max_artifacts_size: Ci::Settings.gitlab_ci['max_artifacts_size'], ) end diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index e767027dc2..e511d5e4b4 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -131,6 +131,22 @@ server { return 418; } + # Build artifacts should be submitted to this location + location ~ ^/[\w\.-]+/[\w\.-]+/builds/download { + client_max_body_size 0; + # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block + error_page 418 = @gitlab-git-http-server; + return 418; + } + + # Build artifacts should be submitted to this location + location ~ /ci/api/v1/builds/[0-9]+/artifacts { + client_max_body_size 0; + # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block + error_page 418 = @gitlab-git-http-server; + return 418; + } + location @gitlab-workhorse { ## If you use HTTPS make sure you disable gzip compression ## to be safe against BREACH attack. diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index 4d31e31f8d..47b1ec8cb0 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -178,6 +178,22 @@ server { return 418; } + # Build artifacts should be submitted to this location + location ~ ^/[\w\.-]+/[\w\.-]+/builds/download { + client_max_body_size 0; + # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block + error_page 418 = @gitlab-git-http-server; + return 418; + } + + # Build artifacts should be submitted to this location + location ~ /ci/api/v1/builds/[0-9]+/artifacts { + client_max_body_size 0; + # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block + error_page 418 = @gitlab-git-http-server; + return 418; + } + location @gitlab-workhorse { ## If you use HTTPS make sure you disable gzip compression ## to be safe against BREACH attack. diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake index f20c7f71ba..3c46bcea40 100644 --- a/lib/tasks/gitlab/backup.rake +++ b/lib/tasks/gitlab/backup.rake @@ -12,6 +12,7 @@ namespace :gitlab do Rake::Task["gitlab:backup:repo:create"].invoke Rake::Task["gitlab:backup:uploads:create"].invoke Rake::Task["gitlab:backup:builds:create"].invoke + Rake::Task["gitlab:backup:artifacts:create"].invoke backup = Backup::Manager.new backup.pack @@ -32,6 +33,7 @@ namespace :gitlab do Rake::Task["gitlab:backup:repo:restore"].invoke unless backup.skipped?("repositories") Rake::Task["gitlab:backup:uploads:restore"].invoke unless backup.skipped?("uploads") Rake::Task["gitlab:backup:builds:restore"].invoke unless backup.skipped?("builds") + Rake::Task["gitlab:backup:artifacts:restore"].invoke unless backup.skipped?("artifacts") Rake::Task["gitlab:shell:setup"].invoke backup.cleanup @@ -113,6 +115,25 @@ namespace :gitlab do end end + namespace :artifacts do + task create: :environment do + $progress.puts "Dumping artifacts ... ".blue + + if ENV["SKIP"] && ENV["SKIP"].include?("artifacts") + $progress.puts "[SKIPPED]".cyan + else + Backup::Artifacts.new.dump + $progress.puts "done".green + end + end + + task restore: :environment do + $progress.puts "Restoring artifacts ... ".blue + Backup::Artifacts.new.restore + $progress.puts "done".green + end + end + def configure_cron_mode if ENV['CRON'] # We need an object we can say 'puts' and 'print' to; let's use a diff --git a/lib/uploaded_file.rb b/lib/uploaded_file.rb new file mode 100644 index 0000000000..d4291f012d --- /dev/null +++ b/lib/uploaded_file.rb @@ -0,0 +1,37 @@ +require "tempfile" +require "fileutils" + +# Taken from: Rack::Test::UploadedFile +class UploadedFile + + # The filename, *not* including the path, of the "uploaded" file + attr_reader :original_filename + + # The tempfile + attr_reader :tempfile + + # The content type of the "uploaded" file + attr_accessor :content_type + + def initialize(path, filename, content_type = "text/plain") + raise "#{path} file does not exist" unless ::File.exist?(path) + + @content_type = content_type + @original_filename = filename || ::File.basename(path) + @tempfile = File.new(path, 'rb') + end + + def path + @tempfile.path + end + + alias_method :local_path, :path + + def method_missing(method_name, *args, &block) #:nodoc: + @tempfile.__send__(method_name, *args, &block) + end + + def respond_to?(method_name, include_private = false) #:nodoc: + @tempfile.respond_to?(method_name, include_private) || super + end +end diff --git a/shared/artifacts/.gitkeep b/shared/artifacts/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/shared/tmp/artifacts-cache/.gitkeep b/shared/tmp/artifacts-cache/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/shared/tmp/artifacts-uploads/.gitkeep b/shared/tmp/artifacts-uploads/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb index 158e85e598..5213ce1099 100644 --- a/spec/features/builds_spec.rb +++ b/spec/features/builds_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe "Builds" do + let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } + before do login_as(:user) @commit = FactoryGirl.create :ci_commit @@ -66,6 +68,15 @@ describe "Builds" do it { expect(page).to have_content @commit.sha[0..7] } it { expect(page).to have_content @commit.git_commit_message } it { expect(page).to have_content @commit.git_author_name } + + context "Download artifacts" do + before do + @build.update_attributes(artifacts_file: artifacts_file) + visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build) + end + + it { expect(page).to have_content 'Download artifacts' } + end end describe "POST /:project/builds/:id/cancel" do @@ -90,4 +101,14 @@ describe "Builds" do it { expect(page).to have_content 'pending' } it { expect(page).to have_content 'Cancel' } end + + describe "GET /:project/builds/:id/download" do + before do + @build.update_attributes(artifacts_file: artifacts_file) + visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build) + click_link 'Download artifacts' + end + + it { expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type) } + end end diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index 340924fafe..90739cd6a2 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -19,7 +19,7 @@ describe "Commits" do stub_ci_commit_to_return_yaml_file end - describe "GET /:project/commits/:sha" do + describe "GET /:project/commits/:sha/ci" do before do visit ci_status_path(@commit) end @@ -29,6 +29,20 @@ describe "Commits" do it { expect(page).to have_content @commit.git_author_name } end + context "Download artifacts" do + let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } + + before do + @build.update_attributes(artifacts_file: artifacts_file) + end + + it do + visit ci_status_path(@commit) + click_on "Download artifacts" + expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type) + end + end + describe "Cancel all builds" do it "cancels commit" do visit ci_status_path(@commit) diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 9963f76f99..5e3779af19 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -333,6 +333,37 @@ module Ci end end + describe "Artifacts" do + it "returns artifacts when defined" do + config = YAML.dump({ + image: "ruby:2.1", + services: ["mysql"], + before_script: ["pwd"], + rspec: { artifacts: ["logs/", "binaries/"], script: "rspec" } + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) + expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({ + except: nil, + stage: "test", + stage_idx: 1, + name: :rspec, + only: nil, + commands: "pwd\nrspec", + tag_list: [], + options: { + image: "ruby:2.1", + services: ["mysql"], + artifacts: ["logs/", "binaries/"] + }, + when: "on_success", + allow_failure: false + }) + end + end + describe "Error handling" do it "indicates that object is invalid" do expect{GitlabCiYamlProcessor.new("invalid_yaml\n!ccdvlf%612334@@@@")}.to raise_error(GitlabCiYamlProcessor::ValidationError) @@ -491,6 +522,13 @@ module Ci GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure or always") end + + it "returns errors if job artifacts is not an array of strings" do + config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: "string" } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts parameter should be an array of strings") + end end end end diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 7f5abb83ac..839b4c6b16 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -400,4 +400,19 @@ describe Ci::Build do end end end + + describe :download_url do + subject { build.download_url } + + it "should be nil if artifact doesn't exist" do + build.update_attributes(artifacts_file: nil) + is_expected.to be_nil + end + + it 'should be nil if artifact exist' do + gif = fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') + build.update_attributes(artifacts_file: gif) + is_expected.to_not be_nil + end + end end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index d26a300ed8..a9ef2fe588 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -343,8 +343,9 @@ describe API::API, api: true do end.to change{ user.keys.count }.by(1) end - it "should raise error for invalid ID" do - expect{post api("/users/ASDF/keys", admin) }.to raise_error(ActionController::RoutingError) + it "should return 405 for invalid ID" do + post api("/users/ASDF/keys", admin) + expect(response.status).to eq(405) end end @@ -374,9 +375,9 @@ describe API::API, api: true do expect(json_response.first['title']).to eq(key.title) end - it "should return 404 for invalid ID" do + it "should return 405 for invalid ID" do get api("/users/ASDF/keys", admin) - expect(response.status).to eq(404) + expect(response.status).to eq(405) end end end @@ -434,7 +435,8 @@ describe API::API, api: true do end it "should raise error for invalid ID" do - expect{post api("/users/ASDF/emails", admin) }.to raise_error(ActionController::RoutingError) + post api("/users/ASDF/emails", admin) + expect(response.status).to eq(405) end end @@ -465,7 +467,8 @@ describe API::API, api: true do end it "should raise error for invalid ID" do - expect{put api("/users/ASDF/emails", admin) }.to raise_error(ActionController::RoutingError) + put api("/users/ASDF/emails", admin) + expect(response.status).to eq(405) end end end diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 88218a93e1..92ea25a372 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -41,7 +41,7 @@ describe Ci::API::API do it "should return 404 error if no builds for specific runner" do commit = FactoryGirl.create(:ci_commit, gl_project: shared_gl_project) - FactoryGirl.create(:ci_build, commit: commit, status: 'pending' ) + FactoryGirl.create(:ci_build, commit: commit, status: 'pending') post ci_api("/builds/register"), token: runner.token @@ -50,7 +50,7 @@ describe Ci::API::API do it "should return 404 error if no builds for shared runner" do commit = FactoryGirl.create(:ci_commit, gl_project: gl_project) - FactoryGirl.create(:ci_build, commit: commit, status: 'pending' ) + FactoryGirl.create(:ci_build, commit: commit, status: 'pending') post ci_api("/builds/register"), token: shared_runner.token @@ -79,7 +79,7 @@ describe Ci::API::API do { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true }, { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true }, { "key" => "DB_NAME", "value" => "postgres", "public" => true }, - { "key" => "SECRET_KEY", "value" => "secret_value", "public" => false }, + { "key" => "SECRET_KEY", "value" => "secret_value", "public" => false } ]) end @@ -122,5 +122,185 @@ describe Ci::API::API do expect(build.reload.trace).to eq 'hello_world' end end + + context "Artifacts" do + let(:file_upload) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } + let(:file_upload2) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/gif') } + let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) } + let(:build) { FactoryGirl.create(:ci_build, commit: commit, runner_id: runner.id) } + let(:authorize_url) { ci_api("/builds/#{build.id}/artifacts/authorize") } + let(:post_url) { ci_api("/builds/#{build.id}/artifacts") } + let(:delete_url) { ci_api("/builds/#{build.id}/artifacts") } + let(:get_url) { ci_api("/builds/#{build.id}/artifacts") } + let(:headers) { { "Gitlab-Git-Http-Server" => "1.0" } } + let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => build.project.token) } + + describe "POST /builds/:id/artifacts/authorize" do + context "should authorize posting artifact to running build" do + before do + build.run! + end + + it "using token as parameter" do + post authorize_url, { token: build.project.token }, headers + expect(response.status).to eq(200) + expect(json_response["temp_path"]).to_not be_nil + end + + it "using token as header" do + post authorize_url, {}, headers_with_token + expect(response.status).to eq(200) + expect(json_response["temp_path"]).to_not be_nil + end + end + + context "should fail to post too large artifact" do + before do + build.run! + end + + it "using token as parameter" do + settings = Gitlab::CurrentSettings::current_application_settings + settings.update_attributes(max_artifacts_size: 0) + post authorize_url, { token: build.project.token, filesize: 100 }, headers + expect(response.status).to eq(413) + end + + it "using token as header" do + settings = Gitlab::CurrentSettings::current_application_settings + settings.update_attributes(max_artifacts_size: 0) + post authorize_url, { filesize: 100 }, headers_with_token + expect(response.status).to eq(413) + end + end + + context "should get denied" do + it do + post authorize_url, { token: 'invalid', filesize: 100 } + expect(response.status).to eq(403) + end + end + end + + describe "POST /builds/:id/artifacts" do + context "Disable sanitizer" do + before do + # by configuring this path we allow to pass temp file from any path + allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return('/') + end + + context "should post artifact to running build" do + before do + build.run! + end + + it do + upload_artifacts(file_upload, headers_with_token) + expect(response.status).to eq(201) + expect(json_response["artifacts_file"]["filename"]).to eq(file_upload.original_filename) + end + + it "updates artifact" do + upload_artifacts(file_upload, headers_with_token) + upload_artifacts(file_upload2, headers_with_token) + expect(response.status).to eq(201) + expect(json_response["artifacts_file"]["filename"]).to eq(file_upload2.original_filename) + end + end + + context "should fail to post too large artifact" do + before do + build.run! + end + + it do + settings = Gitlab::CurrentSettings::current_application_settings + settings.update_attributes(max_artifacts_size: 0) + upload_artifacts(file_upload, headers_with_token) + expect(response.status).to eq(413) + end + end + + context "should fail to post artifacts without file" do + before do + build.run! + end + + it do + post post_url, {}, headers_with_token + expect(response.status).to eq(400) + end + end + + context "should fail to post artifacts without GitLab-Workhorse" do + before do + build.run! + end + + it do + post post_url, { token: build.project.token }, {} + expect(response.status).to eq(403) + end + end + end + + context "should fail to post artifacts for outside of tmp path" do + before do + # by configuring this path we allow to pass file from @tmpdir only + # but all temporary files are stored in system tmp directory + @tmpdir = Dir.mktmpdir + allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return(@tmpdir) + build.run! + end + + after do + FileUtils.remove_entry @tmpdir + end + + it do + upload_artifacts(file_upload, headers_with_token) + expect(response.status).to eq(400) + end + end + + def upload_artifacts(file, headers = {}) + params = { + file: file.path, + filename: file.original_filename, + } + post post_url, params, headers + end + end + + describe "DELETE /builds/:id/artifacts" do + before do + build.run! + post delete_url, token: build.project.token, file: file_upload + end + + it "should delete artifact build" do + build.success + delete delete_url, token: build.project.token + expect(response.status).to eq(200) + end + end + + describe "GET /builds/:id/artifacts" do + before do + build.run! + end + + it "should download artifact" do + build.update_attributes(artifacts_file: file_upload) + get get_url, token: build.project.token + expect(response.status).to eq(200) + end + + it "should fail to download if no artifact uploaded" do + get get_url, token: build.project.token + expect(response.status).to eq(404) + end + end + end end end diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 386ac9c837..06559c3925 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -16,7 +16,7 @@ describe 'gitlab:app namespace rake task' do end def reenable_backup_sub_tasks - %w{db repo uploads builds}.each do |subtask| + %w{db repo uploads builds artifacts}.each do |subtask| Rake::Task["gitlab:backup:#{subtask}:create"].reenable end end @@ -56,6 +56,7 @@ describe 'gitlab:app namespace rake task' do expect(Rake::Task["gitlab:backup:repo:restore"]).to receive(:invoke) expect(Rake::Task["gitlab:backup:builds:restore"]).to receive(:invoke) expect(Rake::Task["gitlab:backup:uploads:restore"]).to receive(:invoke) + expect(Rake::Task["gitlab:backup:artifacts:restore"]).to receive(:invoke) expect(Rake::Task["gitlab:shell:setup"]).to receive(:invoke) expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error end @@ -113,19 +114,20 @@ describe 'gitlab:app namespace rake task' do it 'should set correct permissions on the tar contents' do tar_contents, exit_status = Gitlab::Popen.popen( - %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz} + %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz} ) expect(exit_status).to eq(0) expect(tar_contents).to match('db/') expect(tar_contents).to match('uploads.tar.gz') expect(tar_contents).to match('repositories/') expect(tar_contents).to match('builds.tar.gz') - expect(tar_contents).not_to match(/^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz)\/$/) + expect(tar_contents).to match('artifacts.tar.gz') + expect(tar_contents).not_to match(/^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|artifacts.tar.gz)\/$/) end it 'should delete temp directories' do temp_dirs = Dir.glob( - File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds}') + File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts}') ) expect(temp_dirs).to be_empty @@ -161,12 +163,13 @@ describe 'gitlab:app namespace rake task' do it "does not contain skipped item" do tar_contents, _exit_status = Gitlab::Popen.popen( - %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz} + %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz} ) expect(tar_contents).to match('db/') expect(tar_contents).to match('uploads.tar.gz') expect(tar_contents).to match('builds.tar.gz') + expect(tar_contents).to match('artifacts.tar.gz') expect(tar_contents).not_to match('repositories/') end @@ -178,6 +181,7 @@ describe 'gitlab:app namespace rake task' do expect(Rake::Task["gitlab:backup:db:restore"]).to receive :invoke expect(Rake::Task["gitlab:backup:repo:restore"]).not_to receive :invoke expect(Rake::Task["gitlab:backup:builds:restore"]).to receive :invoke + expect(Rake::Task["gitlab:backup:artifacts:restore"]).to receive :invoke expect(Rake::Task["gitlab:shell:setup"]).to receive :invoke expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error end From 97f58bae87dfcfb36d5a7a490b1c0983435a19f4 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 9 Nov 2015 12:18:00 +0100 Subject: [PATCH 079/140] Change artifacts syntax to allow uploading untracked files --- doc/ci/yaml/README.md | 21 +++++++++++++++--- lib/ci/gitlab_ci_yaml_processor.rb | 16 +++++++++++--- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 23 +++++++++++++++----- 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index d8504aca86..11065fee01 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -262,13 +262,28 @@ The above script will: ### artifacts `artifacts` is used to specify list of files and directories which should be attached to build after success. +1. Send all files in `binaries` and `.config`: ``` artifacts: -- binaries/ -- .config + paths: + - binaries/ + - .config +``` + +2. Send all git untracked files: +``` +artifacts: + untracked: true +``` + +3. Send all git untracked files and files in `binaries`: +``` +artifacts: + untracked: true + paths: + - binaries/ ``` -The above definition will archive all files in `binaries/` and `.config`. The artifacts will be send after the build success to GitLab and will be accessible in GitLab interface to download. This feature requires GitLab Runner v 0.7.0. diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 6f9af5388c..2e2209031e 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -160,11 +160,17 @@ module Ci raise ValidationError, "#{name} job: except parameter should be an array of strings" end - if job[:artifacts] && !validate_array_of_strings(job[:artifacts]) - raise ValidationError, "#{name}: artifacts parameter should be an array of strings" + if job[:artifacts] + if job[:artifacts][:untracked] && !validate_boolean(job[:artifacts][:untracked]) + raise ValidationError, "#{name} job: artifacts:untracked parameter should be an boolean" + end + + if job[:artifacts][:paths] && !validate_array_of_strings(job[:artifacts][:paths]) + raise ValidationError, "#{name} job: artifacts:paths parameter should be an array of strings" + end end - if job[:allow_failure] && !job[:allow_failure].in?([true, false]) + if job[:allow_failure] && !validate_boolean(job[:allow_failure]) raise ValidationError, "#{name} job: allow_failure parameter should be an boolean" end @@ -187,6 +193,10 @@ module Ci value.is_a?(String) || value.is_a?(Symbol) end + def validate_boolean(value) + value.in?([true, false]) + end + def process?(only_params, except_params, ref, tag) if only_params.present? return false unless matching?(only_params, ref, tag) diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 5e3779af19..29fc271382 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -339,7 +339,10 @@ module Ci image: "ruby:2.1", services: ["mysql"], before_script: ["pwd"], - rspec: { artifacts: ["logs/", "binaries/"], script: "rspec" } + rspec: { + artifacts: { paths: ["logs/", "binaries/"], untracked: true }, + script: "rspec" + } }) config_processor = GitlabCiYamlProcessor.new(config) @@ -356,7 +359,10 @@ module Ci options: { image: "ruby:2.1", services: ["mysql"], - artifacts: ["logs/", "binaries/"] + artifacts: { + paths: ["logs/", "binaries/"], + untracked: true + } }, when: "on_success", allow_failure: false @@ -523,11 +529,18 @@ module Ci end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure or always") end - it "returns errors if job artifacts is not an array of strings" do - config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: "string" } }) + it "returns errors if job artifacts:untracked is not an array of strings" do + config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { untracked: "string" } } }) expect do GitlabCiYamlProcessor.new(config) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts parameter should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:untracked parameter should be an boolean") + end + + it "returns errors if job artifacts:paths is not an array of strings" do + config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { paths: "string" } } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:paths parameter should be an array of strings") end end end From 445cdb7579792d0d76c2562b971583bd7d05429b Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 9 Nov 2015 12:49:47 +0100 Subject: [PATCH 080/140] Move tmp artifacts to shared/artifacts/tmp/. Check for GitLab-Workhorse now --- .gitignore | 2 -- app/uploaders/artifact_uploader.rb | 4 ++-- doc/install/installation.md | 2 -- lib/api/helpers.rb | 2 +- shared/{tmp/artifacts-cache => artifacts/tmp/cache}/.gitkeep | 0 .../{tmp/artifacts-uploads => artifacts/tmp/uploads}/.gitkeep | 0 spec/requests/ci/api/builds_spec.rb | 2 +- 7 files changed, 4 insertions(+), 8 deletions(-) rename shared/{tmp/artifacts-cache => artifacts/tmp/cache}/.gitkeep (100%) rename shared/{tmp/artifacts-uploads => artifacts/tmp/uploads}/.gitkeep (100%) diff --git a/.gitignore b/.gitignore index fd137a5047..39ff95c50e 100644 --- a/.gitignore +++ b/.gitignore @@ -38,8 +38,6 @@ public/assets/ public/uploads.* public/uploads/ shared/artifacts/ -shared/tmp/artifacts-uploads/ -shared/tmp/artifacts-cache/ rails_best_practices_output.html /tags tmp/ diff --git a/app/uploaders/artifact_uploader.rb b/app/uploaders/artifact_uploader.rb index 848e0bcbde..b4e0fc5772 100644 --- a/app/uploaders/artifact_uploader.rb +++ b/app/uploaders/artifact_uploader.rb @@ -9,11 +9,11 @@ class ArtifactUploader < CarrierWave::Uploader::Base end def self.artifacts_upload_path - File.expand_path('shared/tmp/artifacts-uploads/', Rails.root) + File.expand_path('shared/artifacts/tmp/uploads/', Rails.root) end def self.artifacts_cache_path - File.expand_path('shared/tmp/artifacts-cache/', Rails.root) + File.expand_path('shared/artifacts/tmp/cache/', Rails.root) end def initialize(build, field) diff --git a/doc/install/installation.md b/doc/install/installation.md index 0ece8bd931..8028e51dbc 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -248,8 +248,6 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da # Change the permissions of the directory where CI artifacts are stored sudo chmod -R u+rwX shared/artifacts/ - sudo chmod -R u+rwX shared/tmp/artifacts-uploads/ - sudo chmod -R u+rwX shared/tmp/artifacts-cache/ # Copy the example Unicorn config sudo -u git -H cp config/unicorn.rb.example config/unicorn.rb diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index b980cd8391..077537959d 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -134,7 +134,7 @@ module API end def require_gitlab_workhorse! - unless headers['Gitlab-Git-Http-Server'].present? || headers['GitLab-Git-HTTP-Server'].present? + unless env['HTTP_GITLAB_WORKHORSE'].present? forbidden!('Request should be executed via GitLab Workhorse') end end diff --git a/shared/tmp/artifacts-cache/.gitkeep b/shared/artifacts/tmp/cache/.gitkeep similarity index 100% rename from shared/tmp/artifacts-cache/.gitkeep rename to shared/artifacts/tmp/cache/.gitkeep diff --git a/shared/tmp/artifacts-uploads/.gitkeep b/shared/artifacts/tmp/uploads/.gitkeep similarity index 100% rename from shared/tmp/artifacts-uploads/.gitkeep rename to shared/artifacts/tmp/uploads/.gitkeep diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 92ea25a372..0076730ef2 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -132,7 +132,7 @@ describe Ci::API::API do let(:post_url) { ci_api("/builds/#{build.id}/artifacts") } let(:delete_url) { ci_api("/builds/#{build.id}/artifacts") } let(:get_url) { ci_api("/builds/#{build.id}/artifacts") } - let(:headers) { { "Gitlab-Git-Http-Server" => "1.0" } } + let(:headers) { { "GitLab-Workhorse" => "1.0" } } let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => build.project.token) } describe "POST /builds/:id/artifacts/authorize" do From 7e4e3fb3b6177d2863580786e9ef6ee3a4ccf037 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 9 Nov 2015 12:55:32 +0100 Subject: [PATCH 081/140] Fix nginx config to use @gitlab-workhorse --- lib/support/nginx/gitlab | 8 ++++---- lib/support/nginx/gitlab-ssl | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index e511d5e4b4..0a7a411807 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -134,16 +134,16 @@ server { # Build artifacts should be submitted to this location location ~ ^/[\w\.-]+/[\w\.-]+/builds/download { client_max_body_size 0; - # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block - error_page 418 = @gitlab-git-http-server; + # 'Error' 418 is a hack to re-use the @gitlab-workhorse block + error_page 418 = @gitlab-workhorse; return 418; } # Build artifacts should be submitted to this location location ~ /ci/api/v1/builds/[0-9]+/artifacts { client_max_body_size 0; - # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block - error_page 418 = @gitlab-git-http-server; + # 'Error' 418 is a hack to re-use the @gitlab-workhorse block + error_page 418 = @gitlab-workhorse; return 418; } diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index 47b1ec8cb0..b463d5b6aa 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -181,16 +181,16 @@ server { # Build artifacts should be submitted to this location location ~ ^/[\w\.-]+/[\w\.-]+/builds/download { client_max_body_size 0; - # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block - error_page 418 = @gitlab-git-http-server; + # 'Error' 418 is a hack to re-use the @gitlab-workhorse block + error_page 418 = @gitlab-workhorse; return 418; } # Build artifacts should be submitted to this location location ~ /ci/api/v1/builds/[0-9]+/artifacts { client_max_body_size 0; - # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block - error_page 418 = @gitlab-git-http-server; + # 'Error' 418 is a hack to re-use the @gitlab-workhorse block + error_page 418 = @gitlab-workhorse; return 418; } From db3213fc1c653b20783f9a41074eaf17132010de Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 9 Nov 2015 22:01:26 +0100 Subject: [PATCH 082/140] Use normal file upload mechanism to upload artifacts --- lib/api/helpers.rb | 16 ++++++++++------ lib/ci/api/builds.rb | 16 +++++++--------- spec/requests/ci/api/builds_spec.rb | 25 +++++++++++++++++-------- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 077537959d..92540ccf2b 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -294,19 +294,23 @@ module API # file helpers - def uploaded_file!(uploads_path) - required_attributes! [:file] + def uploaded_file!(field, uploads_path) + if params[field] + bad_request!("#{field} is not a file") unless params[field].respond_to?(:filename) + return params[field] + end # sanitize file paths - # this requires for all paths to exist + # this requires all paths to exist + required_attributes! %W(#{field}.path) uploads_path = File.realpath(uploads_path) - file_path = File.realpath(params[:file]) + file_path = File.realpath(params["#{field}.path"]) bad_request!('Bad file path') unless file_path.start_with?(uploads_path) UploadedFile.new( file_path, - params[:filename], - params[:filetype] || 'application/octet-stream', + params["#{field}.name"], + params["#{field}.type"] || 'application/octet-stream', ) end diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb index 622849c4b1..dab0df1263 100644 --- a/lib/ci/api/builds.rb +++ b/lib/ci/api/builds.rb @@ -53,7 +53,7 @@ module Ci # Parameters: # id (required) - The ID of a build # token (required) - The build authorization token - # size (optional) - the size of uploaded file + # filesize (optional) - the size of uploaded file # Example Request: # POST /builds/:id/artifacts/authorize post ":id/artifacts/authorize" do @@ -77,18 +77,16 @@ module Ci # Parameters: # id (required) - The ID of a build # token (required) - The build authorization token + # file (required) - The uploaded file + # Parameters (accelerated by GitLab Workhorse): + # file.path - path to locally stored body (generated by Workhorse) + # file.name - real filename as send in Content-Disposition + # file.type - real content type as send in Content-Type # Headers: - # Content-Type - File content type - # Content-Disposition - File media type and real name # BUILD-TOKEN (required) - The build authorization token, the same as token # Body: # The file content # - # Parameters (set by GitLab Workhorse): - # file - path to locally stored body (generated by Workhorse) - # filename - real filename as send in Content-Disposition - # filetype - real content type as send in Content-Type - # filesize - real file size as send in Content-Length # Example Request: # POST /builds/:id/artifacts post ":id/artifacts" do @@ -98,7 +96,7 @@ module Ci authenticate_build_token!(build) forbidden!('build is not running') unless build.running? - file = uploaded_file!(ArtifactUploader.artifacts_upload_path) + file = uploaded_file!(:file, ArtifactUploader.artifacts_upload_path) file_to_large! unless file.size < max_artifacts_size if build.update_attributes(artifacts_file: file) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 0076730ef2..233c15f87f 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -194,8 +194,14 @@ describe Ci::API::API do build.run! end - it do - upload_artifacts(file_upload, headers_with_token) + it "uses regual file post" do + upload_artifacts(file_upload, headers_with_token, false) + expect(response.status).to eq(201) + expect(json_response["artifacts_file"]["filename"]).to eq(file_upload.original_filename) + end + + it "uses accelerated file post" do + upload_artifacts(file_upload, headers_with_token, true) expect(response.status).to eq(201) expect(json_response["artifacts_file"]["filename"]).to eq(file_upload.original_filename) end @@ -263,12 +269,15 @@ describe Ci::API::API do end end - def upload_artifacts(file, headers = {}) - params = { - file: file.path, - filename: file.original_filename, - } - post post_url, params, headers + def upload_artifacts(file, headers = {}, accelerated = true) + if accelerated + post post_url, { + 'file.path' => file.path, + 'file.name' => file.original_filename + }, headers + else + post post_url, { file: file }, headers + end end end From 99c05363ea8ff53cfa9620b626325773d17bb575 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 10 Nov 2015 07:19:31 -0800 Subject: [PATCH 083/140] Remove CSS property preventing hard tabs from rendering in Chromium 45 This is to workaround a bug in Chromium 45 (https://code.google.com/p/chromium/issues/detail?id=446434), which is the default browser in Ubuntu 14.04 and older. Closes #3220 --- CHANGELOG | 1 + app/assets/stylesheets/framework/typography.scss | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 668ea87b28..c6bacab6a5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.2.0 (unreleased) + - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu) - Added a GitLab specific profiling tool called "Sherlock" (see GitLab CE merge request #1749) - Upgrade gitlab_git to 7.2.20 and rugged to 0.23.3 (Stan Hu) - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index e6558a2385..ba0312ba0d 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -173,7 +173,6 @@ * */ body { - text-rendering:optimizeLegibility; -webkit-text-shadow: rgba(255,255,255,0.01) 0 0 1px; } From 802b8fceb444f02fb8c2f67c5c433832baa33be7 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 10 Nov 2015 16:27:50 +0100 Subject: [PATCH 084/140] Fix graph description and text Signed-off-by: Dmitriy Zaporozhets --- app/views/projects/graphs/ci/_builds.haml | 4 ++-- features/steps/project/graph.rb | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/projects/graphs/ci/_builds.haml b/app/views/projects/graphs/ci/_builds.haml index 8424745540..8fca07114f 100644 --- a/app/views/projects/graphs/ci/_builds.haml +++ b/app/views/projects/graphs/ci/_builds.haml @@ -17,13 +17,13 @@ .prepend-top-default %p.light - Builds chart for last month + Builds for last month (#{date_from_to(Date.today - 30.days, Date.today)}) %canvas#monthChart{height: 200} .prepend-top-default %p.light - Builds chart for last year + Builds for last year %canvas#yearChart.padded{height: 250} - [:week, :month, :year].each do |scope| diff --git a/features/steps/project/graph.rb b/features/steps/project/graph.rb index 4abd5288d5..98f31f3b76 100644 --- a/features/steps/project/graph.rb +++ b/features/steps/project/graph.rb @@ -25,9 +25,9 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps step 'page should have CI graphs' do expect(page).to have_content 'Overall' - expect(page).to have_content 'Builds chart for last week' - expect(page).to have_content 'Builds chart for last month' - expect(page).to have_content 'Builds chart for last year' + expect(page).to have_content 'Builds for last week' + expect(page).to have_content 'Builds for last month' + expect(page).to have_content 'Builds for last year' expect(page).to have_content 'Commit duration in minutes for last 30 commits' end From 78966d4ca635506ee5323a88423275bc44d84a49 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 10 Nov 2015 07:36:10 -0800 Subject: [PATCH 085/140] Bump rugged version to be in line with gitlab_git requirements --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 1688ea4d9d..aae1ec7610 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -611,7 +611,7 @@ GEM sexp_processor (~> 4.1) rubyntlm (0.5.2) rubypants (0.2.0) - rugged (0.22.2) + rugged (0.23.3) safe_yaml (1.0.4) sanitize (2.1.0) nokogiri (>= 1.4.4) From ffa8d84a6ef27dea6d87650390821d42f3d1b38a Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 10 Nov 2015 07:56:05 -0800 Subject: [PATCH 086/140] Bump charlock_holmes version --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index aae1ec7610..dca8606806 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -107,7 +107,7 @@ GEM json (>= 1.7) celluloid (0.16.0) timers (~> 4.0.0) - charlock_holmes (0.6.9.4) + charlock_holmes (0.7.3) chunky_png (1.3.4) cliver (0.3.2) coderay (1.1.0) From b0452f26c0c3274d9d16c4b7e6f170737de4e31e Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 10 Nov 2015 08:16:33 -0800 Subject: [PATCH 087/140] Remove duplicate CHANGELOG etnries [ci skip] --- CHANGELOG | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 668ea87b28..168c4b50e4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,8 +3,6 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.2.0 (unreleased) - Added a GitLab specific profiling tool called "Sherlock" (see GitLab CE merge request #1749) - Upgrade gitlab_git to 7.2.20 and rugged to 0.23.3 (Stan Hu) - - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) - - Force update refs/merge-requests/X/head upon a push to the source branch of a merge request (Stan Hu) - Improved performance of finding users by one of their Email addresses - Improved performance of replacing references in comments - Show last project commit to default branch on project home page From 58429d9b26b2f2b62fecab012fce0ebe36571129 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 10 Nov 2015 18:34:05 +0100 Subject: [PATCH 088/140] Add method complexity check to CI Signed-off-by: Dmitriy Zaporozhets --- .gitlab-ci.yml | 7 +++++++ Gemfile | 1 + Gemfile.lock | 4 ++++ lib/tasks/flog.rake | 24 ++++++++++++++++++++++++ 4 files changed, 36 insertions(+) create mode 100644 lib/tasks/flog.rake diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cf6d28b01a..476dabe5e3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -73,3 +73,10 @@ brakeman: tags: - ruby - mysql + +flog: + script: + - bundle exec rake flog + tags: + - ruby + - mysql diff --git a/Gemfile b/Gemfile index c73aa26bd0..91a9321533 100644 --- a/Gemfile +++ b/Gemfile @@ -259,6 +259,7 @@ group :development, :test do gem 'rubocop', '~> 0.28.0', require: false gem 'coveralls', '~> 0.8.2', require: false gem 'simplecov', '~> 0.10.0', require: false + gem 'flog', require: false gem 'benchmark-ips', require: false end diff --git a/Gemfile.lock b/Gemfile.lock index dca8606806..0fc22829cf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -194,6 +194,9 @@ GEM ffi (1.9.10) fission (0.5.0) CFPropertyList (~> 2.2) + flog (4.3.2) + ruby_parser (~> 3.1, > 3.1.0) + sexp_processor (~> 4.4) flowdock (0.7.0) httparty (~> 0.7) multi_json @@ -821,6 +824,7 @@ DEPENDENCIES enumerize (~> 0.7.0) factory_girl_rails (~> 4.3.0) ffaker (~> 2.0.0) + flog fog (~> 1.25.0) font-awesome-rails (~> 4.2) foreman diff --git a/lib/tasks/flog.rake b/lib/tasks/flog.rake new file mode 100644 index 0000000000..4cb19c0c93 --- /dev/null +++ b/lib/tasks/flog.rake @@ -0,0 +1,24 @@ +desc 'Code complexity analyze via flog' +task :flog do + output = %x(bundle exec flog -m app/ lib/gitlab) + exit_code = 0 + output = output.lines + + # Skip total complexity score + output.shift + + # Skip some trash info + output.shift + + output.each do |line| + score, method = line.split(" ") + score = score.to_i + + if score > 40 + exit_code = 1 + puts "High complexity in #{method}. Score: #{score}" + end + end + + exit exit_code +end From afcced01e579cb75d876c4612fc09e87d1502da6 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 10 Nov 2015 13:15:18 -0500 Subject: [PATCH 089/140] Remove text-rendering property Closes #3220 [ci skip] --- app/assets/stylesheets/framework/layout.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss index c7b3b60e76..b91c15d891 100644 --- a/app/assets/stylesheets/framework/layout.scss +++ b/app/assets/stylesheets/framework/layout.scss @@ -5,7 +5,6 @@ html { body { padding-top: $header-height; - text-rendering: geometricPrecision; } } From 97380977221865308d5336da0ea0d49e5c45d03a Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 10 Nov 2015 07:52:35 -0800 Subject: [PATCH 090/140] Fix avatars not showing in Atom feeds and project issues when Gravatar disabled Fix for https://github.com/gitlabhq/gitlabhq/pull/9783 --- CHANGELOG | 1 + app/helpers/events_helper.rb | 2 +- app/helpers/issues_helper.rb | 2 +- app/views/projects/commits/show.atom.builder | 2 +- app/views/projects/notes/_note.html.haml | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d82cab6f36..9d2af96ede 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.2.0 (unreleased) - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu) + - Fix avatars not showing in Atom feeds and project issues when Gravatar disabled (Stan Hu) - Added a GitLab specific profiling tool called "Sherlock" (see GitLab CE merge request #1749) - Upgrade gitlab_git to 7.2.20 and rugged to 0.23.3 (Stan Hu) - Improved performance of finding users by one of their Email addresses diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 6f69c2a9f3..51b872b7a9 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -198,7 +198,7 @@ module EventsHelper xml.link href: event_link xml.title truncate(event_title, length: 80) xml.updated event.created_at.xmlschema - xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(event.author_email) + xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(event.author_email)) xml.author do |author| xml.name event.author_name xml.email event.author_email diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index fda18e7b31..beb083d82d 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -74,7 +74,7 @@ module IssuesHelper issue.project, issue) xml.title truncate(issue.title, length: 80) xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(issue.author_email) + xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(issue.author_email)) xml.author do |author| xml.name issue.author_name xml.email issue.author_email diff --git a/app/views/projects/commits/show.atom.builder b/app/views/projects/commits/show.atom.builder index 3854ad5d61..268b9b815e 100644 --- a/app/views/projects/commits/show.atom.builder +++ b/app/views/projects/commits/show.atom.builder @@ -12,7 +12,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.link href: namespace_project_commit_url(@project.namespace, @project, id: commit.id) xml.title truncate(commit.title, length: 80) xml.updated commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(commit.author_email) + xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(commit.author_email)) xml.author do |author| xml.name commit.author_name xml.email commit.author_email diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 5d18473079..8880830198 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -2,7 +2,7 @@ .timeline-entry-inner .timeline-icon %a{href: user_path(note.author)} - %img.avatar.s40{src: avatar_icon(note.author), alt: ''} + = image_tag avatar_icon(note.author), alt: '', class: 'avatar s40' .timeline-content .note-header - if note_editable?(note) From 5aa142212fc4fb7765aad801c1a25696fb752fba Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 10 Nov 2015 00:27:01 -0800 Subject: [PATCH 091/140] Fix Drone CI service template not saving properly Closes #3419 --- CHANGELOG | 1 + app/models/project_services/drone_ci_service.rb | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index d82cab6f36..7567e25b89 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.2.0 (unreleased) - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu) + - Fix Drone CI service template not saving properly (Stan Hu) - Added a GitLab specific profiling tool called "Sherlock" (see GitLab CE merge request #1749) - Upgrade gitlab_git to 7.2.20 and rugged to 0.23.3 (Stan Hu) - Improved performance of finding users by one of their Email addresses diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb index c73c4b058a..c240213200 100644 --- a/app/models/project_services/drone_ci_service.rb +++ b/app/models/project_services/drone_ci_service.rb @@ -32,7 +32,6 @@ class DroneCiService < CiService def compose_service_hook hook = service_hook || build_service_hook - hook.url = [drone_url, "/api/hook", "?owner=#{project.namespace.path}", "&name=#{project.path}", "&access_token=#{token}"].join hook.enable_ssl_verification = enable_ssl_verification hook.save end From ca25289b78e1b49148438831ad6bf165fa0ce56e Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 10 Nov 2015 15:44:23 -0500 Subject: [PATCH 092/140] Update CHANGELOG [ci skip] --- CHANGELOG | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2b96a0a171..9cfcd23a9f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -34,7 +34,9 @@ v 8.2.0 (unreleased) v 8.1.4 - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) - Prevent redirect loop when home_page_url is set to the root URL - - Ability to add release notes (markdown text and attachments) to git tags + - Fix incoming email config defaults + - Make color of "Accept Merge Request" button consistent with current build status + - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu) v 8.1.3 - Force update refs/merge-requests/X/head upon a push to the source branch of a merge request (Stan Hu) @@ -42,7 +44,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.1 +v 8.1.2 - Fix cloning Wiki repositories via HTTP (Stan Hu) - Add migration to remove satellites directory - Fix specific runners visibility @@ -52,6 +54,9 @@ v 8.1.1 - Fix CI badge - Allow developer to manage builds +v 8.1.1 + - Removed, see 8.1.2 + v 8.1.0 - Ensure MySQL CI limits DB migrations occur after the fields have been created (Stan Hu) - Fix duplicate repositories in GitHub import page (Stan Hu) From eef129bd4ad41665cf5c1f1631e80b2f1bb3e4af Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 10 Nov 2015 22:21:15 +0100 Subject: [PATCH 093/140] Final fixes --- config/initializers/1_settings.rb | 2 +- doc/ci/yaml/README.md | 2 +- lib/ci/api/builds.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index b39e263e39..302124bd97 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -186,7 +186,7 @@ Settings.gitlab_ci['all_broken_builds'] = true if Settings.gitlab_ci['all_br Settings.gitlab_ci['add_pusher'] = false if Settings.gitlab_ci['add_pusher'].nil? Settings.gitlab_ci['url'] ||= Settings.send(:build_gitlab_ci_url) Settings.gitlab_ci['builds_path'] = File.expand_path(Settings.gitlab_ci['builds_path'] || "builds/", Rails.root) -Settings.gitlab_ci['max_artifacts_size'] ||= 100 +Settings.gitlab_ci['max_artifacts_size'] ||= 100 # in megabytes # # Reply by email diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 11065fee01..5d35d1da4e 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -286,7 +286,7 @@ artifacts: The artifacts will be send after the build success to GitLab and will be accessible in GitLab interface to download. -This feature requires GitLab Runner v 0.7.0. +This feature requires GitLab Runner v0.7.0 or higher. ## Validate the .gitlab-ci.yml Each instance of GitLab CI has an embedded debug tool called Lint. diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb index dab0df1263..0a58667280 100644 --- a/lib/ci/api/builds.rb +++ b/lib/ci/api/builds.rb @@ -69,7 +69,7 @@ module Ci end status 200 - { temp_path: ArtifactUploader.artifacts_upload_path } + { TempPath: ArtifactUploader.artifacts_upload_path } end # Upload artifacts to build - Runners only From 752d528019fc9f9c58d458380a6594d358458b4d Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Mon, 14 Sep 2015 12:07:50 -0500 Subject: [PATCH 094/140] Fix trailing space issue with merge requests and issues. Fixes #2514 --- CHANGELOG | 1 + app/controllers/projects/issues_controller.rb | 4 +++- app/controllers/projects/merge_requests_controller.rb | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9cfcd23a9f..939213a526 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ v 8.2.0 (unreleased) - Fix incoming email config defaults - MR target branch is now visible on a list view when it is different from project's default one - Improve Continuous Integration graphs page + - Fix trailing whitespace issue in merge request/issue title v 8.1.4 - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index e767efbdc0..e74c2905e4 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -158,10 +158,12 @@ class Projects::IssuesController < Projects::ApplicationController end def issue_params - params.require(:issue).permit( + permitted = params.require(:issue).permit( :title, :assignee_id, :position, :description, :milestone_id, :state_event, :task_num, label_ids: [] ) + params[:issue][:title].strip! if params[:issue][:title] + permitted end def bulk_update_params diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index b0788a2d07..188f0cc4ce 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -276,11 +276,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def merge_request_params - params.require(:merge_request).permit( + permitted = params.require(:merge_request).permit( :title, :assignee_id, :source_project_id, :source_branch, :target_project_id, :target_branch, :milestone_id, :state_event, :description, :task_num, label_ids: [] ) + params[:merge_request][:title].strip! if params[:merge_request][:title] + permitted end # Make sure merge requests created before 8.0 From 73f8763e6a249c4c4be731673556e7e2d625ee42 Mon Sep 17 00:00:00 2001 From: Andy Brandt Date: Tue, 10 Nov 2015 15:42:17 -0600 Subject: [PATCH 095/140] fix build link --- app/views/projects/builds/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 3374d5432a..37f33edd47 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -168,7 +168,7 @@ %td = ci_icon_for_status(build.status) %td - = link_to namespace_project_build_path(@project.namespace, @project, @build) do + = link_to namespace_project_build_path(@project.namespace, @project, build) do - if build.name = build.name - else From d70f1f35b13411adf184fafb750ae8d8fb6badea Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 10 Nov 2015 23:00:05 +0100 Subject: [PATCH 096/140] Fix tests --- spec/requests/ci/api/builds_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 233c15f87f..7886a6feca 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -144,13 +144,13 @@ describe Ci::API::API do it "using token as parameter" do post authorize_url, { token: build.project.token }, headers expect(response.status).to eq(200) - expect(json_response["temp_path"]).to_not be_nil + expect(json_response["TempPath"]).to_not be_nil end it "using token as header" do post authorize_url, {}, headers_with_token expect(response.status).to eq(200) - expect(json_response["temp_path"]).to_not be_nil + expect(json_response["TempPath"]).to_not be_nil end end From 58074ab7da9c24c030054e6303f9020f1d1f6f83 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 10 Nov 2015 22:48:13 +0100 Subject: [PATCH 097/140] Allow to define cache in `.gitlab-ci.yml` --- CHANGELOG | 1 + doc/ci/yaml/README.md | 66 ++++++++++++++++ lib/ci/gitlab_ci_yaml_processor.rb | 30 ++++++- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 82 ++++++++++++++++++++ 4 files changed, 175 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e54e7329ca..1454f70858 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ v 8.2.0 (unreleased) - Show merge request CI status on merge requests index page - Extend yml syntax for only and except to support specifying repository path - Enable shared runners to all new projects + - Allow to define cache in `.gitlab-ci.yml` - Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu) - Remove deprecated CI events from project settings page - [API] Add ability to fetch the commit ID of the last commit that actually touched a file diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 5d35d1da4e..3e6071a2ee 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -56,6 +56,7 @@ There are a few `keywords` that can't be used as job names: | types | optional | Alias for `stages` | | before_script | optional | Define commands prepended for each job's script | | variables | optional | Define build variables | +| cache | optional | Define list of files that should be cached between subsequent runs | ### image and services This allows to specify a custom Docker image and a list of services that can be used for time of the build. @@ -110,6 +111,19 @@ These variables can be later used in all executed commands and scripts. The YAML-defined variables are also set to all created service containers, thus allowing to fine tune them. +### cache +`cache` is used to specify list of files and directories which should be cached between builds. + +**The global setting allows to specify default cached files for all jobs.** + +To cache all git untracked files and files in `binaries`: +``` +cache: + untracked: true + paths: + - binaries/ +``` + ## Jobs `.gitlab-ci.yml` allows you to specify an unlimited number of jobs. Each job has to have a unique `job_name`, which is not one of the keywords mentioned above. @@ -142,6 +156,7 @@ job_name: | allow_failure | optional | Allow build to fail. Failed build doesn't contribute to commit status | | when | optional | Define when to run build. Can be `on_success`, `on_failure` or `always` | | artifacts | optional | Define list build artifacts | +| cache | optional | Define list of files that should be cached between subsequent runs | ### script `script` is a shell script which is executed by runner. The shell script is prepended with `before_script`. @@ -288,6 +303,57 @@ The artifacts will be send after the build success to GitLab and will be accessi This feature requires GitLab Runner v0.7.0 or higher. +### cache +`cache` is used to specify list of files and directories which should be cached between builds. + +1. Cache all files in `binaries` and `.config`: +``` +rspec: + script: test + cache: + paths: + - binaries/ + - .config +``` + +2. Cache all git untracked files: +``` +rspec: + script: test + cache: + untracked: true +``` + +3. Cache all git untracked files and files in `binaries`: +``` +rspec: + script: test + cache: + untracked: true + paths: + - binaries/ +``` + +4. Locally defined cache overwrites globally defined options. This will cache only `binaries/`: + +``` +cache: + paths: + - my/files + +rspec: + script: test + cache: + paths: + - binaries/ +``` + +The cache is provided on best effort basis, so don't expect that cache will be present. +For implementation details please check GitLab Runner. + +This feature requires GitLab Runner v0.7.0 or higher. + + ## Validate the .gitlab-ci.yml Each instance of GitLab CI has an embedded debug tool called Lint. You can find the link to the Lint in the project's settings page or use short url `/lint`. diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 2e2209031e..3beafcad11 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -4,10 +4,10 @@ module Ci DEFAULT_STAGES = %w(build test deploy) DEFAULT_STAGE = 'test' - ALLOWED_YAML_KEYS = [:before_script, :image, :services, :types, :stages, :variables] - ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, :allow_failure, :type, :stage, :when, :artifacts] + ALLOWED_YAML_KEYS = [:before_script, :image, :services, :types, :stages, :variables, :cache] + ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, :allow_failure, :type, :stage, :when, :artifacts, :cache] - attr_reader :before_script, :image, :services, :variables, :path + attr_reader :before_script, :image, :services, :variables, :path, :cache def initialize(config, path = nil) @config = YAML.load(config) @@ -46,6 +46,7 @@ module Ci @services = @config[:services] @stages = @config[:stages] || @config[:types] @variables = @config[:variables] || {} + @cache = @config[:cache] @config.except!(*ALLOWED_YAML_KEYS) # anything that doesn't have script is considered as unknown @@ -78,7 +79,8 @@ module Ci options: { image: job[:image] || @image, services: job[:services] || @services, - artifacts: job[:artifacts] + artifacts: job[:artifacts], + cache: job[:cache] || @cache, }.compact } end @@ -112,6 +114,16 @@ module Ci raise ValidationError, "variables should be a map of key-valued strings" end + if @cache + if @cache[:untracked] && !validate_boolean(@cache[:untracked]) + raise ValidationError, "cache:untracked parameter should be an boolean" + end + + if @cache[:paths] && !validate_array_of_strings(@cache[:paths]) + raise ValidationError, "cache:paths parameter should be an array of strings" + end + end + @jobs.each do |name, job| validate_job!(name, job) end @@ -160,6 +172,16 @@ module Ci raise ValidationError, "#{name} job: except parameter should be an array of strings" end + if job[:cache] + if job[:cache][:untracked] && !validate_boolean(job[:cache][:untracked]) + raise ValidationError, "#{name} job: cache:untracked parameter should be an boolean" + end + + if job[:cache][:paths] && !validate_array_of_strings(job[:cache][:paths]) + raise ValidationError, "#{name} job: cache:paths parameter should be an array of strings" + end + end + if job[:artifacts] if job[:artifacts][:untracked] && !validate_boolean(job[:artifacts][:untracked]) raise ValidationError, "#{name} job: artifacts:untracked parameter should be an boolean" diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 29fc271382..7d90f9877c 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -333,6 +333,60 @@ module Ci end end + describe "Caches" do + it "returns cache when defined globally" do + config = YAML.dump({ + cache: { paths: ["logs/", "binaries/"], untracked: true }, + rspec: { + script: "rspec" + } + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) + expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( + paths: ["logs/", "binaries/"], + untracked: true, + ) + end + + it "returns cache when defined in a job" do + config = YAML.dump({ + rspec: { + cache: { paths: ["logs/", "binaries/"], untracked: true }, + script: "rspec" + } + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) + expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( + paths: ["logs/", "binaries/"], + untracked: true, + ) + end + + it "overwrite cache when defined for a job and globally" do + config = YAML.dump({ + cache: { paths: ["logs/", "binaries/"], untracked: true }, + rspec: { + script: "rspec", + cache: { paths: ["test/"], untracked: false }, + } + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) + expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( + paths: ["test/"], + untracked: false, + ) + end + end + describe "Artifacts" do it "returns artifacts when defined" do config = YAML.dump({ @@ -542,6 +596,34 @@ module Ci GitlabCiYamlProcessor.new(config) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:paths parameter should be an array of strings") end + + it "returns errors if cache:untracked is not an array of strings" do + config = YAML.dump({ cache: { untracked: "string" }, rspec: { script: "test" } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:untracked parameter should be an boolean") + end + + it "returns errors if cache:paths is not an array of strings" do + config = YAML.dump({ cache: { paths: "string" }, rspec: { script: "test" } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:paths parameter should be an array of strings") + end + + it "returns errors if job cache:untracked is not an array of strings" do + config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { untracked: "string" } } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:untracked parameter should be an boolean") + end + + it "returns errors if job cache:paths is not an array of strings" do + config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { paths: "string" } } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:paths parameter should be an array of strings") + end end end end From 26fab9cb95bda08a044f5500ef0b74854b293a54 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 11 Nov 2015 13:40:47 +0000 Subject: [PATCH 098/140] Fix typo in email comment: hone -> one --- app/views/layouts/notify.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml index a02dd95518..3ca4c34040 100644 --- a/app/views/layouts/notify.html.haml +++ b/app/views/layouts/notify.html.haml @@ -42,8 +42,8 @@ - else #{link_to "View it on GitLab", @target_url}. %br - -# Don't link the host is the line below, hone link in the email is easier to quickly click than two. + -# Don't link the host is the line below, one link in the email is easier to quickly click than two. You're receiving this email because of your account on #{Gitlab.config.gitlab.host}. If you'd like to receive fewer emails, you can adjust your notification settings. - = email_action @target_url + = email_action @target_url \ No newline at end of file From 7eb502c036f412e9e802600cd67c6520a1f3bff1 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 11 Nov 2015 15:17:12 +0100 Subject: [PATCH 099/140] Change "recent" scopes to sort by "id" These scopes can just sort by the "id" column in descending order to achieve the same result. An added benefit is being able to perform a backwards index scan (depending on the rest of the final query) instead of having to actually sort data. --- app/models/concerns/issuable.rb | 2 +- app/models/event.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 5e964f04ef..492a026add 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -24,7 +24,7 @@ module Issuable scope :authored, ->(user) { where(author_id: user) } scope :assigned_to, ->(u) { where(assignee_id: u.id)} - scope :recent, -> { order("created_at DESC") } + scope :recent, -> { reorder(id: :desc) } scope :assigned, -> { where("assignee_id IS NOT NULL") } scope :unassigned, -> { where("assignee_id IS NULL") } scope :of_projects, ->(ids) { where(project_id: ids) } diff --git a/app/models/event.rb b/app/models/event.rb index 47600c57e3..bf64ac29d3 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -45,7 +45,7 @@ class Event < ActiveRecord::Base after_create :reset_project_activity # Scopes - scope :recent, -> { order(created_at: :desc) } + scope :recent, -> { reorder(id: :desc) } scope :code_push, -> { where(action: PUSHED) } scope :in_projects, ->(project_ids) { where(project_id: project_ids).recent } scope :with_associations, -> { includes(project: :namespace) } From 6748dd2fd0b2cf694661e910c6c1bb6ac2be69a1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 11 Nov 2015 15:30:23 +0100 Subject: [PATCH 100/140] Allow flog failure for now Signed-off-by: Dmitriy Zaporozhets --- .gitlab-ci.yml | 1 + lib/tasks/flog.rake | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 476dabe5e3..062fda322a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -80,3 +80,4 @@ flog: tags: - ruby - mysql + allow_failure: true diff --git a/lib/tasks/flog.rake b/lib/tasks/flog.rake index 4cb19c0c93..3bfe999ae7 100644 --- a/lib/tasks/flog.rake +++ b/lib/tasks/flog.rake @@ -2,6 +2,7 @@ desc 'Code complexity analyze via flog' task :flog do output = %x(bundle exec flog -m app/ lib/gitlab) exit_code = 0 + minimum_score = 70 output = output.lines # Skip total complexity score @@ -14,7 +15,7 @@ task :flog do score, method = line.split(" ") score = score.to_i - if score > 40 + if score > minimum_score exit_code = 1 puts "High complexity in #{method}. Score: #{score}" end From 98c954ee815823f27cf021b34684c2c3d5bb0917 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 11 Nov 2015 16:29:00 +0100 Subject: [PATCH 101/140] Add flay: tool to find duplicate code Signed-off-by: Dmitriy Zaporozhets --- .flayignore | 1 + .gitlab-ci.yml | 8 ++++++++ Gemfile | 1 + Gemfile.lock | 4 ++++ lib/tasks/flay.rake | 9 +++++++++ 5 files changed, 23 insertions(+) create mode 100644 .flayignore create mode 100644 lib/tasks/flay.rake diff --git a/.flayignore b/.flayignore new file mode 100644 index 0000000000..9c9875d4f9 --- /dev/null +++ b/.flayignore @@ -0,0 +1 @@ +*.erb diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 062fda322a..128faa07db 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -81,3 +81,11 @@ flog: - ruby - mysql allow_failure: true + +flay: + script: + - bundle exec rake flay + tags: + - ruby + - mysql + allow_failure: true diff --git a/Gemfile b/Gemfile index 91a9321533..67aef0c3a8 100644 --- a/Gemfile +++ b/Gemfile @@ -260,6 +260,7 @@ group :development, :test do gem 'coveralls', '~> 0.8.2', require: false gem 'simplecov', '~> 0.10.0', require: false gem 'flog', require: false + gem 'flay', require: false gem 'benchmark-ips', require: false end diff --git a/Gemfile.lock b/Gemfile.lock index 0fc22829cf..5f95637966 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -194,6 +194,9 @@ GEM ffi (1.9.10) fission (0.5.0) CFPropertyList (~> 2.2) + flay (2.6.1) + ruby_parser (~> 3.0) + sexp_processor (~> 4.0) flog (4.3.2) ruby_parser (~> 3.1, > 3.1.0) sexp_processor (~> 4.4) @@ -824,6 +827,7 @@ DEPENDENCIES enumerize (~> 0.7.0) factory_girl_rails (~> 4.3.0) ffaker (~> 2.0.0) + flay flog fog (~> 1.25.0) font-awesome-rails (~> 4.2) diff --git a/lib/tasks/flay.rake b/lib/tasks/flay.rake new file mode 100644 index 0000000000..5efffc2cda --- /dev/null +++ b/lib/tasks/flay.rake @@ -0,0 +1,9 @@ +desc 'Code duplication analyze via flay' +task :flay do + output = %x(bundle exec flay app/ lib/gitlab/) + + if output.include? "Similar code found" + puts output + exit 1 + end +end From d59a82118128cb8ecd14008f59e6cd73a1b0a71b Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 11 Nov 2015 11:02:52 -0500 Subject: [PATCH 102/140] Update CHANGELOG [ci skip] --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 3da194510b..71300769fe 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -32,12 +32,12 @@ v 8.2.0 (unreleased) - Fix incoming email config defaults - MR target branch is now visible on a list view when it is different from project's default one - Improve Continuous Integration graphs page + - Make color of "Accept Merge Request" button consistent with current build status v 8.1.4 - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) - Prevent redirect loop when home_page_url is set to the root URL - Fix incoming email config defaults - - Make color of "Accept Merge Request" button consistent with current build status - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu) v 8.1.3 From 1974087114f3f365d16547c8a5c3ec2e03a66104 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Thu, 12 Nov 2015 13:16:35 +0800 Subject: [PATCH 103/140] Avoid render edit_form in each notes. Use RJS to render edit note feature. Before: ``` Rendered projects/notes/_note.html.haml (27.9ms) Rendered projects/_zen.html.haml (0.3ms) Rendered projects/notes/_hints.html.haml (0.7ms) Rendered projects/_md_preview.html.haml (3.9ms) Rendered projects/notes/_edit_form.html.haml (6.9ms) Rendered projects/notes/_note.html.haml (17.7ms) Rendered projects/_zen.html.haml (0.3ms) Rendered projects/notes/_hints.html.haml (0.6ms) Rendered projects/_md_preview.html.haml (3.4ms) Rendered projects/notes/_edit_form.html.haml (7.0ms) ``` After: ``` Rendered projects/notes/_note.html.haml (13.8ms) Rendered projects/notes/_note.html.haml (7.1ms) Rendered projects/notes/_note.html.haml (9.5ms) Rendered projects/notes/_note.html.haml (8.5ms) ``` This change reduce at least 6ms * N ('N' - number of notes). --- app/assets/javascripts/notes.js.coffee | 13 ++++++------- app/controllers/projects/notes_controller.rb | 7 ++++++- app/views/projects/notes/_note.html.haml | 5 +---- app/views/projects/notes/_notes_with_form.html.haml | 2 +- app/views/projects/notes/edit.js.erb | 2 ++ config/routes.rb | 2 +- features/steps/project/issues/issues.rb | 2 +- spec/features/notes_on_merge_requests_spec.rb | 6 ------ spec/features/task_lists_spec.rb | 3 --- 9 files changed, 18 insertions(+), 24 deletions(-) create mode 100644 app/views/projects/notes/edit.js.erb diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index ea75c656bc..b0682f1684 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -29,7 +29,6 @@ class @Notes $(document).on "ajax:success", "form.edit_note", @updateNote # Edit note link - $(document).on "click", ".js-note-edit", @showEditForm $(document).on "click", ".note-edit-cancel", @cancelEdit # Reopen and close actions for Issue/MR combined with note form submit @@ -67,7 +66,6 @@ class @Notes $(document).off "ajax:success", ".js-main-target-form" $(document).off "ajax:success", ".js-discussion-note-form" $(document).off "ajax:success", "form.edit_note" - $(document).off "click", ".js-note-edit" $(document).off "click", ".note-edit-cancel" $(document).off "click", ".js-note-delete" $(document).off "click", ".js-note-attachment-delete" @@ -287,13 +285,14 @@ class @Notes Adds a hidden div with the original content of the note to fill the edit note form with if the user cancels ### - showEditForm: (e) -> - e.preventDefault() - note = $(this).closest(".note") + showEditForm: (note, formHTML) -> + nodeText = note.find(".note-text"); + nodeText.hide() + note.find('.note-edit-form').remove() + nodeText.after(formHTML) note.find(".note-body > .note-text").hide() note.find(".note-header").hide() - base_form = note.find(".note-edit-form") - form = base_form.clone().insertAfter(base_form) + form = note.find(".note-edit-form") form.addClass('current-note-edit-form gfm-form') form.find('.div-dropzone').remove() diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 41cd08c93c..0c98e2f1bf 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -3,7 +3,7 @@ class Projects::NotesController < Projects::ApplicationController before_action :authorize_read_note! before_action :authorize_create_note!, only: [:create] before_action :authorize_admin_note!, only: [:update, :destroy] - before_action :find_current_user_notes, except: [:destroy, :delete_attachment] + before_action :find_current_user_notes, except: [:destroy, :edit, :delete_attachment] def index current_fetched_at = Time.now.to_i @@ -29,6 +29,11 @@ class Projects::NotesController < Projects::ApplicationController end end + def edit + @note = note + render layout: false + end + def update @note = Notes::UpdateService.new(project, current_user, note_params).execute(note) diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 5d18473079..d0e5286cbd 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -7,7 +7,7 @@ .note-header - if note_editable?(note) .note-actions - = link_to '#', title: 'Edit comment', class: 'js-note-edit' do + = link_to edit_namespace_project_note_path(note.project.namespace, note.project, note), title: 'Edit comment', remote: true, class: 'js-note-edit' do = icon('pencil-square-o') = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'js-note-delete danger' do @@ -59,9 +59,6 @@ .note-text = preserve do = markdown(note.note, {no_header_anchors: true}) - - unless note.system? - -# System notes can't be edited - = render 'projects/notes/edit_form', note: note - if note.attachment.url .note-attachment diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml index 04222b8f7c..91cefa6d14 100644 --- a/app/views/projects/notes/_notes_with_form.html.haml +++ b/app/views/projects/notes/_notes_with_form.html.haml @@ -7,4 +7,4 @@ = render "projects/notes/form", view: params[:view] :javascript - new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{params[:view]}") + window._notes = new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{params[:view]}") diff --git a/app/views/projects/notes/edit.js.erb b/app/views/projects/notes/edit.js.erb new file mode 100644 index 0000000000..2599bad5d6 --- /dev/null +++ b/app/views/projects/notes/edit.js.erb @@ -0,0 +1,2 @@ +$note = $('.note-row-<%= @note.id %>:visible'); +_notes.showEditForm($note, '<%= escape_javascript(render('edit_form', note: @note)) %>'); diff --git a/config/routes.rb b/config/routes.rb index 2028ea938e..6b053124fe 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -658,7 +658,7 @@ Gitlab::Application.routes.draw do end end - resources :notes, only: [:index, :create, :destroy, :update], constraints: { id: /\d+/ } do + resources :notes, constraints: { id: /\d+/ } do member do delete :delete_attachment end diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb index af2da41bad..be134b9c2b 100644 --- a/features/steps/project/issues/issues.rb +++ b/features/steps/project/issues/issues.rb @@ -219,7 +219,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps end step 'The code block should be unchanged' do - expect(page).to have_content("```\nCommand [1]: /usr/local/bin/git , see [text](doc/text)\n```") + expect(page).to have_content("Command [1]: /usr/local/bin/git , see [text](doc/text)") end step 'project \'Shop\' has issue \'Bugfix1\' with description: \'Description for issue1\'' do diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index d7cb3b2e86..16d5a03e88 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -65,12 +65,6 @@ describe 'Comments', feature: true do end describe 'when editing a note', js: true do - it 'should contain the hidden edit form' do - page.within("#note_#{note.id}") do - is_expected.to have_css('.note-edit-form', visible: false) - end - end - describe 'editing the note' do before do find('.note').hover diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb index fca3c77fc6..6cbe685a93 100644 --- a/spec/features/task_lists_spec.rb +++ b/spec/features/task_lists_spec.rb @@ -51,7 +51,6 @@ feature 'Task Lists', feature: true do expect(page).to have_selector(container) expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox") - expect(page).to have_selector("#{container} .js-task-list-field") expect(page).to have_selector('form.js-issuable-update') expect(page).to have_selector('a.btn-close') end @@ -90,7 +89,6 @@ feature 'Task Lists', feature: true do expect(page).to have_selector('.note .js-task-list-container') expect(page).to have_selector('.note .js-task-list-container .task-list .task-list-item .task-list-item-checkbox') - expect(page).to have_selector('.note .js-task-list-container .js-task-list-field') end it 'is only editable by author' do @@ -127,7 +125,6 @@ feature 'Task Lists', feature: true do expect(page).to have_selector(container) expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox") - expect(page).to have_selector("#{container} .js-task-list-field") expect(page).to have_selector('form.js-issuable-update') expect(page).to have_selector('a.btn-close') end From 2e4a673cbc1a43532e8aa096e4ab5ca034b804f7 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Thu, 12 Nov 2015 17:19:03 +0800 Subject: [PATCH 104/140] Add caching for ApplicationSetting, Ci::ApplicationSetting. ApplicationSetting.current was called in every pages, cache it and expires it after it updated. This changes will avoid a SQL query in every pages (~0.3 - 0.5ms). ```SQL SELECT "application_settings".* FROM "application_settings" ORDER BY "application_settings"."id" DESC LIMIT 1 ``` --- app/models/application_setting.rb | 8 +++++++- app/models/ci/application_setting.rb | 10 ++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 266045f7af..5a9e55a95f 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -68,8 +68,14 @@ class ApplicationSetting < ActiveRecord::Base end end + after_commit do + Rails.cache.write('application_setting.last', self) + end + def self.current - ApplicationSetting.last + Rails.cache.fetch('application_setting.last') do + ApplicationSetting.last + end end def self.create_from_defaults diff --git a/app/models/ci/application_setting.rb b/app/models/ci/application_setting.rb index 0cf496f7d8..4ab3e2dcbb 100644 --- a/app/models/ci/application_setting.rb +++ b/app/models/ci/application_setting.rb @@ -12,9 +12,15 @@ module Ci class ApplicationSetting < ActiveRecord::Base extend Ci::Model - + + after_commit do + Rails.cache.write('ci_application_setting.last', self) + end + def self.current - Ci::ApplicationSetting.last + Rails.cache.fetch('ci_application_setting.last') do + Ci::ApplicationSetting.last + end end def self.create_from_defaults From e073b09f1f0d7b37ece6ecb3e7e485eb3f5e2e6f Mon Sep 17 00:00:00 2001 From: Jon Cairns Date: Tue, 3 Nov 2015 09:25:26 +0000 Subject: [PATCH 105/140] Add missing "omniauth" prefix to option in docs [ci skip] Changes block_auto_created_users to omniauth_block_auto_created_users, otherwise the option is ignored. Fixes #3319. --- CHANGELOG | 1 + doc/integration/omniauth.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 588909d257..932b1b2fa1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ v 8.2.0 (unreleased) - Remove deprecated CI events from project settings page - Use issue editor as cross reference comment author when issue is edited with a new mention. - [API] Add ability to fetch the commit ID of the last commit that actually touched a file + - Fix omniauth documentation setting for omnibus configuration (Jon Cairns) v 8.1.1 - Fix cloning Wiki repositories via HTTP (Stan Hu) diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md index c5cecbc2f2..3348ada015 100644 --- a/doc/integration/omniauth.md +++ b/doc/integration/omniauth.md @@ -36,7 +36,7 @@ If you want to change these settings: ``` gitlab_rails['omniauth_enabled'] = true gitlab_rails['omniauth_allow_single_sign_on'] = false - gitlab_rails['block_auto_created_users'] = true + gitlab_rails['omniauth_block_auto_created_users'] = true ``` * **For installations from source** From 3c96ea766a60e15272f9d9ad1d331c9372465d6a Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 10 Nov 2015 12:05:27 +0100 Subject: [PATCH 106/140] Fix changelog item Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 62def42d95..bcd4b6a21d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -36,6 +36,7 @@ v 8.2.0 (unreleased) - MR target branch is now visible on a list view when it is different from project's default one - Improve Continuous Integration graphs page - Make color of "Accept Merge Request" button consistent with current build status + - Ability to add release notes (markdown text and attachments) to git tags (aka Releases) v 8.1.4 - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) From 2ac1193720907bccc1f321e9c56fb5ca2b0a3a1c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 10 Nov 2015 12:33:21 +0100 Subject: [PATCH 107/140] Add release notes documentation Signed-off-by: Dmitriy Zaporozhets --- doc/workflow/README.md | 1 + doc/workflow/releases.md | 20 ++++++++++++++++++++ doc/workflow/releases/new_tag.png | Bin 0 -> 154755 bytes doc/workflow/releases/tags.png | Bin 0 -> 165449 bytes 4 files changed, 21 insertions(+) create mode 100644 doc/workflow/releases.md create mode 100644 doc/workflow/releases/new_tag.png create mode 100644 doc/workflow/releases/tags.png diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 5b8d72dfd3..a2653704c3 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -13,5 +13,6 @@ - [Project users](add-user/add-user.md) - [Protected branches](protected_branches.md) - [Web Editor](web_editor.md) +- [Releases](releases.md) - [Merge Requests](merge_requests.md) - ["Work In Progress" Merge Requests](wip_merge_requests.md) diff --git a/doc/workflow/releases.md b/doc/workflow/releases.md new file mode 100644 index 0000000000..0eca220e2c --- /dev/null +++ b/doc/workflow/releases.md @@ -0,0 +1,20 @@ +# Releases + +You can turn any git tag into release by just adding a release notes to it. +Release notes behaves like any other markdown form in GitLab so you can write text and drag-n-drop files to it. +Release notes are stored in GitLab database. + +There are several ways to add release notes: + +* In UI when you create new git tag with GitLab +* In UI when you add release notes to existing git tag +* with GitLab API + +## New tag page with release notes text area + +![new_tag](releases/new_tag.png) + +## Tags page with button to add or edit release notes for existing git tag + +![tags](releases/tags.png) + diff --git a/doc/workflow/releases/new_tag.png b/doc/workflow/releases/new_tag.png new file mode 100644 index 0000000000000000000000000000000000000000..e2b64bfe17fb95f221cbd57ec3da684b5e47ee39 GIT binary patch literal 154755 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 I8T-%w0YIRt+5i9m literal 0 HcmV?d00001 diff --git a/doc/workflow/releases/tags.png b/doc/workflow/releases/tags.png new file mode 100644 index 0000000000000000000000000000000000000000..aca91906c68a439d0987085db345cb82c04a51b4 GIT binary patch literal 165449 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 V$vjFuegOEVd&B5D;_B^~{|DQ49mfCw literal 0 HcmV?d00001 From a5ab56fd9191e23dfa60707fa42802342c1563f8 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 12 Nov 2015 15:41:13 +0100 Subject: [PATCH 108/140] Move git tags API to separate file Signed-off-by: Dmitriy Zaporozhets --- doc/api/repositories.md | 74 --------------------- doc/api/tags.md | 75 ++++++++++++++++++++++ lib/api/api.rb | 1 + lib/api/repositories.rb | 35 ---------- lib/api/tags.rb | 44 +++++++++++++ spec/requests/api/repositories_spec.rb | 75 ---------------------- spec/requests/api/tags_spec.rb | 89 ++++++++++++++++++++++++++ 7 files changed, 209 insertions(+), 184 deletions(-) create mode 100644 doc/api/tags.md create mode 100644 lib/api/tags.rb create mode 100644 spec/requests/api/tags_spec.rb diff --git a/doc/api/repositories.md b/doc/api/repositories.md index 3316745380..b6cca5d4e2 100644 --- a/doc/api/repositories.md +++ b/doc/api/repositories.md @@ -1,79 +1,5 @@ # Repositories -## List project repository tags - -Get a list of repository tags from a project, sorted by name in reverse alphabetical order. - -``` -GET /projects/:id/repository/tags -``` - -Parameters: - -- `id` (required) - The ID of a project - -```json -[ - { - "commit": { - "author_name": "John Smith", - "author_email": "john@example.com", - "authored_date": "2012-05-28T04:42:42-07:00", - "committed_date": "2012-05-28T04:42:42-07:00", - "committer_name": "Jack Smith", - "committer_email": "jack@example.com", - "id": "2695effb5807a22ff3d138d593fd856244e155e7", - "message": "Initial commit", - "parents_ids": [ - "2a4b78934375d7f53875269ffd4f45fd83a84ebe" - ] - }, - "name": "v1.0.0", - "message": null - } -] -``` - -## Create a new tag - -Creates new tag in the repository that points to the supplied ref. - -``` -POST /projects/:id/repository/tags -``` - -Parameters: - -- `id` (required) - The ID of a project -- `tag_name` (required) - The name of a tag -- `ref` (required) - Create tag using commit SHA, another tag name, or branch name. -- `message` (optional) - Creates annotated tag. - -```json -{ - "commit": { - "author_name": "John Smith", - "author_email": "john@example.com", - "authored_date": "2012-05-28T04:42:42-07:00", - "committed_date": "2012-05-28T04:42:42-07:00", - "committer_name": "Jack Smith", - "committer_email": "jack@example.com", - "id": "2695effb5807a22ff3d138d593fd856244e155e7", - "message": "Initial commit", - "parents_ids": [ - "2a4b78934375d7f53875269ffd4f45fd83a84ebe" - ] - }, - "name": "v1.0.0", - "message": null -} -``` -The message will be `nil` when creating a lightweight tag otherwise -it will contain the annotation. - -It returns 200 if the operation succeed. In case of an error, -405 with an explaining error message is returned. - ## List repository tree Get a list of repository files and directories in a project. diff --git a/doc/api/tags.md b/doc/api/tags.md new file mode 100644 index 0000000000..dc69a0c6fb --- /dev/null +++ b/doc/api/tags.md @@ -0,0 +1,75 @@ +# Tags + +## List project repository tags + +Get a list of repository tags from a project, sorted by name in reverse alphabetical order. + +``` +GET /projects/:id/repository/tags +``` + +Parameters: + +- `id` (required) - The ID of a project + +```json +[ + { + "commit": { + "author_name": "John Smith", + "author_email": "john@example.com", + "authored_date": "2012-05-28T04:42:42-07:00", + "committed_date": "2012-05-28T04:42:42-07:00", + "committer_name": "Jack Smith", + "committer_email": "jack@example.com", + "id": "2695effb5807a22ff3d138d593fd856244e155e7", + "message": "Initial commit", + "parents_ids": [ + "2a4b78934375d7f53875269ffd4f45fd83a84ebe" + ] + }, + "name": "v1.0.0", + "message": null + } +] +``` + +## Create a new tag + +Creates new tag in the repository that points to the supplied ref. + +``` +POST /projects/:id/repository/tags +``` + +Parameters: + +- `id` (required) - The ID of a project +- `tag_name` (required) - The name of a tag +- `ref` (required) - Create tag using commit SHA, another tag name, or branch name. +- `message` (optional) - Creates annotated tag. + +```json +{ + "commit": { + "author_name": "John Smith", + "author_email": "john@example.com", + "authored_date": "2012-05-28T04:42:42-07:00", + "committed_date": "2012-05-28T04:42:42-07:00", + "committer_name": "Jack Smith", + "committer_email": "jack@example.com", + "id": "2695effb5807a22ff3d138d593fd856244e155e7", + "message": "Initial commit", + "parents_ids": [ + "2a4b78934375d7f53875269ffd4f45fd83a84ebe" + ] + }, + "name": "v1.0.0", + "message": null +} +``` +The message will be `nil` when creating a lightweight tag otherwise +it will contain the annotation. + +It returns 200 if the operation succeed. In case of an error, +405 with an explaining error message is returned. diff --git a/lib/api/api.rb b/lib/api/api.rb index 40671e2517..fe1bf8a481 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -52,5 +52,6 @@ module API mount Labels mount Settings mount Keys + mount Tags end end diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 20d568cf46..d7c48639eb 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -16,41 +16,6 @@ module API end end - # Get a project repository tags - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id/repository/tags - get ":id/repository/tags" do - present user_project.repo.tags.sort_by(&:name).reverse, - with: Entities::RepoTag, project: user_project - end - - # Create tag - # - # Parameters: - # id (required) - The ID of a project - # tag_name (required) - The name of the tag - # ref (required) - Create tag from commit sha or branch - # message (optional) - Specifying a message creates an annotated tag. - # Example Request: - # POST /projects/:id/repository/tags - post ':id/repository/tags' do - authorize_push_project - message = params[:message] || nil - result = CreateTagService.new(user_project, current_user). - execute(params[:tag_name], params[:ref], message) - - if result[:status] == :success - present result[:tag], - with: Entities::RepoTag, - project: user_project - else - render_api_error!(result[:message], 400) - end - end - # Get a project repository tree # # Parameters: diff --git a/lib/api/tags.rb b/lib/api/tags.rb new file mode 100644 index 0000000000..da962bd402 --- /dev/null +++ b/lib/api/tags.rb @@ -0,0 +1,44 @@ +module API + # Releases API + class Tags < Grape::API + before { authenticate! } + before { authorize! :download_code, user_project } + + resource :projects do + # Get a project repository tags + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # GET /projects/:id/repository/tags + get ":id/repository/tags" do + present user_project.repo.tags.sort_by(&:name).reverse, + with: Entities::RepoTag, project: user_project + end + + # Create tag + # + # Parameters: + # id (required) - The ID of a project + # tag_name (required) - The name of the tag + # ref (required) - Create tag from commit sha or branch + # message (optional) - Specifying a message creates an annotated tag. + # Example Request: + # POST /projects/:id/repository/tags + post ':id/repository/tags' do + authorize_push_project + message = params[:message] || nil + result = CreateTagService.new(user_project, current_user). + execute(params[:tag_name], params[:ref], message) + + if result[:status] == :success + present result[:tag], + with: Entities::RepoTag, + project: user_project + else + render_api_error!(result[:message], 400) + end + end + end + end +end diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index faf6b77a46..4911cdd9da 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -11,81 +11,6 @@ describe API::API, api: true do let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) } - describe "GET /projects/:id/repository/tags" do - it "should return an array of project tags" do - get api("/projects/#{project.id}/repository/tags", user) - expect(response.status).to eq(200) - expect(json_response).to be_an Array - expect(json_response.first['name']).to eq(project.repo.tags.sort_by(&:name).reverse.first.name) - end - end - - describe 'POST /projects/:id/repository/tags' do - context 'lightweight tags' do - it 'should create a new tag' do - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v7.0.1', - ref: 'master' - - expect(response.status).to eq(201) - expect(json_response['name']).to eq('v7.0.1') - end - end - - context 'annotated tag' do - it 'should create a new annotated tag' do - # Identity must be set in .gitconfig to create annotated tag. - repo_path = project.repository.path_to_repo - system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.name #{user.name})) - system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.email #{user.email})) - - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v7.1.0', - ref: 'master', - message: 'Release 7.1.0' - - expect(response.status).to eq(201) - expect(json_response['name']).to eq('v7.1.0') - expect(json_response['message']).to eq('Release 7.1.0') - end - end - - it 'should deny for user without push access' do - post api("/projects/#{project.id}/repository/tags", user2), - tag_name: 'v1.9.0', - ref: '621491c677087aa243f165eab467bfdfbee00be1' - expect(response.status).to eq(403) - end - - it 'should return 400 if tag name is invalid' do - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v 1.0.0', - ref: 'master' - expect(response.status).to eq(400) - expect(json_response['message']).to eq('Tag name invalid') - end - - it 'should return 400 if tag already exists' do - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v8.0.0', - ref: 'master' - expect(response.status).to eq(201) - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v8.0.0', - ref: 'master' - expect(response.status).to eq(400) - expect(json_response['message']).to eq('Tag already exists') - end - - it 'should return 400 if ref name is invalid' do - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'mytag', - ref: 'foo' - expect(response.status).to eq(400) - expect(json_response['message']).to eq('Invalid reference name') - end - end - describe "GET /projects/:id/repository/tree" do context "authorized user" do before { project.team << [user2, :reporter] } diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb new file mode 100644 index 0000000000..6ddd7030ce --- /dev/null +++ b/spec/requests/api/tags_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' +require 'mime/types' + +describe API::API, api: true do + include ApiHelpers + include RepoHelpers + + let(:user) { create(:user) } + let(:user2) { create(:user) } + let!(:project) { create(:project, creator_id: user.id) } + let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } + let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) } + + + describe "GET /projects/:id/repository/tags" do + it "should return an array of project tags" do + get api("/projects/#{project.id}/repository/tags", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['name']).to eq(project.repo.tags.sort_by(&:name).reverse.first.name) + end + end + + describe 'POST /projects/:id/repository/tags' do + context 'lightweight tags' do + it 'should create a new tag' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v7.0.1', + ref: 'master' + + expect(response.status).to eq(201) + expect(json_response['name']).to eq('v7.0.1') + end + end + + context 'annotated tag' do + it 'should create a new annotated tag' do + # Identity must be set in .gitconfig to create annotated tag. + repo_path = project.repository.path_to_repo + system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.name #{user.name})) + system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.email #{user.email})) + + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v7.1.0', + ref: 'master', + message: 'Release 7.1.0' + + expect(response.status).to eq(201) + expect(json_response['name']).to eq('v7.1.0') + expect(json_response['message']).to eq('Release 7.1.0') + end + end + + it 'should deny for user without push access' do + post api("/projects/#{project.id}/repository/tags", user2), + tag_name: 'v1.9.0', + ref: '621491c677087aa243f165eab467bfdfbee00be1' + expect(response.status).to eq(403) + end + + it 'should return 400 if tag name is invalid' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v 1.0.0', + ref: 'master' + expect(response.status).to eq(400) + expect(json_response['message']).to eq('Tag name invalid') + end + + it 'should return 400 if tag already exists' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v8.0.0', + ref: 'master' + expect(response.status).to eq(201) + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v8.0.0', + ref: 'master' + expect(response.status).to eq(400) + expect(json_response['message']).to eq('Tag already exists') + end + + it 'should return 400 if ref name is invalid' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'mytag', + ref: 'foo' + expect(response.status).to eq(400) + expect(json_response['message']).to eq('Invalid reference name') + end + end +end From d343d9d8c2f3db0ca5f6fab8ad19e0f79f66e138 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 12 Nov 2015 16:26:23 +0100 Subject: [PATCH 109/140] Add grape routing print Signed-off-by: Dmitriy Zaporozhets --- lib/tasks/grape.rake | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 lib/tasks/grape.rake diff --git a/lib/tasks/grape.rake b/lib/tasks/grape.rake new file mode 100644 index 0000000000..9980e0b798 --- /dev/null +++ b/lib/tasks/grape.rake @@ -0,0 +1,8 @@ +namespace :grape do + desc 'Print compiled grape routes' + task routes: :environment do + API::API.routes.each do |route| + puts route + end + end +end From c119a737935f1e1e8ae7dec814be8accb8980e1d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 12 Nov 2015 16:26:39 +0100 Subject: [PATCH 110/140] Add releases api Signed-off-by: Dmitriy Zaporozhets --- lib/api/entities.rb | 4 ++++ lib/api/tags.rb | 19 ++++++++++++++++++- spec/requests/api/tags_spec.rb | 15 ++++++++++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 20cadae229..400900bc40 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -341,5 +341,9 @@ module API expose :user_oauth_applications expose :after_sign_out_path end + + class Release < Grape::Entity + expose :tag, :description + end end end diff --git a/lib/api/tags.rb b/lib/api/tags.rb index da962bd402..06fa1d23fd 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -1,5 +1,5 @@ module API - # Releases API + # Git Tags API class Tags < Grape::API before { authenticate! } before { authorize! :download_code, user_project } @@ -39,6 +39,23 @@ module API render_api_error!(result[:message], 400) end end + + # Add release notes to tag + # + # Parameters: + # id (required) - The ID of a project + # tag (required) - The name of the tag + # description (required) - Release notes with markdown support + # Example Request: + # PUT /projects/:id/repository/tags + put ':id/repository/:tag/release', requirements: { tag: /.*/ } do + authorize_push_project + required_attributes! [:description] + release = user_project.releases.find_or_initialize_by(tag: params[:tag]) + release.update_attributes(description: params[:description]) + + present release, with: Entities::Release + end end end end diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index 6ddd7030ce..72df9007ee 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -11,7 +11,6 @@ describe API::API, api: true do let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) } - describe "GET /projects/:id/repository/tags" do it "should return an array of project tags" do get api("/projects/#{project.id}/repository/tags", user) @@ -86,4 +85,18 @@ describe API::API, api: true do expect(json_response['message']).to eq('Invalid reference name') end end + + describe 'PUT /projects/:id/repository/:tag/release' do + let(:tag_name) { project.repository.tag_names.first } + let(:description) { 'Awesome release!' } + + it 'should create description for existing git tag' do + put api("/projects/#{project.id}/repository/#{tag_name}/release", user), + description: description + + expect(response.status).to eq(200) + expect(json_response['tag']).to eq(tag_name) + expect(json_response['description']).to eq(description) + end + end end From ba34045f3120fc52ce714d49778a24c647248500 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 12 Nov 2015 17:04:18 +0100 Subject: [PATCH 111/140] Expose release notes to tags api Signed-off-by: Dmitriy Zaporozhets --- lib/api/entities.rb | 6 ++++++ spec/requests/api/tags_spec.rb | 30 +++++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 400900bc40..7528e718c6 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -112,6 +112,12 @@ module API options[:project].repository.commit(repo_obj.target) end end + + expose :release do |repo_obj, options| + if options[:project] + options[:project].releases.find_by(tag: repo_obj.name) + end + end end class RepoObject < Grape::Entity diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index 72df9007ee..4362e0ec28 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -12,11 +12,31 @@ describe API::API, api: true do let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) } describe "GET /projects/:id/repository/tags" do - it "should return an array of project tags" do - get api("/projects/#{project.id}/repository/tags", user) - expect(response.status).to eq(200) - expect(json_response).to be_an Array - expect(json_response.first['name']).to eq(project.repo.tags.sort_by(&:name).reverse.first.name) + let(:tag_name) { project.repository.tag_names.sort.reverse.first } + let(:description) { 'Awesome release!' } + + context 'without releases' do + it "should return an array of project tags" do + get api("/projects/#{project.id}/repository/tags", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['name']).to eq(tag_name) + end + end + + context 'with releases' do + before do + release = project.releases.find_or_initialize_by(tag: tag_name) + release.update_attributes(description: description) + get api("/projects/#{project.id}/repository/tags", user) + end + + it "should return an array of project tags with release info" do + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['name']).to eq(tag_name) + expect(json_response.first['release']['description']).to eq(description) + end end end From 8f53094f0f22fe208f6f6e0d39037188bf14a937 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 12 Nov 2015 23:52:02 +0100 Subject: [PATCH 112/140] Add API docs and correctly expose release api Signed-off-by: Dmitriy Zaporozhets --- doc/api/tags.md | 31 ++++++++++++++++++++++++++++ lib/api/entities.rb | 50 ++++++++++++++++++++++----------------------- lib/api/tags.rb | 2 +- 3 files changed, 57 insertions(+), 26 deletions(-) diff --git a/doc/api/tags.md b/doc/api/tags.md index dc69a0c6fb..0113d4ba04 100644 --- a/doc/api/tags.md +++ b/doc/api/tags.md @@ -28,6 +28,10 @@ Parameters: "2a4b78934375d7f53875269ffd4f45fd83a84ebe" ] }, + "release": { + "tag": "1.0.0", + "description": "Amazing release. Wow" + }, "name": "v1.0.0", "message": null } @@ -48,6 +52,7 @@ Parameters: - `tag_name` (required) - The name of a tag - `ref` (required) - Create tag using commit SHA, another tag name, or branch name. - `message` (optional) - Creates annotated tag. +- `release_description` (optional) - Add release notes to the git tag and store it in the GitLab database. ```json { @@ -64,6 +69,10 @@ Parameters: "2a4b78934375d7f53875269ffd4f45fd83a84ebe" ] }, + "release": { + "tag": "1.0.0", + "description": "Amazing release. Wow" + }, "name": "v1.0.0", "message": null } @@ -73,3 +82,25 @@ it will contain the annotation. It returns 200 if the operation succeed. In case of an error, 405 with an explaining error message is returned. + + +## New release + +Add release notes to the existing git tag + +``` +PUT /projects/:id/repository/:tag/release +``` + +Parameters: + +- `id` (required) - The ID of a project +- `tag` (required) - The name of a tag +- `description` (required) - Release notes with markdown support + +```json +{ + "tag": "1.0.0", + "description": "Amazing release. Wow" +} +``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 7528e718c6..3ca632dfae 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -95,31 +95,6 @@ module API end end - class RepoTag < Grape::Entity - expose :name - expose :message do |repo_obj, _options| - if repo_obj.respond_to?(:message) - repo_obj.message - else - nil - end - end - - expose :commit do |repo_obj, options| - if repo_obj.respond_to?(:commit) - repo_obj.commit - elsif options[:project] - options[:project].repository.commit(repo_obj.target) - end - end - - expose :release do |repo_obj, options| - if options[:project] - options[:project].releases.find_by(tag: repo_obj.name) - end - end - end - class RepoObject < Grape::Entity expose :name @@ -351,5 +326,30 @@ module API class Release < Grape::Entity expose :tag, :description end + + class RepoTag < Grape::Entity + expose :name + expose :message do |repo_obj, _options| + if repo_obj.respond_to?(:message) + repo_obj.message + else + nil + end + end + + expose :commit do |repo_obj, options| + if repo_obj.respond_to?(:commit) + repo_obj.commit + elsif options[:project] + options[:project].repository.commit(repo_obj.target) + end + end + + expose :release, using: Entities::Release do |repo_obj, options| + if options[:project] + options[:project].releases.find_by(tag: repo_obj.name) + end + end + end end end diff --git a/lib/api/tags.rb b/lib/api/tags.rb index 06fa1d23fd..673342dd44 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -29,7 +29,7 @@ module API authorize_push_project message = params[:message] || nil result = CreateTagService.new(user_project, current_user). - execute(params[:tag_name], params[:ref], message) + execute(params[:tag_name], params[:ref], message, params[:release_description]) if result[:status] == :success present result[:tag], From d6db451732a5ed9f31f3a345dd936abc1c07c6cf Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 12 Nov 2015 23:54:22 +0100 Subject: [PATCH 113/140] Add api test for creating tag with release info Signed-off-by: Dmitriy Zaporozhets --- spec/requests/api/tags_spec.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index 4362e0ec28..cc9a5f4758 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -52,6 +52,19 @@ describe API::API, api: true do end end + context 'lightweight tags with release notes' do + it 'should create a new tag' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v7.0.1', + ref: 'master', + release_description: 'Wow' + + expect(response.status).to eq(201) + expect(json_response['name']).to eq('v7.0.1') + expect(json_response['release']['description']).to eq('Wow') + end + end + context 'annotated tag' do it 'should create a new annotated tag' do # Identity must be set in .gitconfig to create annotated tag. From e5cc197a993fd0fbaafd7eeac6253fe38b8a96ad Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 00:06:42 +0100 Subject: [PATCH 114/140] Split huge method MergeRequests::UpdateService#execute Signed-off-by: Dmitriy Zaporozhets --- app/services/merge_requests/update_service.rb | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 61f7d2bbe8..d2849e5193 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -35,35 +35,38 @@ module MergeRequests ) end - if merge_request.previous_changes.include?('target_branch') - create_branch_change_note(merge_request, 'target', - merge_request.previous_changes['target_branch'].first, - merge_request.target_branch) - end - - if merge_request.previous_changes.include?('milestone_id') - create_milestone_note(merge_request) - end - - if merge_request.previous_changes.include?('assignee_id') - create_assignee_note(merge_request) - notification_service.reassigned_merge_request(merge_request, current_user) - end - - if merge_request.previous_changes.include?('title') - create_title_change_note(merge_request, merge_request.previous_changes['title'].first) - end - - if merge_request.previous_changes.include?('target_branch') || - merge_request.previous_changes.include?('source_branch') - merge_request.mark_as_unchecked - end - + handle_changes(merge_request) merge_request.create_new_cross_references!(current_user) execute_hooks(merge_request, 'update') end merge_request end + + def handle_changes(merge_request) + if merge_request.previous_changes.include?('target_branch') + create_branch_change_note(merge_request, 'target', + merge_request.previous_changes['target_branch'].first, + merge_request.target_branch) + end + + if merge_request.previous_changes.include?('milestone_id') + create_milestone_note(merge_request) + end + + if merge_request.previous_changes.include?('assignee_id') + create_assignee_note(merge_request) + notification_service.reassigned_merge_request(merge_request, current_user) + end + + if merge_request.previous_changes.include?('title') + create_title_change_note(merge_request, merge_request.previous_changes['title'].first) + end + + if merge_request.previous_changes.include?('target_branch') || + merge_request.previous_changes.include?('source_branch') + merge_request.mark_as_unchecked + end + end end end From 7f0b4ce16f14129f9c3f94b4a83fa8a6426cd4ba Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 00:12:18 +0100 Subject: [PATCH 115/140] Split complex method SystemHooksService#build_event_data Signed-off-by: Dmitriy Zaporozhets --- app/services/system_hooks_service.rb | 72 ++++++++++++++++------------ 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index 9a5fe4af9d..8b5143e1eb 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -33,17 +33,7 @@ class SystemHooksService ) end when Project - owner = model.owner - - data.merge!({ - name: model.name, - path: model.path, - path_with_namespace: model.path_with_namespace, - project_id: model.id, - owner_name: owner.name, - owner_email: owner.respond_to?(:email) ? owner.email : "", - project_visibility: Project.visibility_levels.key(model.visibility_level_field).downcase - }) + data.merge!(project_data(model)) when User data.merge!({ name: model.name, @@ -51,16 +41,7 @@ class SystemHooksService user_id: model.id }) when ProjectMember - data.merge!({ - project_name: model.project.name, - project_path: model.project.path, - project_path_with_namespace: model.project.path_with_namespace, - project_id: model.project.id, - user_name: model.user.name, - user_email: model.user.email, - access_level: model.human_access, - project_visibility: Project.visibility_levels.key(model.project.visibility_level_field).downcase - }) + data.merge!(project_member_data(model)) when Group owner = model.owner @@ -72,15 +53,7 @@ class SystemHooksService owner_email: owner.respond_to?(:email) ? owner.email : nil, ) when GroupMember - data.merge!( - group_name: model.group.name, - group_path: model.group.path, - group_id: model.group.id, - user_name: model.user.name, - user_email: model.user.email, - user_id: model.user.id, - group_access: model.human_access, - ) + data.merge!(group_member_data(model)) end end @@ -96,4 +69,43 @@ class SystemHooksService "#{model.class.name.downcase}_#{event.to_s}" end end + + def project_data(model) + owner = model.owner + + { + name: model.name, + path: model.path, + path_with_namespace: model.path_with_namespace, + project_id: model.id, + owner_name: owner.name, + owner_email: owner.respond_to?(:email) ? owner.email : "", + project_visibility: Project.visibility_levels.key(model.visibility_level_field).downcase + } + end + + def project_member_data(model) + { + project_name: model.project.name, + project_path: model.project.path, + project_path_with_namespace: model.project.path_with_namespace, + project_id: model.project.id, + user_name: model.user.name, + user_email: model.user.email, + access_level: model.human_access, + project_visibility: Project.visibility_levels.key(model.project.visibility_level_field).downcase + } + end + + def group_member_data(model) + { + group_name: model.group.name, + group_path: model.group.path, + group_id: model.group.id, + user_name: model.user.name, + user_email: model.user.email, + user_id: model.user.id, + group_access: model.human_access, + } + end end From 857710634c516cea8516e73132027a361364211c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 00:13:45 +0100 Subject: [PATCH 116/140] Split complex method Issues::UpdateService#execute Signed-off-by: Dmitriy Zaporozhets --- app/services/issues/update_service.rb | 29 +++++++++++++++------------ 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index 551325e4ca..aa1fd79d22 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -22,24 +22,27 @@ module Issues issue, issue.labels - old_labels, old_labels - issue.labels) end - if issue.previous_changes.include?('milestone_id') - create_milestone_note(issue) - end - - if issue.previous_changes.include?('assignee_id') - create_assignee_note(issue) - notification_service.reassigned_issue(issue, current_user) - end - - if issue.previous_changes.include?('title') - create_title_change_note(issue, issue.previous_changes['title'].first) - end - + handle_changes(issue) issue.create_new_cross_references!(current_user) execute_hooks(issue, 'update') end issue end + + def handle_changes(issue) + if issue.previous_changes.include?('milestone_id') + create_milestone_note(issue) + end + + if issue.previous_changes.include?('assignee_id') + create_assignee_note(issue) + notification_service.reassigned_issue(issue, current_user) + end + + if issue.previous_changes.include?('title') + create_title_change_note(issue, issue.previous_changes['title'].first) + end + end end end From bcc82511966aba863a0a912302e8cfe3368ebc32 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 00:15:33 +0100 Subject: [PATCH 117/140] Split complex method EventsHelper#event_feed_url. Signed-off-by: Dmitriy Zaporozhets --- app/helpers/events_helper.rb | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 51b872b7a9..dde83ff36b 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -108,19 +108,23 @@ module EventsHelper end end elsif event.push? - if event.push_with_commits? && event.md_ref? - if event.commits_count > 1 - namespace_project_compare_url(event.project.namespace, event.project, - from: event.commit_from, to: - event.commit_to) - else - namespace_project_commit_url(event.project.namespace, event.project, - id: event.commit_to) - end + push_event_feed_url(event) + end + end + + def push_event_feed_url(event) + if event.push_with_commits? && event.md_ref? + if event.commits_count > 1 + namespace_project_compare_url(event.project.namespace, event.project, + from: event.commit_from, to: + event.commit_to) else - namespace_project_commits_url(event.project.namespace, event.project, - event.ref_name) + namespace_project_commit_url(event.project.namespace, event.project, + id: event.commit_to) end + else + namespace_project_commits_url(event.project.namespace, event.project, + event.ref_name) end end From ea6a78ef5cb665d6f3b06d71fa693ffa895b4b1a Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 00:44:16 +0100 Subject: [PATCH 118/140] Split complex Gitlab::InlineDiff::processing method Signed-off-by: Dmitriy Zaporozhets --- lib/gitlab/inline_diff.rb | 55 +++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/lib/gitlab/inline_diff.rb b/lib/gitlab/inline_diff.rb index 99e7b529ba..420c3099ce 100644 --- a/lib/gitlab/inline_diff.rb +++ b/lib/gitlab/inline_diff.rb @@ -16,15 +16,7 @@ module Gitlab # Skip inline diff if empty line was replaced with content next if first_line == "-\n" - first_the_same_symbols = 0 - (0..max_length + 1).each do |i| - first_the_same_symbols = i - 1 - if first_line[i] != second_line[i] && i > 0 - break - end - end - - first_token = first_line[0..first_the_same_symbols][1..-1] + first_token = find_first_token(first_line, second_line) start = first_token + START if first_token.empty? @@ -36,25 +28,50 @@ module Gitlab diff_arr[index+2].sub!(first_token, first_token => start) end - last_the_same_symbols = 0 - (1..max_length + 1).each do |i| - last_the_same_symbols = -i - shortest_line = second_line.size > first_line.size ? first_line : second_line - if ( first_line[-i] != second_line[-i] ) || "#{first_token}#{START}".size == shortest_line[1..-i].size - break - end - end - last_the_same_symbols += 1 - last_token = first_line[last_the_same_symbols..-1] + last_token = find_last_token(first_line, second_line, first_token) + # This is tricky: escape backslashes so that `sub` doesn't interpret them # as backreferences. Regexp.escape does NOT do the right thing. replace_token = FINISH + last_token.gsub(/\\/, '\&\&') diff_arr[index+1].sub!(/#{Regexp.escape(last_token)}$/, replace_token) diff_arr[index+2].sub!(/#{Regexp.escape(last_token)}$/, replace_token) end + diff_arr end + def find_first_token(first_line, second_line) + max_length = [first_line.size, second_line.size].max + first_the_same_symbols = 0 + + (0..max_length + 1).each do |i| + first_the_same_symbols = i - 1 + + if first_line[i] != second_line[i] && i > 0 + break + end + end + + first_line[0..first_the_same_symbols][1..-1] + end + + def find_last_token(first_line, second_line, first_token) + max_length = [first_line.size, second_line.size].max + last_the_same_symbols = 0 + + (1..max_length + 1).each do |i| + last_the_same_symbols = -i + shortest_line = second_line.size > first_line.size ? first_line : second_line + + if (first_line[-i] != second_line[-i]) || "#{first_token}#{START}".size == shortest_line[1..-i].size + break + end + end + + last_the_same_symbols += 1 + first_line[last_the_same_symbols..-1] + end + def _indexes_of_changed_lines(diff_arr) chain_of_first_symbols = "" diff_arr.each_with_index do |line, i| From c903f23f3c0b5654c2883cdf63593e53a8898a79 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 00:55:20 +0100 Subject: [PATCH 119/140] Split complex methods in GoogleCodeImport::Importer Signed-off-by: Dmitriy Zaporozhets --- lib/gitlab/google_code_import/importer.rb | 93 ++++++++++++----------- 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb index 03c410726a..87fee28dc0 100644 --- a/lib/gitlab/google_code_import/importer.rb +++ b/lib/gitlab/google_code_import/importer.rb @@ -30,7 +30,7 @@ module Gitlab def user_map @user_map ||= begin - user_map = Hash.new do |hash, user| + user_map = Hash.new do |hash, user| # Replace ... by \.\.\., so `johnsm...@gmail.com` isn't autolinked. Client.mask_email(user).sub("...", "\\.\\.\\.") end @@ -76,18 +76,7 @@ module Gitlab attachments = format_attachments(raw_issue["id"], 0, issue_comment["attachments"]) body = format_issue_body(author, date, content, attachments) - - labels = [] - raw_issue["labels"].each do |label| - name = nice_label_name(label) - labels << name - - unless @known_labels.include?(name) - create_label(name) - @known_labels << name - end - end - labels << nice_status_name(raw_issue["status"]) + labels = import_issue_labels(raw_issue) assignee_id = nil if raw_issue.has_key?("owner") @@ -110,6 +99,7 @@ module Gitlab assignee_id: assignee_id, state: raw_issue["state"] == "closed" ? "closed" : "opened" ) + issue.add_labels_by_names(labels) if issue.iid != raw_issue["id"] @@ -120,6 +110,23 @@ module Gitlab end end + def import_issue_labels(raw_issue) + labels = [] + + raw_issue["labels"].each do |label| + name = nice_label_name(label) + labels << name + + unless @known_labels.include?(name) + create_label(name) + @known_labels << name + end + end + + labels << nice_status_name(raw_issue["status"]) + labels + end + def import_issue_comments(issue, comments) Note.transaction do while raw_comment = comments.shift @@ -172,7 +179,7 @@ module Gitlab "#5cb85c" when "Status: Started" "#8e44ad" - + when "Priority: Critical" "#ffcfcf" when "Priority: High" @@ -181,7 +188,7 @@ module Gitlab "#fff5cc" when "Priority: Low" "#cfe9ff" - + when "Type: Defect" "#d9534f" when "Type: Enhancement" @@ -249,8 +256,8 @@ module Gitlab end if raw_updates.has_key?("cc") - cc = raw_updates["cc"].map do |l| - deleted = l.start_with?("-") + cc = raw_updates["cc"].map do |l| + deleted = l.start_with?("-") l = l[1..-1] if deleted l = user_map[l] l = "~~#{l}~~" if deleted @@ -261,8 +268,8 @@ module Gitlab end if raw_updates.has_key?("labels") - labels = raw_updates["labels"].map do |l| - deleted = l.start_with?("-") + labels = raw_updates["labels"].map do |l| + deleted = l.start_with?("-") l = l[1..-1] if deleted l = nice_label_name(l) l = "~~#{l}~~" if deleted @@ -278,45 +285,39 @@ module Gitlab if raw_updates.has_key?("blockedOn") blocked_ons = raw_updates["blockedOn"].map do |raw_blocked_on| - name, id = raw_blocked_on.split(":", 2) - - deleted = name.start_with?("-") - name = name[1..-1] if deleted - - text = - if name == project.import_source - "##{id}" - else - "#{project.namespace.path}/#{name}##{id}" - end - text = "~~#{text}~~" if deleted - text + format_blocking_updates(raw_blocked_on) end + updates << "*Blocked on: #{blocked_ons.join(", ")}*" end if raw_updates.has_key?("blocking") blockings = raw_updates["blocking"].map do |raw_blocked_on| - name, id = raw_blocked_on.split(":", 2) - - deleted = name.start_with?("-") - name = name[1..-1] if deleted - - text = - if name == project.import_source - "##{id}" - else - "#{project.namespace.path}/#{name}##{id}" - end - text = "~~#{text}~~" if deleted - text + format_blocking_updates(raw_blocked_on) end + updates << "*Blocking: #{blockings.join(", ")}*" end updates end + def format_blocking_updates(raw_blocked_on) + name, id = raw_blocked_on.split(":", 2) + + deleted = name.start_with?("-") + name = name[1..-1] if deleted + + text = + if name == project.import_source + "##{id}" + else + "#{project.namespace.path}/#{name}##{id}" + end + text = "~~#{text}~~" if deleted + text + end + def format_attachments(issue_id, comment_id, raw_attachments) return [] unless raw_attachments @@ -325,7 +326,7 @@ module Gitlab filename = attachment["fileName"] link = "https://storage.googleapis.com/google-code-attachments/#{@repo.name}/issue-#{issue_id}/comment-#{comment_id}/#{filename}" - + text = "[#{filename}](#{link})" text = "!#{text}" if filename =~ /\.(png|jpg|jpeg|gif|bmp|tiff)\z/i text From c71892e1a87ece19edf17f1d61c71d4b99953bc7 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 01:07:33 +0100 Subject: [PATCH 120/140] Even more refactoring to inline_diff.rb Signed-off-by: Dmitriy Zaporozhets --- lib/gitlab/inline_diff.rb | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/lib/gitlab/inline_diff.rb b/lib/gitlab/inline_diff.rb index 420c3099ce..72d0b493be 100644 --- a/lib/gitlab/inline_diff.rb +++ b/lib/gitlab/inline_diff.rb @@ -17,29 +17,36 @@ module Gitlab next if first_line == "-\n" first_token = find_first_token(first_line, second_line) - start = first_token + START - - if first_token.empty? - # In case if we remove string of spaces in commit - diff_arr[index+1].sub!("-", "-" => "-#{START}") - diff_arr[index+2].sub!("+", "+" => "+#{START}") - else - diff_arr[index+1].sub!(first_token, first_token => start) - diff_arr[index+2].sub!(first_token, first_token => start) - end + apply_first_token(diff_arr, index, first_token) last_token = find_last_token(first_line, second_line, first_token) - - # This is tricky: escape backslashes so that `sub` doesn't interpret them - # as backreferences. Regexp.escape does NOT do the right thing. - replace_token = FINISH + last_token.gsub(/\\/, '\&\&') - diff_arr[index+1].sub!(/#{Regexp.escape(last_token)}$/, replace_token) - diff_arr[index+2].sub!(/#{Regexp.escape(last_token)}$/, replace_token) + apply_last_token(diff_arr, index, last_token) end diff_arr end + def apply_first_token(diff_arr, index, first_token) + start = first_token + START + + if first_token.empty? + # In case if we remove string of spaces in commit + diff_arr[index+1].sub!("-", "-" => "-#{START}") + diff_arr[index+2].sub!("+", "+" => "+#{START}") + else + diff_arr[index+1].sub!(first_token, first_token => start) + diff_arr[index+2].sub!(first_token, first_token => start) + end + end + + def apply_last_token(diff_arr, index, last_token) + # This is tricky: escape backslashes so that `sub` doesn't interpret them + # as backreferences. Regexp.escape does NOT do the right thing. + replace_token = FINISH + last_token.gsub(/\\/, '\&\&') + diff_arr[index+1].sub!(/#{Regexp.escape(last_token)}$/, replace_token) + diff_arr[index+2].sub!(/#{Regexp.escape(last_token)}$/, replace_token) + end + def find_first_token(first_line, second_line) max_length = [first_line.size, second_line.size].max first_the_same_symbols = 0 From c613f2d9a85c60bdf34fdafdda41dd5ee1e32904 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 01:08:10 +0100 Subject: [PATCH 121/140] Don't allow flog failure any more Signed-off-by: Dmitriy Zaporozhets --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 128faa07db..141e7ba41d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -80,7 +80,6 @@ flog: tags: - ruby - mysql - allow_failure: true flay: script: From 3d0efa8e0a359c84485a0fd7a3317745bf5648b8 Mon Sep 17 00:00:00 2001 From: Minsik Yoon Date: Thu, 22 Oct 2015 09:55:35 +0900 Subject: [PATCH 122/140] Add ignore white space option in merge request diff fix this issue(https://gitlab.com/gitlab-org/gitlab-ce/issues/1393). Add ignore whitespace optoin to Commits Compare view --- CHANGELOG | 1 + .../projects/compare_controller.rb | 3 +- app/models/merge_request.rb | 2 +- app/models/merge_request_diff.rb | 16 +++++++++- app/services/compare_service.rb | 4 +-- .../merge_requests/show/_diffs.html.haml | 2 +- doc/workflow/merge_requests.md | 12 ++++++++ .../merge_requests/commit_compare.png | Bin 0 -> 89631 bytes .../merge_requests/merge_request_diff.png | Bin 0 -> 120422 bytes .../merge_request_diff_without_whitespace.png | Bin 0 -> 98887 bytes lib/gitlab/compare_result.rb | 4 +-- .../projects/compare_controller_spec.rb | 16 ++++++++++ .../merge_requests_controller_spec.rb | 28 ++++++++++++++++++ 13 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 doc/workflow/merge_requests/commit_compare.png create mode 100644 doc/workflow/merge_requests/merge_request_diff.png create mode 100644 doc/workflow/merge_requests/merge_request_diff_without_whitespace.png diff --git a/CHANGELOG b/CHANGELOG index 62def42d95..b8fe3b1c2f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -36,6 +36,7 @@ v 8.2.0 (unreleased) - MR target branch is now visible on a list view when it is different from project's default one - Improve Continuous Integration graphs page - Make color of "Accept Merge Request" button consistent with current build status + - Add ignore white space option in merge request diff and commit and compare view v 8.1.4 - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 71aaad1fad..55134e11d1 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -12,9 +12,10 @@ class Projects::CompareController < Projects::ApplicationController def show base_ref = Addressable::URI.unescape(params[:from]) @ref = head_ref = Addressable::URI.unescape(params[:to]) + diff_options = { ignore_whitespace_change: true } if params[:w] == '1' compare_result = CompareService.new. - execute(@project, head_ref, @project, base_ref) + execute(@project, head_ref, @project, base_ref, diff_options) if compare_result @commits = Commit.decorate(compare_result.commits, @project) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 85f37e49e6..e81d65c233 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -40,7 +40,7 @@ class MergeRequest < ActiveRecord::Base after_create :create_merge_request_diff after_update :update_merge_request_diff - delegate :commits, :diffs, to: :merge_request_diff, prefix: nil + delegate :commits, :diffs, :diffs_no_whitespace, to: :merge_request_diff, prefix: nil # When this attribute is true some MR validation is ignored # It allows us to close or modify broken merge requests diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 6575d0bc81..c499a4b5b4 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -19,7 +19,7 @@ class MergeRequestDiff < ActiveRecord::Base # Prevent store of diff if commits amount more then 500 COMMITS_SAFE_SIZE = 500 - attr_reader :commits, :diffs + attr_reader :commits, :diffs, :diffs_no_whitespace belongs_to :merge_request @@ -47,6 +47,20 @@ class MergeRequestDiff < ActiveRecord::Base @diffs ||= (load_diffs(st_diffs) || []) end + def diffs_no_whitespace + # Get latest sha of branch from source project + source_sha = merge_request.source_project.commit(source_branch).sha + + compare_result = Gitlab::CompareResult.new( + Gitlab::Git::Compare.new( + merge_request.target_project.repository.raw_repository, + merge_request.target_branch, + source_sha, + ), { ignore_whitespace_change: true } + ) + @diffs_no_whitespace ||= load_diffs(dump_commits(compare_result.diffs)) + end + def commits @commits ||= load_commits(st_commits || []) end diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index bfe6a3dc4b..ec581658fc 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -3,7 +3,7 @@ require 'securerandom' # Compare 2 branches for one repo or between repositories # and return Gitlab::CompareResult object that responds to commits and diffs class CompareService - def execute(source_project, source_branch, target_project, target_branch) + def execute(source_project, source_branch, target_project, target_branch, diff_options = {}) source_commit = source_project.commit(source_branch) return unless source_commit @@ -25,7 +25,7 @@ class CompareService target_project.repository.raw_repository, target_branch, source_sha, - ) + ), diff_options ) end end diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml index 626970f39b..d9cfc3d7ae 100644 --- a/app/views/projects/merge_requests/show/_diffs.html.haml +++ b/app/views/projects/merge_requests/show/_diffs.html.haml @@ -1,5 +1,5 @@ - if @merge_request_diff.collected? - = render "projects/diffs/diffs", diffs: @merge_request.diffs, project: @merge_request.project + = render "projects/diffs/diffs", diffs: params[:w] == '1' ? @merge_request.diffs_no_whitespace : @merge_request.diffs, project: @merge_request.project - elsif @merge_request_diff.empty? .nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch} - else diff --git a/doc/workflow/merge_requests.md b/doc/workflow/merge_requests.md index 751e19da7f..6d57b5d98c 100644 --- a/doc/workflow/merge_requests.md +++ b/doc/workflow/merge_requests.md @@ -38,3 +38,15 @@ To check out a particular merge request: ``` $ git checkout origin/merge-requests/1 ``` + +## Ignore whitespace changes in Merge Request diff view + +![MR diff](merge_requests/merge_request_diff.png) + +It you add `w=1` option to URL, you can see diff without whitespace changes. + +![MR diff without whitespace](merge_requests/merge_request_diff_without_whitespace.png) + +It is also working on commits compare view. + +![Commit Compare](merge_requests/commit_compare.png) diff --git a/doc/workflow/merge_requests/commit_compare.png b/doc/workflow/merge_requests/commit_compare.png new file mode 100644 index 0000000000000000000000000000000000000000..46b3a56a59b9cdf0317d7ae8c1903a65eca2c5af GIT binary patch literal 89631 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∓5Wdx0(-)x_xiuIh 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 literal 0 HcmV?d00001 diff --git a/doc/workflow/merge_requests/merge_request_diff.png b/doc/workflow/merge_requests/merge_request_diff.png new file mode 100644 index 0000000000000000000000000000000000000000..ed08ae91bec73772c3cd51521a46edfea9aace93 GIT binary patch literal 120422 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 literal 0 HcmV?d00001 diff --git a/doc/workflow/merge_requests/merge_request_diff_without_whitespace.png b/doc/workflow/merge_requests/merge_request_diff_without_whitespace.png new file mode 100644 index 0000000000000000000000000000000000000000..67d67a64d122ffa00c76a34a095f56b6f48adb01 GIT binary patch literal 98887 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

!Zn}kl#=#>N^LaqhdtCvCcje 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 literal 0 HcmV?d00001 diff --git a/lib/gitlab/compare_result.rb b/lib/gitlab/compare_result.rb index d72391dade..0d696a1ee2 100644 --- a/lib/gitlab/compare_result.rb +++ b/lib/gitlab/compare_result.rb @@ -2,8 +2,8 @@ module Gitlab class CompareResult attr_reader :commits, :diffs - def initialize(compare) - @commits, @diffs = compare.commits, compare.diffs + def initialize(compare, diff_options = {}) + @commits, @diffs = compare.commits, compare.diffs(nil, diff_options) end end end diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb index 2a447248b7..be19f1abc5 100644 --- a/spec/controllers/projects/compare_controller_spec.rb +++ b/spec/controllers/projects/compare_controller_spec.rb @@ -23,6 +23,22 @@ describe Projects::CompareController do expect(assigns(:commits).length).to be >= 1 end + it 'compare should show some diffs with ignore whitespace change option' do + get(:show, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + from: '08f22f25', + to: '66eceea0', + w: 1) + + expect(response).to be_success + expect(assigns(:diffs).length).to be >= 1 + expect(assigns(:commits).length).to be >= 1 + # without whitespace option, there are more than 2 diff_splits + diff_splits = assigns(:diffs)[0].diff.split("\n") + expect(diff_splits.length).to be <= 2 + end + describe 'non-existent refs' do it 'invalid source ref' do get(:show, diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index b8db859170..3e5e1fa87a 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -147,6 +147,34 @@ describe Projects::MergeRequestsController do end end + describe 'GET diffs with ignore_whitespace_change' do + def go(format: 'html') + get :diffs, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: merge_request.iid, + format: format, + w: 1 + end + + context 'as html' do + it 'renders the diff template' do + go + + expect(response).to render_template('diffs') + end + end + + context 'as json' do + it 'renders the diffs template to a string' do + go format: 'json' + + expect(response).to render_template('projects/merge_requests/show/_diffs') + expect(JSON.parse(response.body)).to have_key('html') + end + end + end + describe 'GET commits' do def go(format: 'html') get :commits, From 3c16fb93fcfb7dc50a46a39cbdd545ddfdab0bc1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 09:45:21 +0100 Subject: [PATCH 123/140] Move spec to proper place and fix unused variable Signed-off-by: Dmitriy Zaporozhets --- lib/gitlab/inline_diff.rb | 1 - spec/lib/gitlab/{diff => }/inline_diff_spec.rb | 0 2 files changed, 1 deletion(-) rename spec/lib/gitlab/{diff => }/inline_diff_spec.rb (100%) diff --git a/lib/gitlab/inline_diff.rb b/lib/gitlab/inline_diff.rb index 72d0b493be..44507bde25 100644 --- a/lib/gitlab/inline_diff.rb +++ b/lib/gitlab/inline_diff.rb @@ -11,7 +11,6 @@ module Gitlab indexes.each do |index| first_line = diff_arr[index+1] second_line = diff_arr[index+2] - max_length = [first_line.size, second_line.size].max # Skip inline diff if empty line was replaced with content next if first_line == "-\n" diff --git a/spec/lib/gitlab/diff/inline_diff_spec.rb b/spec/lib/gitlab/inline_diff_spec.rb similarity index 100% rename from spec/lib/gitlab/diff/inline_diff_spec.rb rename to spec/lib/gitlab/inline_diff_spec.rb From 6384c757b7ce6d1c0c3e2a3828b0cfac26dfb7f9 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 9 Nov 2015 16:48:03 +0100 Subject: [PATCH 124/140] Expose CI enable option in project features - Enable CI by default for all new projects --- .../projects/application_controller.rb | 2 +- app/controllers/projects_controller.rb | 3 ++- app/helpers/projects_helper.rb | 2 +- app/models/project.rb | 23 ++++++++++++++----- app/services/git_push_service.rb | 2 +- app/services/projects/fork_service.rb | 2 +- .../layouts/nav/_project_settings.html.haml | 2 +- app/views/projects/edit.html.haml | 11 ++++++++- app/views/projects/graphs/_head.html.haml | 2 +- config/gitlab.yml.example | 1 + config/initializers/1_settings.rb | 1 + doc/api/projects.md | 6 +++++ lib/api/entities.rb | 2 +- lib/api/projects.rb | 6 +++++ spec/factories/ci/projects.rb | 12 ++++++---- spec/models/project_spec.rb | 9 +++++--- spec/requests/api/projects_spec.rb | 7 ++++-- spec/requests/api/services_spec.rb | 1 + spec/services/projects/create_service_spec.rb | 22 ++++++++++++++++++ spec/services/projects/fork_service_spec.rb | 2 +- 20 files changed, 93 insertions(+), 25 deletions(-) diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index 519d6d6127..d3f926b62b 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -29,7 +29,7 @@ class Projects::ApplicationController < ApplicationController private def ci_enabled - return render_404 unless @project.gitlab_ci? + return render_404 unless @project.builds_enabled? end def ci_project diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 00d13a83ce..30b166334a 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -213,7 +213,8 @@ class ProjectsController < ApplicationController params.require(:project).permit( :name, :path, :description, :issues_tracker, :tag_list, :issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch, - :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar + :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar, + :builds_enabled ) end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 5301c2ccf7..690ae2090d 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -117,7 +117,7 @@ module ProjectsHelper nav_tabs << :merge_requests end - if project.gitlab_ci? && can?(current_user, :read_build, project) + if project.builds_enabled? && can?(current_user, :read_build, project) nav_tabs << :builds end diff --git a/app/models/project.rb b/app/models/project.rb index bdb22e49bb..3e72a9a46a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -52,6 +52,7 @@ class Project < ActiveRecord::Base default_value_for :visibility_level, gitlab_config_features.visibility_level default_value_for :issues_enabled, gitlab_config_features.issues default_value_for :merge_requests_enabled, gitlab_config_features.merge_requests + default_value_for :builds_enabled, gitlab_config_features.builds default_value_for :wiki_enabled, gitlab_config_features.wiki default_value_for :wall_enabled, false default_value_for :snippets_enabled, gitlab_config_features.snippets @@ -457,10 +458,6 @@ class Project < ActiveRecord::Base list.find { |service| service.to_param == name } end - def gitlab_ci? - gitlab_ci_service && gitlab_ci_service.active && gitlab_ci_project.present? - end - def ci_services services.select { |service| service.category == :ci } end @@ -782,9 +779,23 @@ class Project < ActiveRecord::Base ) end - def enable_ci + # TODO: this should be migrated to Project table, + # the same as issues_enabled + def builds_enabled + gitlab_ci_service && gitlab_ci_service.active + end + + def builds_enabled? + builds_enabled + end + + def builds_enabled=(value) service = gitlab_ci_service || create_gitlab_ci_service - service.active = true + service.active = value service.save end + + def enable_ci + self.builds_enabled = true + end end diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 3de7bb9dca..ccb6b97858 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -60,7 +60,7 @@ class GitPushService # If CI was disabled but .gitlab-ci.yml file was pushed # we enable CI automatically - if !project.gitlab_ci? && gitlab_ci_yaml?(newrev) + if !project.builds_enabled? && gitlab_ci_yaml?(newrev) project.enable_ci end diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb index 46374a3909..5da1c7afd9 100644 --- a/app/services/projects/fork_service.rb +++ b/app/services/projects/fork_service.rb @@ -17,7 +17,7 @@ module Projects new_project = CreateService.new(current_user, new_params).execute if new_project.persisted? - if @project.gitlab_ci? + if @project.builds_enabled? new_project.enable_ci settings = @project.gitlab_ci_project.attributes.select do |attr_name, value| diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml index a59939ccd3..377a99e719 100644 --- a/app/views/layouts/nav/_project_settings.html.haml +++ b/app/views/layouts/nav/_project_settings.html.haml @@ -34,7 +34,7 @@ %span Protected Branches - - if @project.gitlab_ci? + - if @project.builds_enabled? = nav_link(controller: :runners) do = link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners', data: {placement: 'right'} do = icon('cog fw') diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index afbf88b550..3ebc175648 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -57,7 +57,16 @@ = f.check_box :merge_requests_enabled %strong Merge Requests %br - %span.descr Submit changes to be merged upstream. + %span.descr Submit changes to be merged upstream + + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :builds_enabled do + = f.check_box :builds_enabled + %strong Builds + %br + %span.descr Test and deploy your changes before merge .form-group .col-sm-offset-2.col-sm-10 diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml index e0d06a14bf..03d0733f91 100644 --- a/app/views/projects/graphs/_head.html.haml +++ b/app/views/projects/graphs/_head.html.haml @@ -3,7 +3,7 @@ = link_to 'Contributors', namespace_project_graph_path = nav_link(action: :commits) do = link_to 'Commits', commits_namespace_project_graph_path - - if @project.gitlab_ci? + - if @project.builds_enabled? = nav_link(action: :ci) do = link_to ci_namespace_project_graph_path do Continuous Integration diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 20894ebcdc..5bd98e3e42 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -84,6 +84,7 @@ production: &base merge_requests: true wiki: true snippets: false + builds: true ## Webhook settings # Number of seconds to wait for HTTP response after sending webhook HTTP POST request (default: 10) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 302124bd97..847d9f2189 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -171,6 +171,7 @@ Settings.gitlab.default_projects_features['issues'] = true if Settings.g Settings.gitlab.default_projects_features['merge_requests'] = true if Settings.gitlab.default_projects_features['merge_requests'].nil? Settings.gitlab.default_projects_features['wiki'] = true if Settings.gitlab.default_projects_features['wiki'].nil? Settings.gitlab.default_projects_features['snippets'] = false if Settings.gitlab.default_projects_features['snippets'].nil? +Settings.gitlab.default_projects_features['builds'] = true if Settings.gitlab.default_projects_features['builds'].nil? Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE) Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive') if Settings.gitlab['repository_downloads_path'].nil? Settings.gitlab['restricted_signup_domains'] ||= [] diff --git a/doc/api/projects.md b/doc/api/projects.md index 9648585703..755cc6525c 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -60,6 +60,7 @@ Parameters: "path_with_namespace": "diaspora/diaspora-client", "issues_enabled": true, "merge_requests_enabled": true, + "builds_enabled": true, "wiki_enabled": true, "snippets_enabled": false, "created_at": "2013-09-30T13: 46: 02Z", @@ -101,6 +102,7 @@ Parameters: "path_with_namespace": "brightbox/puppet", "issues_enabled": true, "merge_requests_enabled": true, + "builds_enabled": true, "wiki_enabled": true, "snippets_enabled": false, "created_at": "2013-09-30T13:46:02Z", @@ -191,6 +193,7 @@ Parameters: "path_with_namespace": "diaspora/diaspora-project-site", "issues_enabled": true, "merge_requests_enabled": true, + "builds_enabled": true, "wiki_enabled": true, "snippets_enabled": false, "created_at": "2013-09-30T13: 46: 02Z", @@ -312,6 +315,7 @@ Parameters: - `description` (optional) - short project description - `issues_enabled` (optional) - `merge_requests_enabled` (optional) +- `builds_enabled` (optional) - `wiki_enabled` (optional) - `snippets_enabled` (optional) - `public` (optional) - if `true` same as setting visibility_level = 20 @@ -334,6 +338,7 @@ Parameters: - `default_branch` (optional) - 'master' by default - `issues_enabled` (optional) - `merge_requests_enabled` (optional) +- `builds_enabled` (optional) - `wiki_enabled` (optional) - `snippets_enabled` (optional) - `public` (optional) - if `true` same as setting visibility_level = 20 @@ -357,6 +362,7 @@ Parameters: - `default_branch` (optional) - `issues_enabled` (optional) - `merge_requests_enabled` (optional) +- `builds_enabled` (optional) - `wiki_enabled` (optional) - `snippets_enabled` (optional) - `public` (optional) - if `true` same as setting visibility_level = 20 diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 20cadae229..73acf66935 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -62,7 +62,7 @@ module API expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group } expose :name, :name_with_namespace expose :path, :path_with_namespace - expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at + expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :builds_enabled, :snippets_enabled, :created_at, :last_activity_at expose :creator_id expose :namespace expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ | project, options | project.forked? } diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 67ee66a205..2b4ada6e2e 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -75,6 +75,7 @@ module API # description (optional) - short project description # issues_enabled (optional) # merge_requests_enabled (optional) + # builds_enabled (optional) # wiki_enabled (optional) # snippets_enabled (optional) # namespace_id (optional) - defaults to user namespace @@ -90,6 +91,7 @@ module API :description, :issues_enabled, :merge_requests_enabled, + :builds_enabled, :wiki_enabled, :snippets_enabled, :namespace_id, @@ -117,6 +119,7 @@ module API # default_branch (optional) - 'master' by default # issues_enabled (optional) # merge_requests_enabled (optional) + # builds_enabled (optional) # wiki_enabled (optional) # snippets_enabled (optional) # public (optional) - if true same as setting visibility_level = 20 @@ -132,6 +135,7 @@ module API :default_branch, :issues_enabled, :merge_requests_enabled, + :builds_enabled, :wiki_enabled, :snippets_enabled, :public, @@ -172,6 +176,7 @@ module API # description (optional) - short project description # issues_enabled (optional) # merge_requests_enabled (optional) + # builds_enabled (optional) # wiki_enabled (optional) # snippets_enabled (optional) # public (optional) - if true same as setting visibility_level = 20 @@ -185,6 +190,7 @@ module API :default_branch, :issues_enabled, :merge_requests_enabled, + :builds_enabled, :wiki_enabled, :snippets_enabled, :public, diff --git a/spec/factories/ci/projects.rb b/spec/factories/ci/projects.rb index 1183a19035..11cb8c9eea 100644 --- a/spec/factories/ci/projects.rb +++ b/spec/factories/ci/projects.rb @@ -31,16 +31,20 @@ FactoryGirl.define do factory :ci_project_without_token, class: Ci::Project do default_ref 'master' - gl_project factory: :empty_project - shared_runners_enabled false factory :ci_project do token 'iPWx6WM4lhHNedGfBpPJNP' end - factory :ci_public_project do - public true + initialize_with do + # TODO: + # this is required, because builds_enabled is initialized when Project is created + # and this create gitlab_ci_project if builds is set to true + # here we take created gitlab_ci_project and update it's attributes + ci_project = create(:empty_project).ensure_gitlab_ci_project + ci_project.update_attributes(attributes) + ci_project end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index f93935ebe3..8d7e6e7676 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -415,12 +415,15 @@ describe Project do it { expect(project.ci_commit(commit.sha)).to eq(commit) } end - describe :enable_ci do + describe :builds_enabled do let(:project) { create :project } - before { project.enable_ci } + before { project.builds_enabled = true } - it { expect(project.gitlab_ci?).to be_truthy } + subject { project.builds_enabled } + + it { is_expected.to eq(project.gitlab_ci_service.active) } + it { expect(project.builds_enabled?).to be_truthy } it { expect(project.gitlab_ci_project).to be_a(Ci::Project) } end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index e9de9e0826..9fc294118a 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -88,8 +88,11 @@ describe API::API, api: true do end it 'returns projects in the correct order when ci_enabled_first parameter is passed' do - [project, project2, project3].each{ |project| project.build_missing_services } - project2.gitlab_ci_service.update(active: true) + [project, project2, project3].each do |project| + project.builds_enabled = false + project.build_missing_services + end + project2.builds_enabled = true get api('/projects', user), { ci_enabled_first: 'true' } expect(response.status).to eq(200) expect(json_response).to be_an Array diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index c0226605a2..b180d2fec7 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -46,6 +46,7 @@ describe API::API, api: true do delete api("/projects/#{project.id}/services/#{dashed_service}", user) expect(response.status).to eq(200) + project.send(service_method).reload expect(project.send(service_method).activated?).to be_falsey end end diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index 25277f0748..e81c4edb7d 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -70,6 +70,28 @@ describe Projects::CreateService do end end + context 'builds_enabled global setting' do + let(:project) { create_project(@user, @opts) } + + subject { project.builds_enabled? } + + context 'global builds_enabled false does not enable CI by default' do + before do + @opts.merge!(builds_enabled: false) + end + + it { is_expected.to be_falsey } + end + + context 'global builds_enabled true does enable CI by default' do + before do + @opts.merge!(builds_enabled: true) + end + + it { is_expected.to be_truthy } + end + end + context 'restricted visibility level' do before do stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index 65a8c81204..e397b2b9b4 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -46,7 +46,7 @@ describe Projects::ForkService do it "fork and enable CI for fork" do @from_project.enable_ci @to_project = fork_project(@from_project, @to_user) - expect(@to_project.gitlab_ci?).to be_truthy + expect(@to_project.builds_enabled?).to be_truthy end end end From e53a56aceae6e79412a853ac3cfd1e47570135eb Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 12 Nov 2015 16:52:22 +0100 Subject: [PATCH 125/140] Fix broken tests --- app/models/ci/project.rb | 24 ---- spec/models/ci/project_spec.rb | 16 +-- spec/requests/ci/api/builds_spec.rb | 4 +- spec/requests/ci/api/commits_spec.rb | 2 +- spec/requests/ci/api/projects_spec.rb | 116 +++++++----------- spec/requests/ci/api/triggers_spec.rb | 2 +- .../ci/create_trigger_request_service_spec.rb | 2 +- 7 files changed, 48 insertions(+), 118 deletions(-) diff --git a/app/models/ci/project.rb b/app/models/ci/project.rb index 4e806ca1a6..f81417ba27 100644 --- a/app/models/ci/project.rb +++ b/app/models/ci/project.rb @@ -66,30 +66,6 @@ module Ci class << self include Ci::CurrentSettings - def base_build_script - <<-eos - git submodule update --init - ls -la - eos - end - - def parse(project) - params = { - gitlab_id: project.id, - default_ref: project.default_branch || 'master', - email_add_pusher: current_application_settings.add_pusher, - email_only_broken_builds: current_application_settings.all_broken_builds, - } - - project = Ci::Project.new(params) - project.build_missing_services - project - end - - def already_added?(project) - where(gitlab_id: project.id).any? - end - def unassigned(runner) joins("LEFT JOIN #{Ci::RunnerProject.table_name} ON #{Ci::RunnerProject.table_name}.project_id = #{Ci::Project.table_name}.id " \ "AND #{Ci::RunnerProject.table_name}.runner_id = #{runner.id}"). diff --git a/spec/models/ci/project_spec.rb b/spec/models/ci/project_spec.rb index 490c6a6798..4546cbfad1 100644 --- a/spec/models/ci/project_spec.rb +++ b/spec/models/ci/project_spec.rb @@ -28,8 +28,8 @@ require 'spec_helper' describe Ci::Project do - let(:gl_project) { FactoryGirl.create :empty_project } - let(:project) { FactoryGirl.create :ci_project, gl_project: gl_project } + let(:project) { FactoryGirl.create :ci_project } + let(:gl_project) { project.gl_project } subject { project } it { is_expected.to have_many(:runner_projects) } @@ -194,18 +194,6 @@ describe Ci::Project do end end - describe 'Project.parse' do - let(:project) { FactoryGirl.create :project } - - subject { Ci::Project.parse(project) } - - it { is_expected.to be_valid } - it { is_expected.to be_kind_of(Ci::Project) } - it { expect(subject.name).to eq(project.name_with_namespace) } - it { expect(subject.gitlab_id).to eq(project.id) } - it { expect(subject.gitlab_url).to eq(project.web_url) } - end - describe :repo_url_with_auth do let(:project) { FactoryGirl.create :ci_project } subject { project.repo_url_with_auth } diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 7886a6feca..392dc2021f 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -5,7 +5,7 @@ describe Ci::API::API do let(:runner) { FactoryGirl.create(:ci_runner, tag_list: ["mysql", "ruby"]) } let(:project) { FactoryGirl.create(:ci_project) } - let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) } + let(:gl_project) { project.gl_project } before do stub_ci_commit_to_return_yaml_file @@ -14,7 +14,7 @@ describe Ci::API::API do describe "Builds API for runners" do let(:shared_runner) { FactoryGirl.create(:ci_runner, token: "SharedRunner") } let(:shared_project) { FactoryGirl.create(:ci_project, name: "SharedProject") } - let(:shared_gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: shared_project) } + let(:shared_gl_project) { shared_project.gl_project } before do FactoryGirl.create :ci_runner_project, project_id: project.id, runner_id: runner.id diff --git a/spec/requests/ci/api/commits_spec.rb b/spec/requests/ci/api/commits_spec.rb index 6049135fd1..aa51ba95bc 100644 --- a/spec/requests/ci/api/commits_spec.rb +++ b/spec/requests/ci/api/commits_spec.rb @@ -4,7 +4,7 @@ describe Ci::API::API, 'Commits' do include ApiHelpers let(:project) { FactoryGirl.create(:ci_project) } - let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) } + let(:gl_project) { project.gl_project } let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) } let(:options) do diff --git a/spec/requests/ci/api/projects_spec.rb b/spec/requests/ci/api/projects_spec.rb index 53f7f91cc1..893fd168d3 100644 --- a/spec/requests/ci/api/projects_spec.rb +++ b/spec/requests/ci/api/projects_spec.rb @@ -41,8 +41,8 @@ describe Ci::API::API do describe "GET /projects/owned" do let!(:gl_project1) {FactoryGirl.create(:empty_project, namespace: user.namespace)} let!(:gl_project2) {FactoryGirl.create(:empty_project, namespace: user.namespace)} - let!(:project1) { FactoryGirl.create(:ci_project, gl_project: gl_project1) } - let!(:project2) { FactoryGirl.create(:ci_project, gl_project: gl_project2) } + let!(:project1) { gl_project1.ensure_gitlab_ci_project } + let!(:project2) { gl_project2.ensure_gitlab_ci_project } before do project1.gl_project.team << [user, :developer] @@ -180,87 +180,53 @@ describe Ci::API::API do end end - describe "POST /projects" do - let(:gl_project) { FactoryGirl.create :empty_project } - let(:project_info) do - { - gitlab_id: gl_project.id - } + describe "POST /projects/:id/runners/:id" do + let(:project) { FactoryGirl.create(:ci_project) } + let(:runner) { FactoryGirl.create(:ci_runner) } + + it "should add the project to the runner" do + project.gl_project.team << [user, :master] + post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options + expect(response.status).to eq(201) + + project.reload + expect(project.runners.first.id).to eq(runner.id) end - let(:invalid_project_info) { {} } + it "should fail if it tries to link a non-existing project or runner" do + post ci_api("/projects/#{project.id}/runners/non-existing"), options + expect(response.status).to eq(404) - context "with valid project info" do - before do - options.merge!(project_info) - end - - it "should create a project with valid data" do - post ci_api("/projects"), options - expect(response.status).to eq(201) - expect(json_response['name']).to eq(gl_project.name_with_namespace) - end + post ci_api("/projects/non-existing/runners/#{runner.id}"), options + expect(response.status).to eq(404) end - context "with invalid project info" do - before do - options.merge!(invalid_project_info) - end + it "non-manager is not authorized" do + allow_any_instance_of(User).to receive(:can_manage_project?).and_return(false) + post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options + expect(response.status).to eq(401) + end + end - it "should error with invalid data" do - post ci_api("/projects"), options - expect(response.status).to eq(400) - end + describe "DELETE /projects/:id/runners/:id" do + let(:project) { FactoryGirl.create(:ci_project) } + let(:runner) { FactoryGirl.create(:ci_runner) } + + it "should remove the project from the runner" do + project.gl_project.team << [user, :master] + post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options + + expect(project.runners).to be_present + delete ci_api("/projects/#{project.id}/runners/#{runner.id}"), options + expect(response.status).to eq(200) + + project.reload + expect(project.runners).to be_empty end - describe "POST /projects/:id/runners/:id" do - let(:project) { FactoryGirl.create(:ci_project) } - let(:runner) { FactoryGirl.create(:ci_runner) } - - it "should add the project to the runner" do - project.gl_project.team << [user, :master] - post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options - expect(response.status).to eq(201) - - project.reload - expect(project.runners.first.id).to eq(runner.id) - end - - it "should fail if it tries to link a non-existing project or runner" do - post ci_api("/projects/#{project.id}/runners/non-existing"), options - expect(response.status).to eq(404) - - post ci_api("/projects/non-existing/runners/#{runner.id}"), options - expect(response.status).to eq(404) - end - - it "non-manager is not authorized" do - allow_any_instance_of(User).to receive(:can_manage_project?).and_return(false) - post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options - expect(response.status).to eq(401) - end - end - - describe "DELETE /projects/:id/runners/:id" do - let(:project) { FactoryGirl.create(:ci_project) } - let(:runner) { FactoryGirl.create(:ci_runner) } - - it "should remove the project from the runner" do - project.gl_project.team << [user, :master] - post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options - - expect(project.runners).to be_present - delete ci_api("/projects/#{project.id}/runners/#{runner.id}"), options - expect(response.status).to eq(200) - - project.reload - expect(project.runners).to be_empty - end - - it "non-manager is not authorized" do - delete ci_api("/projects/#{project.id}/runners/#{runner.id}"), options - expect(response.status).to eq(401) - end + it "non-manager is not authorized" do + delete ci_api("/projects/#{project.id}/runners/#{runner.id}"), options + expect(response.status).to eq(401) end end end diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb index 93617fc4b3..a2b436d581 100644 --- a/spec/requests/ci/api/triggers_spec.rb +++ b/spec/requests/ci/api/triggers_spec.rb @@ -6,7 +6,7 @@ describe Ci::API::API do describe 'POST /projects/:project_id/refs/:ref/trigger' do let!(:trigger_token) { 'secure token' } let!(:gl_project) { FactoryGirl.create(:project) } - let!(:project) { FactoryGirl.create(:ci_project, gl_project: gl_project) } + let!(:project) { gl_project.ensure_gitlab_ci_project } let!(:project2) { FactoryGirl.create(:ci_project) } let!(:trigger) { FactoryGirl.create(:ci_trigger, project: project, token: trigger_token) } let(:options) do diff --git a/spec/services/ci/create_trigger_request_service_spec.rb b/spec/services/ci/create_trigger_request_service_spec.rb index fcafae3864..2ef4bb50a5 100644 --- a/spec/services/ci/create_trigger_request_service_spec.rb +++ b/spec/services/ci/create_trigger_request_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Ci::CreateTriggerRequestService do let(:service) { Ci::CreateTriggerRequestService.new } let(:gl_project) { create(:project) } - let(:project) { create(:ci_project, gl_project: gl_project) } + let(:project) { gl_project.ensure_gitlab_ci_project } let(:trigger) { create(:ci_trigger, project: project) } before do From f36698663cc3a87cc5deec07c693020d97e174f7 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 13 Nov 2015 11:15:17 +0100 Subject: [PATCH 126/140] Bump the GITLAB_WORKHORSE version to 0.4.1 [ci skip] --- CHANGELOG | 1 + GITLAB_WORKHORSE | 1 + 2 files changed, 2 insertions(+) create mode 100644 GITLAB_WORKHORSE diff --git a/CHANGELOG b/CHANGELOG index c766e8ea98..1bd60728e6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -21,6 +21,7 @@ v 8.2.0 (unreleased) - Send build name and stage in CI notification e-mail - Extend yml syntax for only and except to support specifying repository path - Enable shared runners to all new projects + - Bump GitLab-Workhorse to 0.4.1 - Allow to define cache in `.gitlab-ci.yml` - Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu) - Remove deprecated CI events from project settings page diff --git a/GITLAB_WORKHORSE b/GITLAB_WORKHORSE new file mode 100644 index 0000000000..267577d47e --- /dev/null +++ b/GITLAB_WORKHORSE @@ -0,0 +1 @@ +0.4.1 From fea2f214370420cdb86336881bfbcb24a994f6c6 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 13 Nov 2015 14:41:19 +0100 Subject: [PATCH 127/140] Fix caching breaking test of build artifacts --- spec/requests/ci/api/builds_spec.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 392dc2021f..c2be045099 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -160,15 +160,13 @@ describe Ci::API::API do end it "using token as parameter" do - settings = Gitlab::CurrentSettings::current_application_settings - settings.update_attributes(max_artifacts_size: 0) + stub_application_setting(max_artifacts_size: 0) post authorize_url, { token: build.project.token, filesize: 100 }, headers expect(response.status).to eq(413) end it "using token as header" do - settings = Gitlab::CurrentSettings::current_application_settings - settings.update_attributes(max_artifacts_size: 0) + stub_application_setting(max_artifacts_size: 0) post authorize_url, { filesize: 100 }, headers_with_token expect(response.status).to eq(413) end @@ -220,8 +218,7 @@ describe Ci::API::API do end it do - settings = Gitlab::CurrentSettings::current_application_settings - settings.update_attributes(max_artifacts_size: 0) + stub_application_setting(max_artifacts_size: 0) upload_artifacts(file_upload, headers_with_token) expect(response.status).to eq(413) end From a237999f000526b3db5b0b5a72a665adcff29522 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 19:22:46 +0100 Subject: [PATCH 128/140] Annotate models Signed-off-by: Dmitriy Zaporozhets --- app/models/application_setting.rb | 4 +++ app/models/ci/application_setting.rb | 2 +- app/models/ci/build.rb | 14 ++++++-- app/models/ci/commit.rb | 25 +++++++------- app/models/ci/event.rb | 2 +- app/models/ci/project.rb | 4 +-- app/models/ci/runner.rb | 2 +- app/models/ci/runner_project.rb | 2 +- app/models/ci/service.rb | 2 +- app/models/ci/trigger.rb | 2 +- app/models/ci/trigger_request.rb | 2 +- app/models/ci/variable.rb | 2 +- app/models/ci/web_hook.rb | 2 +- app/models/commit_status.rb | 33 +++++++++++++++++++ app/models/generic_commit_status.rb | 33 +++++++++++++++++++ app/models/group.rb | 1 + app/models/hooks/project_hook.rb | 25 +++++++------- app/models/hooks/service_hook.rb | 25 +++++++------- app/models/hooks/system_hook.rb | 25 +++++++------- app/models/hooks/web_hook.rb | 25 +++++++------- app/models/label.rb | 1 + app/models/merge_request.rb | 1 + app/models/namespace.rb | 1 + .../project_services/ci/hip_chat_service.rb | 2 +- .../project_services/ci/mail_service.rb | 2 +- .../project_services/ci/slack_service.rb | 2 +- app/models/release.rb | 12 +++++++ app/models/user.rb | 1 + spec/factories/labels.rb | 1 + spec/factories/merge_requests.rb | 1 + spec/factories/releases.rb | 12 +++++++ spec/models/application_setting_spec.rb | 4 +++ spec/models/ci/commit_spec.rb | 25 +++++++------- spec/models/ci/project_spec.rb | 4 +-- spec/models/ci/runner_project_spec.rb | 2 +- spec/models/ci/runner_spec.rb | 2 +- spec/models/ci/service_spec.rb | 2 +- spec/models/ci/trigger_spec.rb | 12 +++++++ spec/models/ci/variable_spec.rb | 2 +- spec/models/ci/web_hook_spec.rb | 2 +- spec/models/commit_status_spec.rb | 33 +++++++++++++++++++ spec/models/generic_commit_status_spec.rb | 33 +++++++++++++++++++ spec/models/group_spec.rb | 1 + spec/models/label_spec.rb | 1 + spec/models/merge_request_spec.rb | 1 + spec/models/namespace_spec.rb | 1 + spec/models/release_spec.rb | 12 +++++++ spec/models/user_spec.rb | 2 ++ 48 files changed, 311 insertions(+), 96 deletions(-) diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 6d2711268e..9e70247ef5 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -23,6 +23,10 @@ # after_sign_out_path :string(255) # session_expire_delay :integer default(10080), not null # import_sources :text +# help_page_text :text +# admin_notification_email :string(255) +# shared_runners_enabled :boolean default(TRUE), not null +# max_artifacts_size :integer default(100), not null # class ApplicationSetting < ActiveRecord::Base diff --git a/app/models/ci/application_setting.rb b/app/models/ci/application_setting.rb index 4ab3e2dcbb..1307fa0b47 100644 --- a/app/models/ci/application_setting.rb +++ b/app/models/ci/application_setting.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: application_settings +# Table name: ci_application_settings # # id :integer not null, primary key # all_broken_builds :boolean diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 0ec7e21032..e78b154084 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: builds +# Table name: ci_builds # # id :integer not null, primary key # project_id :integer @@ -11,16 +11,24 @@ # updated_at :datetime # started_at :datetime # runner_id :integer -# commit_id :integer # coverage :float +# commit_id :integer # commands :text # job_id :integer # name :string(255) +# deploy :boolean default(FALSE) # options :text # allow_failure :boolean default(FALSE), not null # stage :string(255) -# deploy :boolean default(FALSE) # trigger_request_id :integer +# stage_idx :integer +# tag :boolean +# ref :string(255) +# user_id :integer +# type :string(255) +# target_url :string(255) +# description :string(255) +# artifacts_file :text # module Ci diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index e58420d82d..33b5717392 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -1,18 +1,19 @@ # == Schema Information # -# Table name: commits +# Table name: ci_commits # -# id :integer not null, primary key -# project_id :integer -# ref :string(255) -# sha :string(255) -# before_sha :string(255) -# push_data :text -# created_at :datetime -# updated_at :datetime -# tag :boolean default(FALSE) -# yaml_errors :text -# committed_at :datetime +# id :integer not null, primary key +# project_id :integer +# ref :string(255) +# sha :string(255) +# before_sha :string(255) +# push_data :text +# created_at :datetime +# updated_at :datetime +# tag :boolean default(FALSE) +# yaml_errors :text +# committed_at :datetime +# gl_project_id :integer # module Ci diff --git a/app/models/ci/event.rb b/app/models/ci/event.rb index cac3a7a49c..8c39be4267 100644 --- a/app/models/ci/event.rb +++ b/app/models/ci/event.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: events +# Table name: ci_events # # id :integer not null, primary key # project_id :integer diff --git a/app/models/ci/project.rb b/app/models/ci/project.rb index 4e806ca1a6..b3e05e42e9 100644 --- a/app/models/ci/project.rb +++ b/app/models/ci/project.rb @@ -1,9 +1,9 @@ # == Schema Information # -# Table name: projects +# Table name: ci_projects # # id :integer not null, primary key -# name :string(255) not null +# name :string(255) # timeout :integer default(3600), not null # created_at :datetime # updated_at :datetime diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index b719ad3c87..8971048581 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: runners +# Table name: ci_runners # # id :integer not null, primary key # token :string(255) diff --git a/app/models/ci/runner_project.rb b/app/models/ci/runner_project.rb index 44453ee4b4..3f4fc43873 100644 --- a/app/models/ci/runner_project.rb +++ b/app/models/ci/runner_project.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: runner_projects +# Table name: ci_runner_projects # # id :integer not null, primary key # runner_id :integer not null diff --git a/app/models/ci/service.rb b/app/models/ci/service.rb index ed5e3f940b..8063c51e82 100644 --- a/app/models/ci/service.rb +++ b/app/models/ci/service.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: services +# Table name: ci_services # # id :integer not null, primary key # type :string(255) diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb index fe224b7dc7..b73c35d5ae 100644 --- a/app/models/ci/trigger.rb +++ b/app/models/ci/trigger.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: triggers +# Table name: ci_triggers # # id :integer not null, primary key # token :string(255) diff --git a/app/models/ci/trigger_request.rb b/app/models/ci/trigger_request.rb index 29cd955339..9973d2e5ad 100644 --- a/app/models/ci/trigger_request.rb +++ b/app/models/ci/trigger_request.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: trigger_requests +# Table name: ci_trigger_requests # # id :integer not null, primary key # trigger_id :integer not null diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb index 7a542802fa..b3d2b809e0 100644 --- a/app/models/ci/variable.rb +++ b/app/models/ci/variable.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: variables +# Table name: ci_variables # # id :integer not null, primary key # project_id :integer not null diff --git a/app/models/ci/web_hook.rb b/app/models/ci/web_hook.rb index 8f03b0625d..7ca16a1bde 100644 --- a/app/models/ci/web_hook.rb +++ b/app/models/ci/web_hook.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: web_hooks +# Table name: ci_web_hooks # # id :integer not null, primary key # url :string(255) not null diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index d346c5d35d..e70f4d3718 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -1,3 +1,36 @@ +# == Schema Information +# +# Table name: ci_builds +# +# id :integer not null, primary key +# project_id :integer +# status :string(255) +# finished_at :datetime +# trace :text +# created_at :datetime +# updated_at :datetime +# started_at :datetime +# runner_id :integer +# coverage :float +# commit_id :integer +# commands :text +# job_id :integer +# name :string(255) +# deploy :boolean default(FALSE) +# options :text +# allow_failure :boolean default(FALSE), not null +# stage :string(255) +# trigger_request_id :integer +# stage_idx :integer +# tag :boolean +# ref :string(255) +# user_id :integer +# type :string(255) +# target_url :string(255) +# description :string(255) +# artifacts_file :text +# + class CommitStatus < ActiveRecord::Base self.table_name = 'ci_builds' diff --git a/app/models/generic_commit_status.rb b/app/models/generic_commit_status.rb index fa54e3540d..12c934e249 100644 --- a/app/models/generic_commit_status.rb +++ b/app/models/generic_commit_status.rb @@ -1,3 +1,36 @@ +# == Schema Information +# +# Table name: ci_builds +# +# id :integer not null, primary key +# project_id :integer +# status :string(255) +# finished_at :datetime +# trace :text +# created_at :datetime +# updated_at :datetime +# started_at :datetime +# runner_id :integer +# coverage :float +# commit_id :integer +# commands :text +# job_id :integer +# name :string(255) +# deploy :boolean default(FALSE) +# options :text +# allow_failure :boolean default(FALSE), not null +# stage :string(255) +# trigger_request_id :integer +# stage_idx :integer +# tag :boolean +# ref :string(255) +# user_id :integer +# type :string(255) +# target_url :string(255) +# description :string(255) +# artifacts_file :text +# + class GenericCommitStatus < CommitStatus before_validation :set_default_values diff --git a/app/models/group.rb b/app/models/group.rb index 34904af3b5..793a3b5ef2 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -11,6 +11,7 @@ # type :string(255) # description :string(255) default(""), not null # avatar :string(255) +# public :boolean default(FALSE) # require 'carrierwave/orm/activerecord' diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb index ca7066b959..337b309712 100644 --- a/app/models/hooks/project_hook.rb +++ b/app/models/hooks/project_hook.rb @@ -2,18 +2,19 @@ # # Table name: web_hooks # -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# type :string(255) default("ProjectHook") -# service_id :integer -# push_events :boolean default(TRUE), not null -# issues_events :boolean default(FALSE), not null -# merge_requests_events :boolean default(FALSE), not null -# tag_push_events :boolean default(FALSE) -# note_events :boolean default(FALSE), not null +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null +# tag_push_events :boolean default(FALSE) +# note_events :boolean default(FALSE), not null +# enable_ssl_verification :boolean default(TRUE) # class ProjectHook < WebHook diff --git a/app/models/hooks/service_hook.rb b/app/models/hooks/service_hook.rb index b55e217975..09bb3ee52a 100644 --- a/app/models/hooks/service_hook.rb +++ b/app/models/hooks/service_hook.rb @@ -2,18 +2,19 @@ # # Table name: web_hooks # -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# type :string(255) default("ProjectHook") -# service_id :integer -# push_events :boolean default(TRUE), not null -# issues_events :boolean default(FALSE), not null -# merge_requests_events :boolean default(FALSE), not null -# tag_push_events :boolean default(FALSE) -# note_events :boolean default(FALSE), not null +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null +# tag_push_events :boolean default(FALSE) +# note_events :boolean default(FALSE), not null +# enable_ssl_verification :boolean default(TRUE) # class ServiceHook < WebHook diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb index 6fb2d42102..2f63c59b07 100644 --- a/app/models/hooks/system_hook.rb +++ b/app/models/hooks/system_hook.rb @@ -2,18 +2,19 @@ # # Table name: web_hooks # -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# type :string(255) default("ProjectHook") -# service_id :integer -# push_events :boolean default(TRUE), not null -# issues_events :boolean default(FALSE), not null -# merge_requests_events :boolean default(FALSE), not null -# tag_push_events :boolean default(FALSE) -# note_events :boolean default(FALSE), not null +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null +# tag_push_events :boolean default(FALSE) +# note_events :boolean default(FALSE), not null +# enable_ssl_verification :boolean default(TRUE) # class SystemHook < WebHook diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index a078accbdb..d6c6f415c4 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -2,18 +2,19 @@ # # Table name: web_hooks # -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# type :string(255) default("ProjectHook") -# service_id :integer -# push_events :boolean default(TRUE), not null -# issues_events :boolean default(FALSE), not null -# merge_requests_events :boolean default(FALSE), not null -# tag_push_events :boolean default(FALSE) -# note_events :boolean default(FALSE), not null +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null +# tag_push_events :boolean default(FALSE) +# note_events :boolean default(FALSE), not null +# enable_ssl_verification :boolean default(TRUE) # class WebHook < ActiveRecord::Base diff --git a/app/models/label.rb b/app/models/label.rb index 1bb4b5f55c..b306aecbac 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -8,6 +8,7 @@ # project_id :integer # created_at :datetime # updated_at :datetime +# template :boolean default(FALSE) # class Label < ActiveRecord::Base diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index e81d65c233..2eb03b8ba5 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -20,6 +20,7 @@ # position :integer default(0) # locked_at :datetime # updated_by_id :integer +# merge_error :string(255) # require Rails.root.join("app/models/commit") diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 5782e649f8..20b92e68d6 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -11,6 +11,7 @@ # type :string(255) # description :string(255) default(""), not null # avatar :string(255) +# public :boolean default(FALSE) # class Namespace < ActiveRecord::Base diff --git a/app/models/project_services/ci/hip_chat_service.rb b/app/models/project_services/ci/hip_chat_service.rb index f17993d9f3..0df03890ef 100644 --- a/app/models/project_services/ci/hip_chat_service.rb +++ b/app/models/project_services/ci/hip_chat_service.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: services +# Table name: ci_services # # id :integer not null, primary key # type :string(255) diff --git a/app/models/project_services/ci/mail_service.rb b/app/models/project_services/ci/mail_service.rb index fd19330100..d31dd6899c 100644 --- a/app/models/project_services/ci/mail_service.rb +++ b/app/models/project_services/ci/mail_service.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: services +# Table name: ci_services # # id :integer not null, primary key # type :string(255) diff --git a/app/models/project_services/ci/slack_service.rb b/app/models/project_services/ci/slack_service.rb index ee8e498882..7064bfe78d 100644 --- a/app/models/project_services/ci/slack_service.rb +++ b/app/models/project_services/ci/slack_service.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: services +# Table name: ci_services # # id :integer not null, primary key # type :string(255) diff --git a/app/models/release.rb b/app/models/release.rb index e196b84eb1..89f70278af 100644 --- a/app/models/release.rb +++ b/app/models/release.rb @@ -1,3 +1,15 @@ +# == Schema Information +# +# Table name: releases +# +# id :integer not null, primary key +# tag :string(255) +# description :text +# project_id :integer +# created_at :datetime +# updated_at :datetime +# + class Release < ActiveRecord::Base belongs_to :project diff --git a/app/models/user.rb b/app/models/user.rb index 67fef1c1e6..9ffadcf446 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -54,6 +54,7 @@ # public_email :string(255) default(""), not null # dashboard :integer default(0) # project_view :integer default(0) +# consumed_timestep :integer # layout :integer default(0) # diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb index 6829387c66..8b12ee11af 100644 --- a/spec/factories/labels.rb +++ b/spec/factories/labels.rb @@ -8,6 +8,7 @@ # project_id :integer # created_at :datetime # updated_at :datetime +# template :boolean default(FALSE) # # Read about factories at https://github.com/thoughtbot/factory_girl diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index 6080d0ccde..729a49c9f7 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -20,6 +20,7 @@ # position :integer default(0) # locked_at :datetime # updated_by_id :integer +# merge_error :string(255) # FactoryGirl.define do diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb index 80d6bbee6c..43d09b1753 100644 --- a/spec/factories/releases.rb +++ b/spec/factories/releases.rb @@ -1,3 +1,15 @@ +# == Schema Information +# +# Table name: releases +# +# id :integer not null, primary key +# tag :string(255) +# description :text +# project_id :integer +# created_at :datetime +# updated_at :datetime +# + # Read about factories at https://github.com/thoughtbot/factory_girl FactoryGirl.define do diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index f01fe8bd39..dfbac7b400 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -23,6 +23,10 @@ # after_sign_out_path :string(255) # session_expire_delay :integer default(10080), not null # import_sources :text +# help_page_text :text +# admin_notification_email :string(255) +# shared_runners_enabled :boolean default(TRUE), not null +# max_artifacts_size :integer default(100), not null # require 'spec_helper' diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb index 44dbd083f0..a13f6458ca 100644 --- a/spec/models/ci/commit_spec.rb +++ b/spec/models/ci/commit_spec.rb @@ -1,18 +1,19 @@ # == Schema Information # -# Table name: commits +# Table name: ci_commits # -# id :integer not null, primary key -# project_id :integer -# ref :string(255) -# sha :string(255) -# before_sha :string(255) -# push_data :text -# created_at :datetime -# updated_at :datetime -# tag :boolean default(FALSE) -# yaml_errors :text -# committed_at :datetime +# id :integer not null, primary key +# project_id :integer +# ref :string(255) +# sha :string(255) +# before_sha :string(255) +# push_data :text +# created_at :datetime +# updated_at :datetime +# tag :boolean default(FALSE) +# yaml_errors :text +# committed_at :datetime +# gl_project_id :integer # require 'spec_helper' diff --git a/spec/models/ci/project_spec.rb b/spec/models/ci/project_spec.rb index 490c6a6798..a0864442d9 100644 --- a/spec/models/ci/project_spec.rb +++ b/spec/models/ci/project_spec.rb @@ -1,9 +1,9 @@ # == Schema Information # -# Table name: projects +# Table name: ci_projects # # id :integer not null, primary key -# name :string(255) not null +# name :string(255) # timeout :integer default(3600), not null # created_at :datetime # updated_at :datetime diff --git a/spec/models/ci/runner_project_spec.rb b/spec/models/ci/runner_project_spec.rb index 0218d48413..37682c6ea0 100644 --- a/spec/models/ci/runner_project_spec.rb +++ b/spec/models/ci/runner_project_spec.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: runner_projects +# Table name: ci_runner_projects # # id :integer not null, primary key # runner_id :integer not null diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index f8a51c29dc..9a1233b909 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: runners +# Table name: ci_runners # # id :integer not null, primary key # token :string(255) diff --git a/spec/models/ci/service_spec.rb b/spec/models/ci/service_spec.rb index 2df70e8888..36cda988eb 100644 --- a/spec/models/ci/service_spec.rb +++ b/spec/models/ci/service_spec.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: services +# Table name: ci_services # # id :integer not null, primary key # type :string(255) diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb index 19c14ef2da..b8aa3c1e77 100644 --- a/spec/models/ci/trigger_spec.rb +++ b/spec/models/ci/trigger_spec.rb @@ -1,3 +1,15 @@ +# == Schema Information +# +# Table name: ci_triggers +# +# id :integer not null, primary key +# token :string(255) +# project_id :integer not null +# deleted_at :datetime +# created_at :datetime +# updated_at :datetime +# + require 'spec_helper' describe Ci::Trigger do diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb index d034a6c7b9..a515f5881f 100644 --- a/spec/models/ci/variable_spec.rb +++ b/spec/models/ci/variable_spec.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: variables +# Table name: ci_variables # # id :integer not null, primary key # project_id :integer not null diff --git a/spec/models/ci/web_hook_spec.rb b/spec/models/ci/web_hook_spec.rb index bf9481ab81..2865482a21 100644 --- a/spec/models/ci/web_hook_spec.rb +++ b/spec/models/ci/web_hook_spec.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: web_hooks +# Table name: ci_web_hooks # # id :integer not null, primary key # url :string(255) not null diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index c96a606fda..dca0715eed 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -1,3 +1,36 @@ +# == Schema Information +# +# Table name: ci_builds +# +# id :integer not null, primary key +# project_id :integer +# status :string(255) +# finished_at :datetime +# trace :text +# created_at :datetime +# updated_at :datetime +# started_at :datetime +# runner_id :integer +# coverage :float +# commit_id :integer +# commands :text +# job_id :integer +# name :string(255) +# deploy :boolean default(FALSE) +# options :text +# allow_failure :boolean default(FALSE), not null +# stage :string(255) +# trigger_request_id :integer +# stage_idx :integer +# tag :boolean +# ref :string(255) +# user_id :integer +# type :string(255) +# target_url :string(255) +# description :string(255) +# artifacts_file :text +# + require 'spec_helper' describe CommitStatus do diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb index f442fa5fbe..c86314c454 100644 --- a/spec/models/generic_commit_status_spec.rb +++ b/spec/models/generic_commit_status_spec.rb @@ -1,3 +1,36 @@ +# == Schema Information +# +# Table name: ci_builds +# +# id :integer not null, primary key +# project_id :integer +# status :string(255) +# finished_at :datetime +# trace :text +# created_at :datetime +# updated_at :datetime +# started_at :datetime +# runner_id :integer +# coverage :float +# commit_id :integer +# commands :text +# job_id :integer +# name :string(255) +# deploy :boolean default(FALSE) +# options :text +# allow_failure :boolean default(FALSE), not null +# stage :string(255) +# trigger_request_id :integer +# stage_idx :integer +# tag :boolean +# ref :string(255) +# user_id :integer +# type :string(255) +# target_url :string(255) +# description :string(255) +# artifacts_file :text +# + require 'spec_helper' describe GenericCommitStatus do diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 0f23e81ace..bbfc5535ee 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -11,6 +11,7 @@ # type :string(255) # description :string(255) default(""), not null # avatar :string(255) +# public :boolean default(FALSE) # require 'spec_helper' diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index 6518213d71..511ee8cbd9 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -8,6 +8,7 @@ # project_id :integer # created_at :datetime # updated_at :datetime +# template :boolean default(FALSE) # require 'spec_helper' diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index eed2cbc541..90af75ff0e 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -20,6 +20,7 @@ # position :integer default(0) # locked_at :datetime # updated_by_id :integer +# merge_error :string(255) # require 'spec_helper' diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 1d72a9503a..a98b9cb732 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -11,6 +11,7 @@ # type :string(255) # description :string(255) default(""), not null # avatar :string(255) +# public :boolean default(FALSE) # require 'spec_helper' diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb index 527005b2b6..72ecb442a3 100644 --- a/spec/models/release_spec.rb +++ b/spec/models/release_spec.rb @@ -1,3 +1,15 @@ +# == Schema Information +# +# Table name: releases +# +# id :integer not null, primary key +# tag :string(255) +# description :text +# project_id :integer +# created_at :datetime +# updated_at :datetime +# + require 'rails_helper' RSpec.describe Release, type: :model do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 49e0bfdd2e..7d716c2312 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -54,6 +54,8 @@ # public_email :string(255) default(""), not null # dashboard :integer default(0) # project_view :integer default(0) +# consumed_timestep :integer +# layout :integer default(0) # require 'spec_helper' From 4bb99677b1ddee84ab0433efe081de8025e5aa53 Mon Sep 17 00:00:00 2001 From: Alec Cooper Date: Thu, 12 Nov 2015 21:38:26 -0500 Subject: [PATCH 129/140] Relative links in the README file shown on the repository homepage should point to the default branch, not to master --- CHANGELOG | 1 + lib/gitlab/markdown/relative_link_filter.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 1bd60728e6..5d22e95091 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -39,6 +39,7 @@ v 8.2.0 (unreleased) - Improve Continuous Integration graphs page - Make color of "Accept Merge Request" button consistent with current build status - Add ignore white space option in merge request diff and commit and compare view + - Relative links from a repositories README.md now link to the default branch v 8.1.4 - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) diff --git a/lib/gitlab/markdown/relative_link_filter.rb b/lib/gitlab/markdown/relative_link_filter.rb index 6ee3d1ce03..632be4d754 100644 --- a/lib/gitlab/markdown/relative_link_filter.rb +++ b/lib/gitlab/markdown/relative_link_filter.rb @@ -51,7 +51,7 @@ module Gitlab relative_url_root, context[:project].path_with_namespace, path_type(file_path), - ref || 'master', # assume that if no ref exists we can point to master + ref || context[:project].default_branch, # if no ref exists, point to the default branch file_path ].compact.join('/').squeeze('/').chomp('/') From 9337da7864756116f3865e14d46227ffe857d0e5 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 15 Nov 2015 21:37:13 -0500 Subject: [PATCH 130/140] Revert "Merge pull request #9812 from chrspeich/hide-tabs-lone-auth-provider" This reverts commit 84999611d8f7894219eb4ebc76555c79b1f14794, reversing changes made to 0d9fb211f3f842d10e1c57dcb9d3d42a9c11cd0b. --- app/views/devise/shared/_signin_box.html.haml | 46 ++++++++----------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml index 9a1331b254..41ad2c231d 100644 --- a/app/views/devise/shared/_signin_box.html.haml +++ b/app/views/devise/shared/_signin_box.html.haml @@ -7,34 +7,26 @@ %h3 Sign in .login-body - if form_based_providers.any? - - if form_based_providers.count >= 2 || signin_enabled? - %ul.nav.nav-tabs - - if crowd_enabled? - %li.active - = link_to "Crowd", "#tab-crowd", 'data-toggle' => 'tab' - - @ldap_servers.each_with_index do |server, i| - %li{class: (:active if i.zero? && !crowd_enabled?)} - = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' - - if signin_enabled? - %li - = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' - .tab-content - - if crowd_enabled? - %div.tab-pane.active{id: "tab-crowd"} - = render 'devise/sessions/new_crowd' - - @ldap_servers.each_with_index do |server, i| - %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero? && !crowd_enabled?)} - = render 'devise/sessions/new_ldap', server: server - - if signin_enabled? - %div#tab-signin.tab-pane - = render 'devise/sessions/new_base' - - else + %ul.nav.nav-tabs - if crowd_enabled? - = render 'devise/sessions/new_crowd' - - elsif @ldap_servers.any? - = render 'devise/sessions/new_ldap', server: @ldap_servers.first - - elsif signin_enabled? - = render 'devise/sessions/new_base' + %li.active + = link_to "Crowd", "#tab-crowd", 'data-toggle' => 'tab' + - @ldap_servers.each_with_index do |server, i| + %li{class: (:active if i.zero? && !crowd_enabled?)} + = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' + - if signin_enabled? + %li + = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' + .tab-content + - if crowd_enabled? + %div.tab-pane.active{id: "tab-crowd"} + = render 'devise/sessions/new_crowd' + - @ldap_servers.each_with_index do |server, i| + %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero? && !crowd_enabled?)} + = render 'devise/sessions/new_ldap', server: server + - if signin_enabled? + %div#tab-signin.tab-pane + = render 'devise/sessions/new_base' - elsif signin_enabled? = render 'devise/sessions/new_base' From 743d66e4da5c11f638d12a7f7ec6354631ee2b90 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 10:11:16 +0100 Subject: [PATCH 131/140] Improve english text Signed-off-by: Dmitriy Zaporozhets --- doc/api/tags.md | 2 +- doc/workflow/releases.md | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/api/tags.md b/doc/api/tags.md index 0113d4ba04..b5b90cf6b8 100644 --- a/doc/api/tags.md +++ b/doc/api/tags.md @@ -40,7 +40,7 @@ Parameters: ## Create a new tag -Creates new tag in the repository that points to the supplied ref. +Creates a new tag in the repository that points to the supplied ref. ``` POST /projects/:id/repository/tags diff --git a/doc/workflow/releases.md b/doc/workflow/releases.md index 0eca220e2c..6176784fc5 100644 --- a/doc/workflow/releases.md +++ b/doc/workflow/releases.md @@ -1,14 +1,14 @@ # Releases -You can turn any git tag into release by just adding a release notes to it. -Release notes behaves like any other markdown form in GitLab so you can write text and drag-n-drop files to it. -Release notes are stored in GitLab database. +You can turn any git tag into a release, by adding a note to it. +Release notes behave like any other markdown form in GitLab so you can write text and drag-n-drop files to it. +Release notes are stored in the database of GitLab. There are several ways to add release notes: -* In UI when you create new git tag with GitLab -* In UI when you add release notes to existing git tag -* with GitLab API +* In the interface, when you create a new git tag with GitLab +* In the interface, by adding a note to an existing git tag +* with the GitLab API ## New tag page with release notes text area From 9e0d443f9e420a347cb88508c9afebf17454fac1 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 16 Nov 2015 12:18:31 +0200 Subject: [PATCH 132/140] Disabling cache for test environment --- config/environments/test.rb | 2 ++ config/initializers/session_store.rb | 20 +++++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/config/environments/test.rb b/config/environments/test.rb index 2d5e7addcd..955540837d 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -30,4 +30,6 @@ Gitlab::Application.configure do config.active_support.deprecation = :stderr config.eager_load = false + + config.cache_store = :null_store end diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index 04ed9e90df..d7c5432da7 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -9,12 +9,14 @@ begin rescue end -Gitlab::Application.config.session_store( - :redis_store, # Using the cookie_store would enable session replay attacks. - servers: Gitlab::Application.config.cache_store[1].merge(namespace: 'session:gitlab'), # re-use the Redis config from the Rails cache store - key: '_gitlab_session', - secure: Gitlab.config.gitlab.https, - httponly: true, - expire_after: Settings.gitlab['session_expire_delay'] * 60, - path: (Gitlab::Application.config.relative_url_root.nil?) ? '/' : Gitlab::Application.config.relative_url_root -) +unless Rails.env.test? + Gitlab::Application.config.session_store( + :redis_store, # Using the cookie_store would enable session replay attacks. + servers: Gitlab::Application.config.cache_store[1].merge(namespace: 'session:gitlab'), # re-use the Redis config from the Rails cache store + key: '_gitlab_session', + secure: Gitlab.config.gitlab.https, + httponly: true, + expire_after: Settings.gitlab['session_expire_delay'] * 60, + path: (Gitlab::Application.config.relative_url_root.nil?) ? '/' : Gitlab::Application.config.relative_url_root + ) +end From 14032d8eb1a60ae5920286249c1044be2fa27278 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Mon, 12 Oct 2015 16:42:14 +0200 Subject: [PATCH 133/140] Add support for git lfs. --- .gitignore | 1 + app/controllers/projects_controller.rb | 5 +- app/models/lfs_object.rb | 8 + app/models/lfs_objects_project.rb | 8 + app/models/project.rb | 12 + app/uploaders/lfs_object_uploader.rb | 29 + config/gitlab.yml.example | 10 +- config/initializers/1_settings.rb | 7 + config/routes.rb | 2 +- .../20151103134857_create_lfs_objects.rb | 10 + ...51103134958_create_lfs_objects_projects.rb | 12 + .../20151104105513_add_file_to_lfs_objects.rb | 5 + ...14113410_add_index_for_lfs_oid_and_size.rb | 6 + db/schema.rb | 22 +- lib/gitlab/backend/grack_auth.rb | 5 +- lib/gitlab/git_access.rb | 6 +- lib/gitlab/lfs/response.rb | 308 +++++++++ lib/gitlab/lfs/router.rb | 95 +++ lib/support/nginx/gitlab | 9 +- lib/support/nginx/gitlab-ssl | 9 +- spec/factories/lfs_objects.rb | 12 + spec/factories/lfs_objects_projects.rb | 8 + spec/lib/gitlab/lfs/lfs_router_spec.rb | 650 ++++++++++++++++++ 23 files changed, 1226 insertions(+), 13 deletions(-) create mode 100644 app/models/lfs_object.rb create mode 100644 app/models/lfs_objects_project.rb create mode 100644 app/uploaders/lfs_object_uploader.rb create mode 100644 db/migrate/20151103134857_create_lfs_objects.rb create mode 100644 db/migrate/20151103134958_create_lfs_objects_projects.rb create mode 100644 db/migrate/20151104105513_add_file_to_lfs_objects.rb create mode 100644 db/migrate/20151114113410_add_index_for_lfs_oid_and_size.rb create mode 100644 lib/gitlab/lfs/response.rb create mode 100644 lib/gitlab/lfs/router.rb create mode 100644 spec/factories/lfs_objects.rb create mode 100644 spec/factories/lfs_objects_projects.rb create mode 100644 spec/lib/gitlab/lfs/lfs_router_spec.rb diff --git a/.gitignore b/.gitignore index 39ff95c50e..f5b6427ca0 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,4 @@ rails_best_practices_output.html tmp/ vendor/bundle/* builds/* +shared/* diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 30b166334a..23453195e8 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -72,8 +72,7 @@ class ProjectsController < ApplicationController def remove_fork return access_denied! unless can?(current_user, :remove_fork_project, @project) - if @project.forked? - @project.forked_project_link.destroy + if @project.unlink_fork flash[:notice] = 'The fork relationship has been removed.' end end @@ -243,7 +242,7 @@ class ProjectsController < ApplicationController project.repository_exists? && !project.empty_repo? end - # Override get_id from ExtractsPath, which returns the branch and file path + # Override get_id from ExtractsPath, which returns the branch and file path # for the blob/tree, which in this case is just the root of the default branch. def get_id project.repository.root_ref diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb new file mode 100644 index 0000000000..3c1426f59d --- /dev/null +++ b/app/models/lfs_object.rb @@ -0,0 +1,8 @@ +class LfsObject < ActiveRecord::Base + has_many :lfs_objects_projects, dependent: :destroy + has_many :projects, through: :lfs_objects_projects + + validates :oid, presence: true, uniqueness: true + + mount_uploader :file, LfsObjectUploader +end diff --git a/app/models/lfs_objects_project.rb b/app/models/lfs_objects_project.rb new file mode 100644 index 0000000000..0fd5f089db --- /dev/null +++ b/app/models/lfs_objects_project.rb @@ -0,0 +1,8 @@ +class LfsObjectsProject < ActiveRecord::Base + belongs_to :project + belongs_to :lfs_object + + validates :lfs_object_id, presence: true + validates :lfs_object_id, uniqueness: { scope: [:project_id], message: "already exists in project" } + validates :project_id, presence: true +end diff --git a/app/models/project.rb b/app/models/project.rb index 3e72a9a46a..9ea0d15497 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -124,6 +124,8 @@ class Project < ActiveRecord::Base has_many :ci_commits, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id has_many :ci_builds, through: :ci_commits, source: :builds, dependent: :destroy, class_name: 'Ci::Build' has_many :releases, dependent: :destroy + has_many :lfs_objects_projects, dependent: :destroy + has_many :lfs_objects, through: :lfs_objects_projects has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" has_one :gitlab_ci_project, dependent: :destroy, class_name: "Ci::Project", foreign_key: :gitlab_id @@ -798,4 +800,14 @@ class Project < ActiveRecord::Base def enable_ci self.builds_enabled = true end + + def unlink_fork + if forked? + forked_from_project.lfs_objects.find_each do |lfs_object| + lfs_object.projects << self + end + + forked_project_link.destroy + end + end end diff --git a/app/uploaders/lfs_object_uploader.rb b/app/uploaders/lfs_object_uploader.rb new file mode 100644 index 0000000000..28085b3108 --- /dev/null +++ b/app/uploaders/lfs_object_uploader.rb @@ -0,0 +1,29 @@ +# encoding: utf-8 + +class LfsObjectUploader < CarrierWave::Uploader::Base + storage :file + + def store_dir + "#{Gitlab.config.lfs.storage_path}/#{model.oid[0,2]}/#{model.oid[2,2]}" + end + + def cache_dir + "#{Gitlab.config.lfs.storage_path}/tmp/cache" + end + + def move_to_cache + true + end + + def move_to_store + true + end + + def exists? + file.try(:exists?) + end + + def filename + model.oid[4..-1] + end +end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 5bd98e3e42..8fdb2603ce 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -124,6 +124,12 @@ production: &base # The mailbox where incoming mail will end up. Usually "inbox". mailbox: "inbox" + ## Git LFS + lfs: + enabled: false + # The location where LFS objects are stored (default: shared/lfs-objects). + # storage_path: shared/lfs-objects + ## Gravatar ## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html gravatar: @@ -317,8 +323,6 @@ production: &base # path: /mnt/gitlab # Default: shared - - # # 4. Advanced settings # ========================== @@ -419,6 +423,8 @@ test: <<: *base gravatar: enabled: true + lfs: + enabled: false gitlab: host: localhost port: 80 diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 847d9f2189..6b7990c0ab 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -199,6 +199,13 @@ Settings.incoming_email['ssl'] = false if Settings.incoming_email['ssl']. Settings.incoming_email['start_tls'] = false if Settings.incoming_email['start_tls'].nil? Settings.incoming_email['mailbox'] = "inbox" if Settings.incoming_email['mailbox'].nil? +# +# Git LFS +# +Settings['lfs'] ||= Settingslogic.new({}) +Settings.lfs['enabled'] = false if Settings.lfs['enabled'].nil? +Settings.lfs['storage_path'] = File.expand_path(Settings.lfs['storage_path'] || File.join(Settings.shared['path'], "lfs-objects"), Rails.root) + # # Gravatar # diff --git a/config/routes.rb b/config/routes.rb index 095c562be8..bd85f4e3c6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -93,7 +93,7 @@ Gitlab::Application.routes.draw do end # Enable Grack support - mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post] + mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post, :put] # Help get 'help' => 'help#index' diff --git a/db/migrate/20151103134857_create_lfs_objects.rb b/db/migrate/20151103134857_create_lfs_objects.rb new file mode 100644 index 0000000000..2d04c170a8 --- /dev/null +++ b/db/migrate/20151103134857_create_lfs_objects.rb @@ -0,0 +1,10 @@ +class CreateLfsObjects < ActiveRecord::Migration + def change + create_table :lfs_objects do |t| + t.string :oid, null: false, unique: true + t.integer :size, null: false + + t.timestamps + end + end +end diff --git a/db/migrate/20151103134958_create_lfs_objects_projects.rb b/db/migrate/20151103134958_create_lfs_objects_projects.rb new file mode 100644 index 0000000000..f3f58b931e --- /dev/null +++ b/db/migrate/20151103134958_create_lfs_objects_projects.rb @@ -0,0 +1,12 @@ +class CreateLfsObjectsProjects < ActiveRecord::Migration + def change + create_table :lfs_objects_projects do |t| + t.integer :lfs_object_id, null: false + t.integer :project_id, null: false + + t.timestamps + end + + add_index :lfs_objects_projects, :project_id + end +end diff --git a/db/migrate/20151104105513_add_file_to_lfs_objects.rb b/db/migrate/20151104105513_add_file_to_lfs_objects.rb new file mode 100644 index 0000000000..7c57f3f0df --- /dev/null +++ b/db/migrate/20151104105513_add_file_to_lfs_objects.rb @@ -0,0 +1,5 @@ +class AddFileToLfsObjects < ActiveRecord::Migration + def change + add_column :lfs_objects, :file, :string + end +end diff --git a/db/migrate/20151114113410_add_index_for_lfs_oid_and_size.rb b/db/migrate/20151114113410_add_index_for_lfs_oid_and_size.rb new file mode 100644 index 0000000000..d10f1f6e60 --- /dev/null +++ b/db/migrate/20151114113410_add_index_for_lfs_oid_and_size.rb @@ -0,0 +1,6 @@ +class AddIndexForLfsOidAndSize < ActiveRecord::Migration + def change + add_index :lfs_objects, :oid + add_index :lfs_objects, [:oid, :size] + end +end diff --git a/db/schema.rb b/db/schema.rb index f631d73f33..a8e8dfe6bb 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20151109100728) do +ActiveRecord::Schema.define(version: 20151114113410) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -422,6 +422,26 @@ ActiveRecord::Schema.define(version: 20151109100728) do add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree + create_table "lfs_objects", force: true do |t| + t.string "oid", null: false, unique: true + t.integer "size", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.string "file" + end + + add_index "lfs_objects", ["oid", "size"], name: "index_lfs_objects_on_oid_and_size", using: :btree + add_index "lfs_objects", ["oid"], name: "index_lfs_objects_on_oid", using: :btree + + create_table "lfs_objects_projects", force: true do |t| + t.integer "lfs_object_id", null: false + t.integer "project_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "lfs_objects_projects", ["project_id"], name: "index_lfs_objects_projects_on_project_id", using: :btree + create_table "members", force: true do |t| t.integer "access_level", null: false t.integer "source_id", null: false diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index 440ef5a3cb..0d156047ff 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -33,6 +33,9 @@ module Grack auth! + lfs_response = Gitlab::Lfs::Router.new(project, @user, @request).try_call + return lfs_response unless lfs_response.nil? + if project && authorized_request? # Tell gitlab-workhorse the request is OK, and what the GL_ID is render_grack_auth_ok @@ -72,7 +75,7 @@ module Grack matched_login = /(?^[a-zA-Z]*-ci)-token$/.match(login) if project && matched_login.present? && git_cmd == 'git-upload-pack' - underscored_service = matched_login['s'].underscore + underscored_service = matched_login['s'].underscore if Service.available_services_names.include?(underscored_service) service_method = "#{underscored_service}_service" diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index c90184d31c..3ed1eec517 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -13,7 +13,7 @@ module Gitlab def user return @user if defined?(@user) - @user = + @user = case actor when User actor @@ -125,7 +125,7 @@ module Gitlab def change_access_check(change) oldrev, newrev, ref = change.split(' ') - action = + action = if project.protected_branch?(branch_name(ref)) protected_branch_action(oldrev, newrev, branch_name(ref)) elsif protected_tag?(tag_name(ref)) @@ -148,7 +148,7 @@ module Gitlab build_status_object(false, "You are not allowed to change existing tags on this project.") else # :push_code build_status_object(false, "You are not allowed to push code to this project.") - end + end return status end diff --git a/lib/gitlab/lfs/response.rb b/lib/gitlab/lfs/response.rb new file mode 100644 index 0000000000..4202c78646 --- /dev/null +++ b/lib/gitlab/lfs/response.rb @@ -0,0 +1,308 @@ +module Gitlab + module Lfs + class Response + + def initialize(project, user, request) + @origin_project = project + @project = storage_project(project) + @user = user + @env = request.env + @request = request + end + + # Return a response for a download request + # Can be a response to: + # Request from a user to get the file + # Request from gitlab-workhorse which file to serve to the user + def render_download_hypermedia_response(oid) + render_response_to_download do + if check_download_accept_header? + render_lfs_download_hypermedia(oid) + else + render_not_found + end + end + end + + def render_download_object_response(oid) + render_response_to_download do + if check_download_sendfile_header? && check_download_accept_header? + render_lfs_sendfile(oid) + else + render_not_found + end + end + end + + def render_lfs_api_auth + render_response_to_push do + request_body = JSON.parse(@request.body.read) + return render_not_found if request_body.empty? || request_body['objects'].empty? + + response = build_response(request_body['objects']) + [ + 200, + { + "Content-Type" => "application/json; charset=utf-8", + "Cache-Control" => "private", + }, + [JSON.dump(response)] + ] + end + end + + def render_storage_upload_authorize_response(oid, size) + render_response_to_push do + [ + 200, + { "Content-Type" => "application/json; charset=utf-8" }, + [JSON.dump({ + 'StoreLFSPath' => "#{Gitlab.config.lfs.storage_path}/tmp/upload", + 'LfsOid' => oid, + 'LfsSize' => size + })] + ] + end + end + + def render_storage_upload_store_response(oid, size, tmp_file_name) + render_response_to_push do + render_lfs_upload_ok(oid, size, tmp_file_name) + end + end + + private + + def render_not_enabled + [ + 501, + { + "Content-Type" => "application/vnd.git-lfs+json", + }, + [JSON.dump({ + 'message' => 'Git LFS is not enabled on this GitLab server, contact your admin.', + 'documentation_url' => "#{Gitlab.config.gitlab.url}/help", + })] + ] + end + + def render_unauthorized + [ + 401, + { + 'Content-Type' => 'text/plain' + }, + ['Unauthorized'] + ] + end + + def render_not_found + [ + 404, + { + "Content-Type" => "application/vnd.git-lfs+json" + }, + [JSON.dump({ + 'message' => 'Not found.', + 'documentation_url' => "#{Gitlab.config.gitlab.url}/help", + })] + ] + end + + def render_forbidden + [ + 403, + { + "Content-Type" => "application/vnd.git-lfs+json" + }, + [JSON.dump({ + 'message' => 'Access forbidden. Check your access level.', + 'documentation_url' => "#{Gitlab.config.gitlab.url}/help", + })] + ] + end + + def render_lfs_sendfile(oid) + return render_not_found unless oid.present? + + lfs_object = object_for_download(oid) + + if lfs_object && lfs_object.file.exists? + [ + 200, + { + # GitLab-workhorse will forward Content-Type header + "Content-Type" => "application/octet-stream", + "X-Sendfile" => lfs_object.file.path + }, + [] + ] + else + render_not_found + end + end + + def render_lfs_download_hypermedia(oid) + return render_not_found unless oid.present? + + lfs_object = object_for_download(oid) + if lfs_object + [ + 200, + { "Content-Type" => "application/vnd.git-lfs+json" }, + [JSON.dump(download_hypermedia(oid))] + ] + else + render_not_found + end + end + + def render_lfs_upload_ok(oid, size, tmp_file) + if store_file(oid, size, tmp_file) + [ + 200, + { + 'Content-Type' => 'text/plain', + 'Content-Length' => 0 + }, + [] + ] + else + [ + 422, + { 'Content-Type' => 'text/plain' }, + ["Unprocessable entity"] + ] + end + end + + def render_response_to_download + return render_not_enabled unless Gitlab.config.lfs.enabled + + unless @project.public? + return render_unauthorized unless @user + return render_forbidden unless user_can_fetch? + end + + yield + end + + def render_response_to_push + return render_not_enabled unless Gitlab.config.lfs.enabled + return render_unauthorized unless @user + return render_forbidden unless user_can_push? + + yield + end + + def check_download_sendfile_header? + @env['HTTP_X_SENDFILE_TYPE'].to_s == "X-Sendfile" + end + + def check_download_accept_header? + @env['HTTP_ACCEPT'].to_s == "application/vnd.git-lfs+json; charset=utf-8" + end + + def user_can_fetch? + # Check user access against the project they used to initiate the pull + @user.can?(:download_code, @origin_project) + end + + def user_can_push? + # Check user access against the project they used to initiate the push + @user.can?(:push_code, @origin_project) + end + + def storage_project(project) + if project.forked? + project.forked_from_project + else + project + end + end + + def store_file(oid, size, tmp_file) + tmp_file_path = File.join("#{Gitlab.config.lfs.storage_path}/tmp/upload", tmp_file) + + object = LfsObject.find_or_create_by(oid: oid, size: size) + if object.file.exists? + success = true + else + success = move_tmp_file_to_storage(object, tmp_file_path) + end + + if success + success = link_to_project(object) + end + + success + ensure + # Ensure that the tmp file is removed + FileUtils.rm_f(tmp_file_path) + end + + def object_for_download(oid) + @project.lfs_objects.find_by(oid: oid) + end + + def move_tmp_file_to_storage(object, path) + File.open(path) do |f| + object.file = f + end + + object.file.store! + object.save + end + + def link_to_project(object) + if object && !object.projects.exists?(@project) + object.projects << @project + object.save + end + end + + def select_existing_objects(objects) + objects_oids = objects.map { |o| o['oid'] } + @project.lfs_objects.where(oid: objects_oids).pluck(:oid).to_set + end + + def build_response(objects) + selected_objects = select_existing_objects(objects) + + upload_hypermedia(objects, selected_objects) + end + + def download_hypermedia(oid) + { + '_links' => { + 'download' => + { + 'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{oid}", + 'header' => { + 'Accept' => "application/vnd.git-lfs+json; charset=utf-8", + 'Authorization' => @env['HTTP_AUTHORIZATION'] + }.compact + } + } + } + end + + def upload_hypermedia(all_objects, existing_objects) + all_objects.each do |object| + object['_links'] = hypermedia_links(object) unless existing_objects.include?(object['oid']) + end + + { 'objects' => all_objects } + end + + def hypermedia_links(object) + { + "upload" => { + 'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{object['oid']}/#{object['size']}", + 'header' => { 'Authorization' => @env['HTTP_AUTHORIZATION'] } + }.compact + } + end + end + end +end diff --git a/lib/gitlab/lfs/router.rb b/lib/gitlab/lfs/router.rb new file mode 100644 index 0000000000..4809e83498 --- /dev/null +++ b/lib/gitlab/lfs/router.rb @@ -0,0 +1,95 @@ +module Gitlab + module Lfs + class Router + def initialize(project, user, request) + @project = project + @user = user + @env = request.env + @request = request + end + + def try_call + return unless @request && @request.path.present? + + case @request.request_method + when 'GET' + get_response + when 'POST' + post_response + when 'PUT' + put_response + else + nil + end + end + + private + + def get_response + path_match = @request.path.match(/\/(info\/lfs|gitlab-lfs)\/objects\/([0-9a-f]{64})$/) + return nil unless path_match + + oid = path_match[2] + return nil unless oid + + case path_match[1] + when "info/lfs" + lfs.render_download_hypermedia_response(oid) + when "gitlab-lfs" + lfs.render_download_object_response(oid) + else + nil + end + end + + def post_response + post_path = @request.path.match(/\/info\/lfs\/objects(\/batch)?$/) + return nil unless post_path + + # Check for Batch API + if post_path[0].ends_with?("/info/lfs/objects/batch") + lfs.render_lfs_api_auth + else + nil + end + end + + def put_response + object_match = @request.path.match(/\/gitlab-lfs\/objects\/([0-9a-f]{64})\/([0-9]+)(|\/authorize){1}$/) + return nil if object_match.nil? + + oid = object_match[1] + size = object_match[2].try(:to_i) + return nil if oid.nil? || size.nil? + + # GitLab-workhorse requests + # 1. Try to authorize the request + # 2. send a request with a header containing the name of the temporary file + if object_match[3] && object_match[3] == '/authorize' + lfs.render_storage_upload_authorize_response(oid, size) + else + tmp_file_name = sanitize_tmp_filename(@request.env['HTTP_X_GITLAB_LFS_TMP']) + return nil unless tmp_file_name + + lfs.render_storage_upload_store_response(oid, size, tmp_file_name) + end + end + + def lfs + return unless @project + + Gitlab::Lfs::Response.new(@project, @user, @request) + end + + def sanitize_tmp_filename(name) + if name.present? + name.gsub!(/^.*(\\|\/)/, '') + name = name.match(/[0-9a-f]{73}/) + name[0] if name + else + nil + end + end + end + end +end diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index 0a7a411807..93f2ad07ae 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -44,7 +44,7 @@ upstream gitlab-workhorse { ## Normal HTTP host server { - ## Either remove "default_server" from the listen line below, + ## Either remove "default_server" from the listen line below, ## or delete the /etc/nginx/sites-enabled/default file. This will cause gitlab ## to be served if you visit any address that your server responds to, eg. ## the ip address of the server (http://x.x.x.x/)n 0.0.0.0:80 default_server; @@ -113,6 +113,13 @@ server { proxy_pass http://gitlab; } + location ~ ^/[\w\.-]+/[\w\.-]+/gitlab-lfs/objects { + client_max_body_size 0; + # 'Error' 418 is a hack to re-use the @gitlab-workhorse block + error_page 418 = @gitlab-workhorse; + return 418; + } + location ~ ^/[\w\.-]+/[\w\.-]+/(info/refs|git-upload-pack|git-receive-pack)$ { # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index b463d5b6aa..90749947fa 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -48,7 +48,7 @@ upstream gitlab-workhorse { ## Redirects all HTTP traffic to the HTTPS host server { - ## Either remove "default_server" from the listen line below, + ## Either remove "default_server" from the listen line below, ## or delete the /etc/nginx/sites-enabled/default file. This will cause gitlab ## to be served if you visit any address that your server responds to, eg. ## the ip address of the server (http://x.x.x.x/) @@ -160,6 +160,13 @@ server { proxy_pass http://gitlab; } + location ~ ^/[\w\.-]+/[\w\.-]+/gitlab-lfs/objects { + client_max_body_size 0; + # 'Error' 418 is a hack to re-use the @gitlab-workhorse block + error_page 418 = @gitlab-workhorse; + return 418; + } + location ~ ^/[\w\.-]+/[\w\.-]+/(info/refs|git-upload-pack|git-receive-pack)$ { # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; diff --git a/spec/factories/lfs_objects.rb b/spec/factories/lfs_objects.rb new file mode 100644 index 0000000000..7fb2d77ca3 --- /dev/null +++ b/spec/factories/lfs_objects.rb @@ -0,0 +1,12 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :lfs_object do + oid "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" + size 499013 + end + + trait :with_file do + file { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") } + end +end diff --git a/spec/factories/lfs_objects_projects.rb b/spec/factories/lfs_objects_projects.rb new file mode 100644 index 0000000000..93de6607df --- /dev/null +++ b/spec/factories/lfs_objects_projects.rb @@ -0,0 +1,8 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :lfs_objects_project do + lfs_object + project + end +end diff --git a/spec/lib/gitlab/lfs/lfs_router_spec.rb b/spec/lib/gitlab/lfs/lfs_router_spec.rb new file mode 100644 index 0000000000..cebcb5bc88 --- /dev/null +++ b/spec/lib/gitlab/lfs/lfs_router_spec.rb @@ -0,0 +1,650 @@ +require 'spec_helper' + +describe Gitlab::Lfs::Router do + let(:project) { create(:project) } + let(:public_project) { create(:project, :public) } + let(:forked_project) { fork_project(public_project, user) } + + let(:user) { create(:user) } + let(:user_two) { create(:user) } + let!(:lfs_object) { create(:lfs_object, :with_file) } + + let(:request) { Rack::Request.new(env) } + let(:env) do + { + 'rack.input' => '', + 'REQUEST_METHOD' => 'GET', + } + end + + let(:lfs_router_auth) { new_lfs_router(project, user) } + let(:lfs_router_noauth) { new_lfs_router(project, nil) } + let(:lfs_router_public_auth) { new_lfs_router(public_project, user) } + let(:lfs_router_public_noauth) { new_lfs_router(public_project, nil) } + let(:lfs_router_forked_noauth) { new_lfs_router(forked_project, nil) } + let(:lfs_router_forked_auth) { new_lfs_router(forked_project, user_two) } + + let(:sample_oid) { "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" } + let(:sample_size) { 499013 } + + describe 'when lfs is disabled' do + before do + allow(Gitlab.config.lfs).to receive(:enabled).and_return(false) + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/#{sample_oid}" + end + + it 'responds with 501' do + respond_with_disabled = [ 501, + { "Content-Type"=>"application/vnd.git-lfs+json" }, + ["{\"message\":\"Git LFS is not enabled on this GitLab server, contact your admin.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"] + ] + expect(lfs_router_auth.try_call).to match_array(respond_with_disabled) + end + end + + describe 'when fetching lfs object' do + before do + enable_lfs + env['HTTP_ACCEPT'] = "application/vnd.git-lfs+json; charset=utf-8" + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/#{sample_oid}" + end + + describe 'when user is authenticated' do + context 'and user has project download access' do + before do + @auth = authorize(user) + env["HTTP_AUTHORIZATION"] = @auth + project.lfs_objects << lfs_object + project.team << [user, :master] + end + + it "responds with status 200" do + expect(lfs_router_auth.try_call.first).to eq(200) + end + + it "responds with download hypermedia" do + json_response = ActiveSupport::JSON.decode(lfs_router_auth.try_call.last.first) + + expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}") + expect(json_response['_links']['download']['header']).to eq("Authorization" => @auth, "Accept" => "application/vnd.git-lfs+json; charset=utf-8") + end + end + + context 'and user does not have project access' do + it "responds with status 403" do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + end + + describe 'when user is unauthenticated' do + context 'and user does not have download access' do + it "responds with status 401" do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + + context 'and user has download access' do + before do + project.team << [user, :master] + end + + it "responds with status 401" do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + end + + describe 'and project is public' do + context 'and project has access to the lfs object' do + before do + public_project.lfs_objects << lfs_object + end + + context 'and user is authenticated' do + it "responds with status 200 and sends download hypermedia" do + expect(lfs_router_public_auth.try_call.first).to eq(200) + json_response = ActiveSupport::JSON.decode(lfs_router_public_auth.try_call.last.first) + + expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{public_project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}") + expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8") + end + end + + context 'and user is unauthenticated' do + it "responds with status 200 and sends download hypermedia" do + expect(lfs_router_public_noauth.try_call.first).to eq(200) + json_response = ActiveSupport::JSON.decode(lfs_router_public_noauth.try_call.last.first) + + expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{public_project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}") + expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8") + end + end + end + + context 'and project does not have access to the lfs object' do + it "responds with status 404" do + expect(lfs_router_public_auth.try_call.first).to eq(404) + end + end + end + + describe 'and request comes from gitlab-workhorse' do + before do + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}" + end + context 'without user being authorized' do + it "responds with status 401" do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + + context 'with required headers' do + before do + env['HTTP_X_SENDFILE_TYPE'] = "X-Sendfile" + end + + context 'when user does not have project access' do + it "responds with status 403" do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + + context 'when user has project access' do + before do + project.lfs_objects << lfs_object + project.team << [user, :master] + end + + it "responds with status 200" do + expect(lfs_router_auth.try_call.first).to eq(200) + end + + it "responds with the file location" do + expect(lfs_router_auth.try_call[1]['Content-Type']).to eq("application/octet-stream") + expect(lfs_router_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path) + end + end + end + + context 'without required headers' do + it "responds with status 403" do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + end + + describe 'from a forked public project' do + before do + env['HTTP_ACCEPT'] = "application/vnd.git-lfs+json; charset=utf-8" + env["PATH_INFO"] = "#{forked_project.repository.path_with_namespace}.git/info/lfs/objects/#{sample_oid}" + end + + context "when fetching a lfs object" do + context "and user has project download access" do + before do + public_project.lfs_objects << lfs_object + end + + it "can download the lfs object" do + expect(lfs_router_forked_auth.try_call.first).to eq(200) + json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first) + + expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{forked_project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}") + expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8") + end + end + + context "and user is not authenticated but project is public" do + before do + public_project.lfs_objects << lfs_object + end + + it "can download the lfs object" do + expect(lfs_router_forked_auth.try_call.first).to eq(200) + end + end + + context "and user has project download access" do + before do + env["PATH_INFO"] = "#{forked_project.repository.path_with_namespace}.git/info/lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897" + @auth = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) + env["HTTP_AUTHORIZATION"] = @auth + lfs_object_two = create(:lfs_object, :with_file, oid: "91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", size: 1575078) + public_project.lfs_objects << lfs_object_two + end + + it "can get a lfs object that is not in the forked project" do + expect(lfs_router_forked_auth.try_call.first).to eq(200) + + json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first) + expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{forked_project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") + expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8", "Authorization" => @auth) + end + end + + context "and user has project download access" do + before do + env["PATH_INFO"] = "#{forked_project.repository.path_with_namespace}.git/info/lfs/objects/267c8b1d876743971e3a9978405818ff5ca731c4c870b06507619cd9b1847b6b" + lfs_object_three = create(:lfs_object, :with_file, oid: "267c8b1d876743971e3a9978405818ff5ca731c4c870b06507619cd9b1847b6b", size: 127192524) + project.lfs_objects << lfs_object_three + end + + it "cannot get a lfs object that is not in the project" do + expect(lfs_router_forked_auth.try_call.first).to eq(404) + end + end + end + end + end + + describe 'when initiating pushing of the lfs object' do + before do + enable_lfs + env['REQUEST_METHOD'] = 'POST' + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch" + end + + describe 'when user is authenticated' do + before do + body = { 'objects' => [{ + 'oid' => sample_oid, + 'size' => sample_size + }] + }.to_json + env['rack.input'] = StringIO.new(body) + end + + describe 'when user has project push access' do + before do + @auth = authorize(user) + env["HTTP_AUTHORIZATION"] = @auth + project.team << [user, :master] + end + + context 'when pushing an lfs object that already exists' do + before do + public_project.lfs_objects << lfs_object + end + + it "responds with status 200 and links the object to the project" do + response_body = lfs_router_auth.try_call.last + response = ActiveSupport::JSON.decode(response_body.first) + + expect(response['objects']).to be_kind_of(Array) + expect(response['objects'].first['oid']).to eq(sample_oid) + expect(response['objects'].first['size']).to eq(sample_size) + expect(lfs_object.projects.pluck(:id)).to_not include(project.id) + expect(lfs_object.projects.pluck(:id)).to include(public_project.id) + expect(response['objects'].first).to have_key('_links') + end + end + + context 'when pushing a lfs object that does not exist' do + before do + body = { + 'objects' => [{ + 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }] + }.to_json + env['rack.input'] = StringIO.new(body) + end + + it "responds with status 200 and upload hypermedia link" do + response = lfs_router_auth.try_call + expect(response.first).to eq(200) + + response_body = ActiveSupport::JSON.decode(response.last.first) + expect(response_body['objects']).to be_kind_of(Array) + expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") + expect(response_body['objects'].first['size']).to eq(1575078) + expect(lfs_object.projects.pluck(:id)).not_to include(project.id) + expect(response_body['objects'].first['_links']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") + expect(response_body['objects'].first['_links']['upload']['header']).to eq("Authorization" => @auth) + end + end + + context 'when pushing one new and one existing lfs object' do + before do + body = { + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }, + { 'oid' => sample_oid, + 'size' => sample_size + } + ] + }.to_json + env['rack.input'] = StringIO.new(body) + public_project.lfs_objects << lfs_object + end + + it "responds with status 200 with upload hypermedia link for the new object" do + response = lfs_router_auth.try_call + expect(response.first).to eq(200) + + response_body = ActiveSupport::JSON.decode(response.last.first) + expect(response_body['objects']).to be_kind_of(Array) + + + expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") + expect(response_body['objects'].first['size']).to eq(1575078) + expect(response_body['objects'].first['_links']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") + expect(response_body['objects'].first['_links']['upload']['header']).to eq("Authorization" => @auth) + + expect(response_body['objects'].last['oid']).to eq(sample_oid) + expect(response_body['objects'].last['size']).to eq(sample_size) + expect(lfs_object.projects.pluck(:id)).to_not include(project.id) + expect(lfs_object.projects.pluck(:id)).to include(public_project.id) + expect(response_body['objects'].last).to have_key('_links') + end + end + end + + context 'when user does not have push access' do + it 'responds with 403' do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + end + + context 'when user is not authenticated' do + context 'when user has push access' do + before do + project.team << [user, :master] + end + + it "responds with status 401" do + expect(lfs_router_public_noauth.try_call.first).to eq(401) + end + end + + context 'when user does not have push access' do + it "responds with status 401" do + expect(lfs_router_public_noauth.try_call.first).to eq(401) + end + end + end + end + + describe 'when pushing a lfs object' do + before do + enable_lfs + env['REQUEST_METHOD'] = 'PUT' + end + + describe 'to one project' do + describe 'when user has push access to the project' do + before do + project.team << [user, :master] + end + + describe 'when user is authenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(project) + end + + it 'responds with status 200, location of lfs store and object details' do + json_response = ActiveSupport::JSON.decode(lfs_router_auth.try_call.last.first) + + expect(lfs_router_auth.try_call.first).to eq(200) + expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload") + expect(json_response['LfsOid']).to eq(sample_oid) + expect(json_response['LfsSize']).to eq(sample_size) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(project) + end + + it 'responds with status 200 and lfs object is linked to the project' do + expect(lfs_router_auth.try_call.first).to eq(200) + expect(lfs_object.projects.pluck(:id)).to include(project.id) + end + end + end + + describe 'when user is unauthenticated' do + let(:lfs_router_noauth) { new_lfs_router(project, nil) } + + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(project) + end + + it 'responds with status 401' do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(project) + end + + it 'responds with status 401' do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + + context 'and request is sent with a malformed headers' do + before do + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}" + env["HTTP_X_GITLAB_LFS_TMP"] = "cat /etc/passwd" + end + + it 'does not recognize it as a valid lfs command' do + expect(lfs_router_noauth.try_call).to eq(nil) + end + end + end + end + + describe 'and user does not have push access' do + describe 'when user is authenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(project) + end + + it 'responds with 403' do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(project) + end + + it 'responds with 403' do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + end + + describe 'when user is unauthenticated' do + let(:lfs_router_noauth) { new_lfs_router(project, nil) } + + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(project) + end + + it 'responds with 401' do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(project) + end + + it 'responds with 401' do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + end + end + end + + describe "to a forked project" do + let(:forked_project) { fork_project(public_project, user) } + + describe 'when user has push access to the project' do + before do + forked_project.team << [user_two, :master] + end + + describe 'when user is authenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(forked_project) + end + + it 'responds with status 200, location of lfs store and object details' do + json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first) + + expect(lfs_router_forked_auth.try_call.first).to eq(200) + expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload") + expect(json_response['LfsOid']).to eq(sample_oid) + expect(json_response['LfsSize']).to eq(sample_size) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(forked_project) + end + + it 'responds with status 200 and lfs object is linked to the source project' do + expect(lfs_router_forked_auth.try_call.first).to eq(200) + expect(lfs_object.projects.pluck(:id)).to include(public_project.id) + end + end + end + + describe 'when user is unauthenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(forked_project) + end + + it 'responds with status 401' do + expect(lfs_router_forked_noauth.try_call.first).to eq(401) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(forked_project) + end + + it 'responds with status 401' do + expect(lfs_router_forked_noauth.try_call.first).to eq(401) + end + end + end + end + + describe 'and user does not have push access' do + describe 'when user is authenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(forked_project) + end + + it 'responds with 403' do + expect(lfs_router_forked_auth.try_call.first).to eq(403) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(forked_project) + end + + it 'responds with 403' do + expect(lfs_router_forked_auth.try_call.first).to eq(403) + end + end + end + + describe 'when user is unauthenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(forked_project) + end + + it 'responds with 401' do + expect(lfs_router_forked_noauth.try_call.first).to eq(401) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(forked_project) + end + + it 'responds with 401' do + expect(lfs_router_forked_noauth.try_call.first).to eq(401) + end + end + end + end + + describe 'and second project not related to fork or a source project' do + let(:second_project) { create(:project) } + let(:lfs_router_second_project) { new_lfs_router(second_project, user) } + + before do + public_project.lfs_objects << lfs_object + headers_for_upload_finalize(second_project) + end + + context 'when pushing the same lfs object to the second project' do + before do + second_project.team << [user, :master] + end + + it 'responds with 200 and links the lfs object to the project' do + expect(lfs_router_second_project.try_call.first).to eq(200) + expect(lfs_object.projects.pluck(:id)).to include(second_project.id, public_project.id) + end + end + end + end + end + + def enable_lfs + allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) + end + + def authorize(user) + ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) + end + + def new_lfs_router(project, user) + Gitlab::Lfs::Router.new(project, user, request) + end + + def header_for_upload_authorize(project) + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize" + end + + def headers_for_upload_finalize(project) + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}" + env["HTTP_X_GITLAB_LFS_TMP"] = "#{sample_oid}6e561c9d4" + end + + def fork_project(project, user, object = nil) + allow(RepositoryForkWorker).to receive(:perform_async).and_return(true) + Projects::ForkService.new(project, user, {}).execute + end +end From 796bb651700c26ce1a5693ba6d6c8b2353cb6e34 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 14 Nov 2015 19:29:58 +0100 Subject: [PATCH 134/140] Remove duplicate methods in uploaders Signed-off-by: Dmitriy Zaporozhets --- app/uploaders/attachment_uploader.rb | 19 ++----------------- app/uploaders/avatar_uploader.rb | 19 ++----------------- app/uploaders/file_uploader.rb | 19 ++----------------- app/uploaders/uploader_helper.rb | 19 +++++++++++++++++++ 4 files changed, 25 insertions(+), 51 deletions(-) create mode 100644 app/uploaders/uploader_helper.rb diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb index a9691bee46..a65a896e41 100644 --- a/app/uploaders/attachment_uploader.rb +++ b/app/uploaders/attachment_uploader.rb @@ -1,26 +1,11 @@ # encoding: utf-8 class AttachmentUploader < CarrierWave::Uploader::Base + include UploaderHelper + storage :file def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end - - def image? - img_ext = %w(png jpg jpeg gif bmp tiff) - if file.respond_to?(:extension) - img_ext.include?(file.extension.downcase) - else - # Not all CarrierWave storages respond to :extension - ext = file.path.split('.').last.downcase - img_ext.include?(ext) - end - rescue - false - end - - def file_storage? - self.class.storage == CarrierWave::Storage::File - end end diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb index 7cad044555..6135c3ad96 100644 --- a/app/uploaders/avatar_uploader.rb +++ b/app/uploaders/avatar_uploader.rb @@ -1,6 +1,8 @@ # encoding: utf-8 class AvatarUploader < CarrierWave::Uploader::Base + include UploaderHelper + storage :file after :store, :reset_events_cache @@ -9,23 +11,6 @@ class AvatarUploader < CarrierWave::Uploader::Base "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end - def image? - img_ext = %w(png jpg jpeg gif bmp tiff) - if file.respond_to?(:extension) - img_ext.include?(file.extension.downcase) - else - # Not all CarrierWave storages respond to :extension - ext = file.path.split('.').last.downcase - img_ext.include?(ext) - end - rescue - false - end - - def file_storage? - self.class.storage == CarrierWave::Storage::File - end - def reset_events_cache(file) model.reset_events_cache if model.is_a?(User) end diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb index e821158583..ac920119a8 100644 --- a/app/uploaders/file_uploader.rb +++ b/app/uploaders/file_uploader.rb @@ -1,5 +1,7 @@ # encoding: utf-8 class FileUploader < CarrierWave::Uploader::Base + include UploaderHelper + storage :file attr_accessor :project, :secret @@ -28,21 +30,4 @@ class FileUploader < CarrierWave::Uploader::Base def secure_url File.join("/uploads", @secret, file.filename) end - - def file_storage? - self.class.storage == CarrierWave::Storage::File - end - - def image? - img_ext = %w(png jpg jpeg gif bmp tiff) - if file.respond_to?(:extension) - img_ext.include?(file.extension.downcase) - else - # Not all CarrierWave storages respond to :extension - ext = file.path.split('.').last.downcase - img_ext.include?(ext) - end - rescue - false - end end diff --git a/app/uploaders/uploader_helper.rb b/app/uploaders/uploader_helper.rb new file mode 100644 index 0000000000..5ef440f336 --- /dev/null +++ b/app/uploaders/uploader_helper.rb @@ -0,0 +1,19 @@ +# Extra methods for uploader +module UploaderHelper + def image? + img_ext = %w(png jpg jpeg gif bmp tiff) + if file.respond_to?(:extension) + img_ext.include?(file.extension.downcase) + else + # Not all CarrierWave storages respond to :extension + ext = file.path.split('.').last.downcase + img_ext.include?(ext) + end + rescue + false + end + + def file_storage? + self.class.storage == CarrierWave::Storage::File + end +end From cd513034e66a3eccb9022968af3a09bc7203a9c9 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 14 Nov 2015 19:31:14 +0100 Subject: [PATCH 135/140] Set less strict flay option for now Signed-off-by: Dmitriy Zaporozhets --- lib/tasks/flay.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/flay.rake b/lib/tasks/flay.rake index 5efffc2cda..dfb9df4772 100644 --- a/lib/tasks/flay.rake +++ b/lib/tasks/flay.rake @@ -1,6 +1,6 @@ desc 'Code duplication analyze via flay' task :flay do - output = %x(bundle exec flay app/ lib/gitlab/) + output = %x(bundle exec flay --mass 30 app/ lib/gitlab/) if output.include? "Similar code found" puts output From 433e4a80efa50bdc3b588ae34c488b6982c2985d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 14 Nov 2015 19:38:05 +0100 Subject: [PATCH 136/140] Fix code duplication in NotificationsHelper Signed-off-by: Dmitriy Zaporozhets --- app/helpers/notifications_helper.rb | 38 ++++++++++------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index cf11f8e532..ba07278614 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -16,40 +16,28 @@ module NotificationsHelper def notification_list_item(notification_level, user_membership) case notification_level when Notification::N_DISABLED - content_tag(:li, class: active_level_for(user_membership, Notification::N_DISABLED)) do - link_to '#', class: 'update-notification', data: { notification_level: Notification::N_DISABLED } do - icon('microphone-slash fw', text: 'Disabled') - end - end + update_notification_link(Notification::N_DISABLED, user_membership, 'Disabled', 'microphone-slash') when Notification::N_PARTICIPATING - content_tag(:li, class: active_level_for(user_membership, Notification::N_PARTICIPATING)) do - link_to '#', class: 'update-notification', data: { notification_level: Notification::N_PARTICIPATING } do - icon('volume-up fw', text: 'Participate') - end - end + update_notification_link(Notification::N_PARTICIPATING, user_membership, 'Participate', 'volume-up') when Notification::N_WATCH - content_tag(:li, class: active_level_for(user_membership, Notification::N_WATCH)) do - link_to '#', class: 'update-notification', data: { notification_level: Notification::N_WATCH } do - icon('eye fw', text: 'Watch') - end - end + update_notification_link(Notification::N_WATCH, user_membership, 'Watch', 'eye') when Notification::N_MENTION - content_tag(:li, class: active_level_for(user_membership, Notification::N_MENTION)) do - link_to '#', class: 'update-notification', data: { notification_level: Notification::N_MENTION } do - icon('at fw', text: 'On mention') - end - end + update_notification_link(Notification::N_MENTION, user_membership, 'On mention', 'at') when Notification::N_GLOBAL - content_tag(:li, class: active_level_for(user_membership, Notification::N_GLOBAL)) do - link_to '#', class: 'update-notification', data: { notification_level: Notification::N_GLOBAL } do - icon('globe fw', text: 'Global') - end - end + update_notification_link(Notification::N_GLOBAL, user_membership, 'Global', 'globe') else # do nothing end end + def update_notification_link(notification_label, user_membership, title, icon) + content_tag(:li, class: active_level_for(user_membership, notification_level)) do + link_to '#', class: 'update-notification', data: { notification_level: notification_label } do + icon("#{icon} fw", text: title) + end + end + end + def notification_label(user_membership) Notification.new(user_membership).to_s end From 0698c96d7dc472a0e7e74c290047a50eeddf27bb Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 14 Nov 2015 19:43:48 +0100 Subject: [PATCH 137/140] Remove duplicate code in Repository#*_names_contains Signed-off-by: Dmitriy Zaporozhets --- app/models/repository.rb | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index f8c4cb1387..f76b770e86 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -346,8 +346,8 @@ class Repository end end - def branch_names_contains(sha) - args = %W(#{Gitlab.config.git.bin_path} branch --contains #{sha}) + def refs_contains_sha(ref_type, sha) + args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha}) names = Gitlab::Popen.popen(args, path_to_repo).first if names.respond_to?(:split) @@ -363,21 +363,12 @@ class Repository end end + def branch_names_contains(sha) + refs_contains_sha('branch', sha) + end + def tag_names_contains(sha) - args = %W(#{Gitlab.config.git.bin_path} tag --contains #{sha}) - names = Gitlab::Popen.popen(args, path_to_repo).first - - if names.respond_to?(:split) - names = names.split("\n").map(&:strip) - - names.each do |name| - name.slice! '* ' - end - - names - else - [] - end + refs_contains_sha('tag', sha) end def branches From c9f2f2a4838f9aa49782acdb7cfad75d06f31ac2 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 09:53:17 +0100 Subject: [PATCH 138/140] Fix wrong variable name Signed-off-by: Dmitriy Zaporozhets --- app/helpers/notifications_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index ba07278614..499c655d2b 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -30,9 +30,9 @@ module NotificationsHelper end end - def update_notification_link(notification_label, user_membership, title, icon) + def update_notification_link(notification_level, user_membership, title, icon) content_tag(:li, class: active_level_for(user_membership, notification_level)) do - link_to '#', class: 'update-notification', data: { notification_level: notification_label } do + link_to '#', class: 'update-notification', data: { notification_level: notification_level } do icon("#{icon} fw", text: title) end end From 03f5ff750b107b30a6d306aafb6699a9c9ecff0d Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 16 Nov 2015 13:24:36 +0100 Subject: [PATCH 139/140] Show specific runners from projects where user is master or owner --- CHANGELOG | 1 + app/models/user.rb | 15 ++++++++++----- spec/features/runners_spec.rb | 12 +++++++++++- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9806666833..45ef22e7e8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -35,6 +35,7 @@ v 8.2.0 (unreleased) - New design for project graphs page - Remove deprecated dumped yaml file generated from previous job definitions - Fix incoming email config defaults + - Show specific runners from projects where user is master or owner - MR target branch is now visible on a list view when it is different from project's default one - Improve Continuous Integration graphs page - Make color of "Accept Merge Request" button consistent with current build status diff --git a/app/models/user.rb b/app/models/user.rb index 9ffadcf446..61abea1f6e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -405,6 +405,15 @@ class User < ActiveRecord::Base end end + def master_or_owner_projects_id + @master_or_owner_projects_id ||= begin + scope = { access_level: [ Gitlab::Access::MASTER, Gitlab::Access::OWNER ] } + project_ids = personal_projects.pluck(:id) + project_ids.push(*groups_projects.where(members: scope).pluck(:id)) + project_ids.push(*projects.where(members: scope).pluck(:id).uniq) + end + end + # Projects user has access to def authorized_projects @authorized_projects ||= Project.where(id: authorized_projects_id) @@ -765,14 +774,10 @@ class User < ActiveRecord::Base !solo_owned_groups.present? end - def ci_authorized_projects - @ci_authorized_projects ||= Ci::Project.where(gitlab_id: authorized_projects_id) - end - def ci_authorized_runners @ci_authorized_runners ||= begin runner_ids = Ci::RunnerProject.joins(:project). - where(ci_projects: { gitlab_id: authorized_projects_id }).select(:runner_id) + where(ci_projects: { gitlab_id: master_or_owner_projects_id }).select(:runner_id) Ci::Runner.specific.where(id: runner_ids) end end diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index 06adb7633b..b025902663 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -14,15 +14,25 @@ describe "Runners" do @project2 = FactoryGirl.create :ci_project @project2.gl_project.team << [user, :master] + @project3 = FactoryGirl.create :ci_project + @project3.gl_project.team << [user, :developer] + @shared_runner = FactoryGirl.create :ci_shared_runner @specific_runner = FactoryGirl.create :ci_specific_runner @specific_runner2 = FactoryGirl.create :ci_specific_runner + @specific_runner3 = FactoryGirl.create :ci_specific_runner @project.runners << @specific_runner @project2.runners << @specific_runner2 + @project3.runners << @specific_runner3 visit runners_path(@project.gl_project) end + before do + expect(page).to_not have_content(@specific_runner3.display_name) + expect(page).to_not have_content(@specific_runner3.display_name) + end + it "places runners in right places" do expect(page.find(".available-specific-runners")).to have_content(@specific_runner2.display_name) expect(page.find(".activated-specific-runners")).to have_content(@specific_runner.display_name) @@ -76,10 +86,10 @@ describe "Runners" do @project.gl_project.team << [user, :master] @specific_runner = FactoryGirl.create :ci_specific_runner @project.runners << @specific_runner - visit runners_path(@project.gl_project) end it "shows runner information" do + visit runners_path(@project.gl_project) click_on @specific_runner.short_sha expect(page).to have_content(@specific_runner.platform) end From f194f56538ab5da5cc4dd9f89d5784877a5c4bd9 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 16 Nov 2015 16:54:13 +0200 Subject: [PATCH 140/140] fix tests --- Gemfile.lock | 5 ++++- spec/lib/gitlab/git_access_spec.rb | 18 +++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 1a8c690e1f..0040ebe0c4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -840,7 +840,7 @@ DEPENDENCIES github-linguist (~> 4.7.0) github-markup (~> 1.3.1) gitlab-flowdock-git-hook (~> 1.0.1) - gitlab-license (~> 0.0.3) + gitlab-license (~> 0.0.2) gitlab_emoji (~> 0.1) gitlab_git (~> 7.2.20) gitlab_meta (= 7.0) @@ -942,3 +942,6 @@ DEPENDENCIES virtus (~> 1.0.1) webmock (~> 1.21.0) wikicloth (= 0.8.1) + +BUNDLED WITH + 1.10.6 diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 8f043342c8..ea9f78213a 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -249,19 +249,19 @@ describe Gitlab::GitAccess do describe "git_hook_check" do describe "author email check" do it 'returns true' do - expect(access.git_hook_check(user, project, 'refs/heads/master', '6f6d7e7ed', '570e7b2ab')).to be_truthy + expect(access.git_hook_check(user, project, 'refs/heads/master', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).to be_truthy end it 'returns false' do project.create_git_hook project.git_hook.update(commit_message_regex: "@only.com") - expect(access.git_hook_check(user, project, 'refs/heads/master', '6f6d7e7ed', '570e7b2ab')).not_to be_allowed + expect(access.git_hook_check(user, project, 'refs/heads/master', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).not_to be_allowed end it 'returns true for tags' do project.create_git_hook project.git_hook.update(commit_message_regex: "@only.com") - expect(access.git_hook_check(user, project, 'refs/tags/v1', '6f6d7e7ed', '570e7b2ab')).to be_allowed + expect(access.git_hook_check(user, project, 'refs/tags/v1', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).to be_allowed end end @@ -272,12 +272,12 @@ describe Gitlab::GitAccess do end it 'returns false for non-member user' do - expect(access.git_hook_check(user, project, 'refs/heads/master', '6f6d7e7ed', '570e7b2ab')).not_to be_allowed + expect(access.git_hook_check(user, project, 'refs/heads/master', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).not_to be_allowed end it 'returns true if committer is a gitlab member' do create(:user, email: 'dmitriy.zaporozhets@gmail.com') - expect(access.git_hook_check(user, project, 'refs/heads/master', '6f6d7e7ed', '570e7b2ab')).to be_allowed + expect(access.git_hook_check(user, project, 'refs/heads/master', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).to be_allowed end end @@ -285,13 +285,13 @@ describe Gitlab::GitAccess do it 'returns false when filename is prohibited' do project.create_git_hook project.git_hook.update(file_name_regex: "jpg$") - expect(access.git_hook_check(user, project, 'refs/heads/master', '913c66a37', '33f3729a4')).not_to be_allowed + expect(access.git_hook_check(user, project, 'refs/heads/master', '913c66a37b4a45b9769037c55c2d238bd0942d2e', '33f3729a45c02fc67d00adb1b8bca394b0e761d9')).not_to be_allowed end it 'returns true if file name is allowed' do project.create_git_hook project.git_hook.update(file_name_regex: "exe$") - expect(access.git_hook_check(user, project, 'refs/heads/master', '913c66a37', '33f3729a4')).to be_allowed + expect(access.git_hook_check(user, project, 'refs/heads/master', '913c66a37b4a45b9769037c55c2d238bd0942d2e', '33f3729a45c02fc67d00adb1b8bca394b0e761d9')).to be_allowed end end @@ -303,13 +303,13 @@ describe Gitlab::GitAccess do it "returns false when size is too large" do project.create_git_hook project.git_hook.update(max_file_size: 1) - expect(access.git_hook_check(user, project, 'refs/heads/master', 'cfe32cf6', '913c66a37')).not_to be_allowed + expect(access.git_hook_check(user, project, 'refs/heads/master', 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660', '913c66a37b4a45b9769037c55c2d238bd0942d2e')).not_to be_allowed end it "returns true when size is allowed" do project.create_git_hook project.git_hook.update(max_file_size: 2) - expect(access.git_hook_check(user, project, 'refs/heads/master', 'cfe32cf6', '913c66a37')).to be_allowed + expect(access.git_hook_check(user, project, 'refs/heads/master', 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660', '913c66a37b4a45b9769037c55c2d238bd0942d2e')).to be_allowed end end end