diff --git a/.gitignore b/.gitignore index 2c6b65b7b7..7a7b5c9393 100644 --- a/.gitignore +++ b/.gitignore @@ -1,42 +1,42 @@ -.bundle -.rbx/ -db/*.sqlite3 -db/*.sqlite3-journal -log/*.log* -tmp/ -.sass-cache/ -coverage/* -backups/* +*.log *.swp -public/uploads/ -.ruby-version -.ruby-gemset -.rvmrc -.rbenv-version +.DS_Store +.bundle +.chef .directory -nohup.out -Vagrantfile +.envrc +.gitlab_shell_secret +.idea +.rbenv-version +.rbx/ +.ruby-gemset +.ruby-version +.rvmrc +.sass-cache/ +.secret .vagrant -config/gitlab.yml +Vagrantfile +backups/* +config/aws.yml config/database.yml +config/gitlab.yml config/initializers/omniauth.rb config/initializers/rack_attack.rb config/initializers/smtp_settings.rb -config/unicorn.rb config/resque.yml -config/aws.yml +config/unicorn.rb +coverage/* +db/*.sqlite3 +db/*.sqlite3-journal db/data.yml -.idea -.DS_Store -.chef -vendor/bundle/* -rails_best_practices_output.html doc/code/* -.secret -*.log -public/uploads.* -public/assets/ -.envrc dump.rdb +log/*.log* +nohup.out +public/assets/ +public/uploads.* +public/uploads/ +rails_best_practices_output.html tags -.gitlab_shell_secret +tmp/ +vendor/bundle/* diff --git a/.ruby-version b/.ruby-version index ac2cdeba01..cd57a8b95d 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.1.3 +2.1.5 diff --git a/CHANGELOG b/CHANGELOG index 4aa746e668..4b616082d9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,21 +1,45 @@ v 7.7.0 - - + - Add Jetbrains Teamcity CI service (Jason Lippert) - - - - - - - - - - + - Mention notification level + - Markdown preview in wiki (Yuriy Glukhov) + - Raise group avatar filesize limit to 200kb - OAuth applications feature - - - - + - Show user SSH keys in admin area + - Developer can push to protected branches option - Set project path instead of project name in create form - - - - New side navigation - - + - Updates to the messages returned by API (sponsored by O'Reilly Media) + - New UI layout with side navigation + - + - + - + - Add alert message in case of outdated browser (IE < 10) + - + - Added API support for sorting projects + - Update gitlab_git to version 7.0.0.rc13 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - Change some of application settings on fly in admin area UI + - Redesign signin/signup pages + - Close standard input in Gitlab::Popen.popen v 7.6.0 - Fork repository to groups @@ -41,8 +65,15 @@ v 7.6.0 - Possibility to create Milestones or Labels when Issues are disabled - Fix bug with showing gpg signature in tag +v 7.5.3 + - Bump gitlab_git to 7.0.0.rc12 (includes Rugged 0.21.2) + v 7.5.2 - Don't log Sidekiq arguments by default + - Fix restore of wiki repositories from backups + +v 7.5.1 + - Add missing timestamps to 'members' table v 7.5.0 - API: Add support for Hipchat (Kevin Houdebert) @@ -62,7 +93,7 @@ v 7.5.0 - Performance improvements - Fix post-receive issue for projects with deleted forks - New gitlab-shell version with custom hooks support - - Improve code + - Improve code - GitLab CI 5.2+ support (does not support older versions) - Fixed bug when you can not push commits starting with 000000 to protected branches - Added a password strength indicator diff --git a/Gemfile b/Gemfile index 6620d2b2ba..48fa25930c 100644 --- a/Gemfile +++ b/Gemfile @@ -32,9 +32,12 @@ gem 'omniauth-kerberos' gem 'doorkeeper', '2.0.1' gem "rack-oauth2", "~> 1.0.5" +# Browser detection +gem "browser" + # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", '7.0.0.rc12' +gem "gitlab_git", '7.0.0.rc13' # Ruby/Rack Git Smart-HTTP Server Handler gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack' @@ -93,7 +96,7 @@ gem "github-markup" gem 'redcarpet', '~> 3.1.2' gem 'RedCloth' gem 'rdoc', '~>3.6' -gem 'org-ruby', '= 0.9.9' +gem 'org-ruby', '= 0.9.12' gem 'creole', '~>0.3.6' gem 'wikicloth', '=0.8.1' gem 'asciidoctor', '= 0.1.4' diff --git a/Gemfile.lock b/Gemfile.lock index b69fa83fbc..da56c4a0a5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -50,6 +50,7 @@ GEM debug_inspector (>= 0.0.1) bootstrap-sass (3.0.3.0) sass (~> 3.2) + browser (0.7.2) builder (3.2.2) capybara (2.2.1) mime-types (>= 1.16) @@ -123,7 +124,7 @@ GEM equalizer (0.0.8) erubis (2.7.0) escape_utils (0.2.4) - eventmachine (1.0.3) + eventmachine (1.0.4) excon (0.32.1) execjs (2.0.2) expression_parser (0.9.0) @@ -182,7 +183,7 @@ GEM mime-types (~> 1.19) gitlab_emoji (0.0.1.1) emoji (~> 1.0.1) - gitlab_git (7.0.0.rc12) + gitlab_git (7.0.0.rc13) activesupport (~> 4.0) charlock_holmes (~> 0.6) gitlab-linguist (~> 3.0) @@ -280,7 +281,7 @@ GEM kaminari (0.15.1) actionpack (>= 3.0.0) activesupport (>= 3.0.0) - kgio (2.8.1) + kgio (2.9.2) launchy (2.4.2) addressable (~> 2.3) letter_opener (1.1.2) @@ -343,7 +344,7 @@ GEM omniauth-twitter (1.0.1) multi_json (~> 1.3) omniauth-oauth (~> 1.0) - org-ruby (0.9.9) + org-ruby (0.9.12) rubypants (~> 0.2) orm_adapter (0.5.0) pg (0.15.1) @@ -409,7 +410,7 @@ GEM activesupport (= 4.1.1) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - raindrops (0.12.0) + raindrops (0.13.0) rake (10.3.2) raphael-rails (2.1.2) rb-fsevent (0.9.3) @@ -619,6 +620,7 @@ DEPENDENCIES better_errors binding_of_caller bootstrap-sass (~> 3.0) + browser capybara (~> 2.2.1) carrierwave coffee-rails @@ -646,7 +648,7 @@ DEPENDENCIES gitlab-grack (~> 2.0.0.pre) gitlab-linguist (~> 3.0.0) gitlab_emoji (~> 0.0.1.1) - gitlab_git (= 7.0.0.rc12) + gitlab_git (= 7.0.0.rc13) gitlab_meta (= 7.0) gitlab_omniauth-ldap (= 1.2.0) gollum-lib (~> 3.0.0) @@ -681,7 +683,7 @@ DEPENDENCIES omniauth-kerberos omniauth-shibboleth omniauth-twitter - org-ruby (= 0.9.9) + org-ruby (= 0.9.12) pg poltergeist (~> 1.5.1) pry diff --git a/Procfile b/Procfile index a5693f8dbc..a0ab4a734a 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,2 @@ web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"} -worker: bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,common,default,gitlab_shell +worker: bundle exec sidekiq -q post_receive -q mailer -q system_hook -q project_web_hook -q gitlab_shell -q common -q default diff --git a/VERSION b/VERSION index 88848b5b1e..0b59b6fe31 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.6.0.pre-ee +7.7.0.pre-ee diff --git a/app/assets/javascripts/protected_branches.js.coffee b/app/assets/javascripts/protected_branches.js.coffee new file mode 100644 index 0000000000..691fd4f10d --- /dev/null +++ b/app/assets/javascripts/protected_branches.js.coffee @@ -0,0 +1,21 @@ +$ -> + $(":checkbox").change -> + name = $(this).attr("name") + if name == "developers_can_push" + id = $(this).val() + checked = $(this).is(":checked") + url = $(this).data("url") + $.ajax + type: "PUT" + url: url + dataType: "json" + data: + id: id + developers_can_push: checked + + success: -> + new Flash("Branch updated.", "notice") + location.reload true + + error: -> + new Flash("Failed to update branch!", "alert") diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss index eb55392aa4..b41a2911bf 100644 --- a/app/assets/stylesheets/generic/common.scss +++ b/app/assets/stylesheets/generic/common.scss @@ -207,24 +207,16 @@ li.note { } } -.no-ssh-key-message { - padding: 10px 0; - background: #C67; - margin: 0; - color: #FFF; - margin-top: -1px; +.browser-alert { + padding: 10px; text-align: center; - + background: #C67; + color: #fff; + font-weight: bold; a { color: #fff; text-decoration: underline; } - - .links-xs { - text-align: center; - font-size: 16px; - padding: 5px; - } } .warning_message { @@ -282,7 +274,7 @@ img.emoji { } .navless-container { - margin-top: 20px; + margin-top: 68px; } .description-block { @@ -309,11 +301,17 @@ table { .dashboard-intro-icon { float: left; + text-align: center; font-size: 32px; color: #AAA; - padding: 5px 0; - width: 50px; - min-height: 100px; + width: 60px; +} + +.dashboard-intro-text { + display: inline-block; + margin-left: -60px; + padding-left: 60px; + width: 100%; } .broadcast-message { @@ -364,3 +362,9 @@ table { .task-status { margin-left: 10px; } + +#nprogress .spinner { + top: auto !important; + bottom: 20px !important; + left: 20px !important; +} diff --git a/app/assets/stylesheets/generic/forms.scss b/app/assets/stylesheets/generic/forms.scss index e8b23090b0..1a83256995 100644 --- a/app/assets/stylesheets/generic/forms.scss +++ b/app/assets/stylesheets/generic/forms.scss @@ -31,7 +31,12 @@ fieldset legend { margin-bottom: 18px; background-color: whitesmoke; border-top: 1px solid #e5e5e5; - padding-left: 17%; +} + +@media (min-width: $screen-sm-min) { + .form-actions { + padding-left: 17%; + } } label { @@ -88,7 +93,8 @@ label { @include box-shadow(none); } -.issuable-description { +.issuable-description, +.wiki-content { margin-top: 35px; } diff --git a/app/assets/stylesheets/generic/markdown_area.scss b/app/assets/stylesheets/generic/markdown_area.scss index 4168e235ca..5a87cc6c61 100644 --- a/app/assets/stylesheets/generic/markdown_area.scss +++ b/app/assets/stylesheets/generic/markdown_area.scss @@ -65,6 +65,7 @@ .edit_note, .issuable-description, .milestone-description, +.wiki-content, .merge-request-form { .nav-tabs { margin-bottom: 0; diff --git a/app/assets/stylesheets/main/variables.scss b/app/assets/stylesheets/main/variables.scss index ca296c85a9..92b220f801 100644 --- a/app/assets/stylesheets/main/variables.scss +++ b/app/assets/stylesheets/main/variables.scss @@ -46,4 +46,4 @@ $deleted: #f77; /** * NProgress customize */ -$nprogress-color: #3498db; +$nprogress-color: #c0392b; diff --git a/app/assets/stylesheets/sections/events.scss b/app/assets/stylesheets/sections/events.scss index 93ad17f57c..3c3a0d92c6 100644 --- a/app/assets/stylesheets/sections/events.scss +++ b/app/assets/stylesheets/sections/events.scss @@ -145,8 +145,12 @@ * Last push widget */ .event-last-push { + overflow: auto; .event-last-push-text { - @include str-truncated(75%); + @include str-truncated(100%); + float:left; + margin-right: -150px; + padding-right: 150px; line-height: 24px; } } diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/sections/header.scss index db419f7653..32b0b10c64 100644 --- a/app/assets/stylesheets/sections/header.scss +++ b/app/assets/stylesheets/sections/header.scss @@ -4,9 +4,13 @@ */ header { &.navbar-gitlab { + z-index: 100; margin-bottom: 0; min-height: 40px; border: none; + position: fixed; + top: 0; + width: 100%; .navbar-inner { filter: none; @@ -82,8 +86,6 @@ header { } } - z-index: 10; - .container { width: 100% !important; padding-left: 0px; diff --git a/app/assets/stylesheets/sections/login.scss b/app/assets/stylesheets/sections/login.scss index 1bcb1f6d68..901733ef9f 100644 --- a/app/assets/stylesheets/sections/login.scss +++ b/app/assets/stylesheets/sections/login.scss @@ -1,48 +1,66 @@ /* Login Page */ .login-page { - h1 { - font-size: 3em; - font-weight: 200; + .container { + max-width: 960px; + } + + .navbar-gitlab .container { + max-width: none; + } + + .brand-holder { + font-size: 18px; + line-height: 1.5; + + p { + color: #888; + } + + h1:first-child { + font-weight: normal; + margin-bottom: 30px; + } + + img { + max-width: 100%; + margin-bottom: 30px; + } + + a { + font-weight: bold; + } } .login-box{ - padding: 0 15px; + background: #fafafa; + border-radius: 10px; + box-shadow: 0 0px 2px #CCC; + padding: 15px; .login-heading h3 { font-weight: 300; - line-height: 2; + line-height: 1.5; + margin: 0; + display: none; } .login-footer { margin-top: 10px; } - .btn { - padding: 12px !important; - @extend .btn-block; - } - } - - .brand-image { - img { - max-width: 100%; - margin-bottom: 20px; + a.forgot { + float: right; + padding-top: 6px } - &.default-brand-image { - margin: 0 80px; + .nav .active a { + background: transparent; } } - .login-logo { - margin: 10px 0 30px 0; - display: block; - } - .form-control { - background-color: #F5F5F5; - font-size: 16px; - padding: 14px 10px; + font-size: 14px; + padding: 10px 8px; width: 100%; height: auto; @@ -68,11 +86,6 @@ } } - .login-box a.forgot { - float: right; - padding-top: 6px - } - .devise-errors { h2 { font-size: 14px; @@ -80,7 +93,19 @@ } } - .brand-holder { - border-right: 1px solid #EEE; + .remember-me { + margin-top: -10px; + + label { + font-weight: normal; + } + } +} + +@media (max-width: $screen-xs-max) { + .login-page { + .col-sm-5.pull-right { + float: none !important; + } } } diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss index 920702ff3c..8445b77c1a 100644 --- a/app/assets/stylesheets/sections/merge_requests.scss +++ b/app/assets/stylesheets/sections/merge_requests.scss @@ -11,10 +11,27 @@ } } - .accept-group { - label { - margin: 5px; + .accept-merge-holder { + margin-top: 5px; + + .accept-action { + display: inline-block; + + .accept_merge_request { + padding: 10px 20px; + } + } + + .accept-control { + display: inline-block; margin-left: 20px; + padding: 10px 0; + line-height: 20px; + font-weight: bold; + + .checkbox { + margin: 0; + } } } } @@ -170,7 +187,3 @@ .merge-request-show-labels .label { padding: 6px 10px; } - -.mr-commits .commit { - padding: 10px 15px; -} diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss index 74c500f88b..1550e30fe5 100644 --- a/app/assets/stylesheets/sections/notes.scss +++ b/app/assets/stylesheets/sections/notes.scss @@ -62,6 +62,7 @@ ul.notes { } .note-body { @include md-typography; + overflow: auto; } .note-header { padding-bottom: 3px; diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss index 7b894cf00b..fbfe9ad4c9 100644 --- a/app/assets/stylesheets/sections/projects.scss +++ b/app/assets/stylesheets/sections/projects.scss @@ -308,3 +308,10 @@ ul.nav.nav-projects-tabs { display: none; } } + + +table.table.protected-branches-list tr.no-border { + th, td { + border: 0; + } +} diff --git a/app/assets/stylesheets/sections/sidebar.scss b/app/assets/stylesheets/sections/sidebar.scss index 51d6b2c920..79441eba6d 100644 --- a/app/assets/stylesheets/sections/sidebar.scss +++ b/app/assets/stylesheets/sections/sidebar.scss @@ -3,6 +3,7 @@ } .sidebar-wrapper { + z-index: 99; overflow-y: auto; background: #F5F5F5; } @@ -11,6 +12,7 @@ width: 100%; padding: 15px; background: #FFF; + margin-top: 48px; } .nav-sidebar { @@ -85,15 +87,7 @@ padding-left: 0px; li { - line-height: 28px; - font-size: 12px; list-style: none; - - a { - padding: 5px 15px; - font-size: 12px; - padding-left: 20px; - } } } @@ -104,10 +98,11 @@ .sidebar-wrapper { width: 250px; - position: absolute; + position: fixed; left: 250px; height: 100%; margin-left: -250px; + border-right: 1px solid #EAEAEA; .nav-sidebar { margin-top: 20px; @@ -119,7 +114,6 @@ .content-wrapper { padding: 20px; - border-left: 1px solid #EAEAEA; } } @@ -130,14 +124,16 @@ .sidebar-wrapper { width: 52px; - position: absolute; - left: 50px; + position: fixed; + top: 0; + left: 0; height: 100%; - margin-left: -50px; + border-right: 1px solid #EAEAEA; + overflow-x: hidden; .nav-sidebar { margin-top: 20px; - position: fixed; + position: absolute; top: 45px; width: 52px; diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb new file mode 100644 index 0000000000..5116f1f177 --- /dev/null +++ b/app/controllers/admin/application_settings_controller.rb @@ -0,0 +1,31 @@ +class Admin::ApplicationSettingsController < Admin::ApplicationController + before_filter :set_application_setting + + def show + end + + def update + if @application_setting.update_attributes(application_setting_params) + redirect_to admin_application_settings_path, + notice: 'Application settings saved successfully' + else + render :show + end + end + + private + + def set_application_setting + @application_setting = ApplicationSetting.current + end + + def application_setting_params + params.require(:application_setting).permit( + :default_projects_limit, + :signup_enabled, + :signin_enabled, + :gravatar_enabled, + :sign_in_text, + ) + end +end diff --git a/app/controllers/admin/keys_controller.rb b/app/controllers/admin/keys_controller.rb new file mode 100644 index 0000000000..21111bb44f --- /dev/null +++ b/app/controllers/admin/keys_controller.rb @@ -0,0 +1,34 @@ +class Admin::KeysController < Admin::ApplicationController + before_filter :user, only: [:show, :destroy] + + def show + @key = user.keys.find(params[:id]) + + respond_to do |format| + format.html + format.js { render nothing: true } + end + end + + def destroy + key = user.keys.find(params[:id]) + + respond_to do |format| + if key.destroy + format.html { redirect_to [:admin, user], notice: 'User key was successfully removed.' } + else + format.html { redirect_to [:admin, user], alert: 'Failed to remove user key.' } + end + end + end + + protected + + def user + @user ||= User.find_by!(username: params[:user_id]) + end + + def key_params + params.require(:user_id, :id) + end +end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index baad9095b7..aea8545d38 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -11,6 +11,7 @@ class Admin::UsersController < Admin::ApplicationController def show @personal_projects = user.personal_projects @joined_projects = user.projects.joined(@user) + @keys = user.keys.order('id DESC') end def new @@ -118,7 +119,7 @@ class Admin::UsersController < Admin::ApplicationController :email, :remember_me, :bio, :name, :username, :skype, :linkedin, :twitter, :website_url, :color_scheme_id, :theme_id, :force_random_password, :extern_uid, :provider, :password_expires_at, :avatar, :hide_no_ssh_key, - :projects_limit, :can_create_group, :admin + :projects_limit, :can_create_group, :admin, :key_id ) end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 582225ef30..d14d6ab1a2 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,6 +1,8 @@ require 'gon' class ApplicationController < ActionController::Base + include Gitlab::CurrentSettings + before_filter :authenticate_user_from_token! before_filter :authenticate_user! before_filter :reject_blocked! @@ -13,7 +15,7 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception - helper_method :abilities, :can? + helper_method :abilities, :can?, :current_application_settings rescue_from Encoding::CompatibilityError do |exception| log_exception(exception) @@ -263,10 +265,6 @@ class ApplicationController < ActionController::Base # or improve current implementation to filter only issues you # created or assigned or mentioned #@filter_params[:authorized_only] = true - - unless @filter_params[:assignee_id] - @filter_params[:assignee_id] = current_user.id - end end @filter_params diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index f362f449e7..95801f8b8f 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -11,7 +11,7 @@ class Projects::MilestonesController < Projects::ApplicationController respond_to :html def index - @milestones = case params[:f] + @milestones = case params[:state] when 'all'; @project.milestones.order("state, due_date DESC") when 'closed'; @project.milestones.closed.order("due_date DESC") else @project.milestones.active.order("due_date ASC") diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb index bd31b1d3c5..02160d973b 100644 --- a/app/controllers/projects/protected_branches_controller.rb +++ b/app/controllers/projects/protected_branches_controller.rb @@ -15,6 +15,24 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController redirect_to project_protected_branches_path(@project) end + def update + protected_branch = @project.protected_branches.find(params[:id]) + + if protected_branch && + protected_branch.update_attributes( + developers_can_push: params[:developers_can_push] + ) + + respond_to do |format| + format.json { render :json => protected_branch, status: :ok } + end + else + respond_to do |format| + format.json { render json: protected_branch.errors, status: :unprocessable_entity } + end + end + end + def destroy @project.protected_branches.find(params[:id]).destroy @@ -27,6 +45,6 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController private def protected_branch_params - params.require(:protected_branch).permit(:name) + params.require(:protected_branch).permit(:name, :developers_can_push) end end diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index c50a1f1e75..ef4d260914 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -42,7 +42,7 @@ class Projects::ServicesController < Projects::ApplicationController :title, :token, :type, :active, :api_key, :subdomain, :room, :recipients, :project_url, :webhook, :user_key, :device, :priority, :sound, :bamboo_url, :username, :password, - :build_key, :server + :build_key, :server, :teamcity_url, :build_type ) end end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 6d3214b70a..52db44bf82 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -26,7 +26,9 @@ class RegistrationsController < Devise::RegistrationsController private def signup_enabled? - redirect_to new_user_session_path unless Gitlab.config.gitlab.signup_enabled + unless current_application_settings.signup_enabled? + redirect_to(new_user_session_path) + end end def sign_up_params diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 5ced98152a..7b6982c507 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,16 +1,16 @@ class SessionsController < Devise::SessionsController - def new - redirect_path = if request.referer.present? && (params['redirect_to_referer'] == 'yes') - referer_uri = URI(request.referer) - if referer_uri.host == Gitlab.config.gitlab.host - referer_uri.path - else - request.fullpath - end - else - request.fullpath - end + redirect_path = + if request.referer.present? && (params['redirect_to_referer'] == 'yes') + referer_uri = URI(request.referer) + if referer_uri.host == Gitlab.config.gitlab.host + referer_uri.path + else + request.fullpath + end + else + request.fullpath + end # Prevent a 'you are already signed in' message directly after signing: # we should never redirect to '/users/sign_in' after signing in successfully. diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 90cc58f44b..f21b0bd1f5 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -293,4 +293,21 @@ module ApplicationHelper path << "?#{options.to_param}" path end + + def outdated_browser? + browser.ie? && browser.version.to_i < 10 + end + + def path_to_key(key, admin = false) + if admin + admin_user_key_path(@user, key) + else + profile_key_path(key) + end + end + + def redirect_from_root? + request.env['rack.session']['user_return_to'] == + '/' + end end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb new file mode 100644 index 0000000000..0429931610 --- /dev/null +++ b/app/helpers/application_settings_helper.rb @@ -0,0 +1,17 @@ +module ApplicationSettingsHelper + def gravatar_enabled? + current_application_settings.gravatar_enabled? + end + + def signup_enabled? + current_application_settings.signup_enabled? + end + + def signin_enabled? + current_application_settings.signin_enabled? + end + + def extra_sign_in_text + current_application_settings.sign_in_text + end +end diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb index 976a396e7b..4dae96644c 100644 --- a/app/helpers/dashboard_helper.rb +++ b/app/helpers/dashboard_helper.rb @@ -1,18 +1,11 @@ module DashboardHelper - def entities_per_project(project, entity) - case entity.to_sym - when :issue then @issues.where(project_id: project.id) - when :merge_request then @merge_requests.where(target_project_id: project.id) - else - [] - end.count - end - def projects_dashboard_filter_path(options={}) exist_opts = { sort: params[:sort], scope: params[:scope], group: params[:group], + tag: params[:tag], + visibility_level: params[:visibility_level], } options = exist_opts.merge(options) @@ -22,32 +15,11 @@ module DashboardHelper path end - def assigned_entities_count(current_user, entity, scope = nil) - items = current_user.send('assigned_' + entity.pluralize) - get_count(items, scope) + def assigned_issues_dashboard_path + issues_dashboard_path(assignee_id: current_user.id) end - def authored_entities_count(current_user, entity, scope = nil) - items = current_user.send(entity.pluralize) - get_count(items, scope) - end - - def authorized_entities_count(current_user, entity, scope = nil) - items = entity.classify.constantize - get_count(items, scope, true, current_user) - end - - protected - - def get_count(items, scope, get_authorized = false, current_user = nil) - items = items.opened - if scope.kind_of?(Group) - items = items.of_group(scope) - elsif scope.kind_of?(Project) - items = items.of_projects(scope) - elsif get_authorized - items = items.of_projects(current_user.authorized_projects) - end - items.count + def assigned_mrs_dashboard_path + merge_requests_dashboard_path(assignee_id: current_user.id) end end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 0d2a914f60..5855bcabe5 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -33,18 +33,6 @@ module GroupsHelper title end - def group_filter_path(entity, options={}) - exist_opts = { - status: params[:status] - } - - options = exist_opts.merge(options) - - path = request.path - path << "?#{options.to_param}" - path - end - def group_settings_page? if current_controller?('groups') current_action?('edit') || current_action?('projects') diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb new file mode 100644 index 0000000000..6847123d2d --- /dev/null +++ b/app/helpers/milestones_helper.rb @@ -0,0 +1,9 @@ +module MilestonesHelper + def milestones_filter_path(opts = {}) + if @project + project_milestones_path(@project, opts) + elsif @group + group_milestones_path(@group, opts) + end + end +end diff --git a/app/helpers/profile_helper.rb b/app/helpers/profile_helper.rb index 6480fd3886..9e37e44732 100644 --- a/app/helpers/profile_helper.rb +++ b/app/helpers/profile_helper.rb @@ -14,6 +14,6 @@ module ProfileHelper end def show_profile_remove_tab? - gitlab_config.signup_enabled + signup_enabled? end end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb new file mode 100644 index 0000000000..47fa6f1071 --- /dev/null +++ b/app/models/application_setting.rb @@ -0,0 +1,15 @@ +class ApplicationSetting < ActiveRecord::Base + def self.current + ApplicationSetting.last + end + + def self.create_from_defaults + create( + default_projects_limit: Settings.gitlab['default_projects_limit'], + signup_enabled: Settings.gitlab['signup_enabled'], + signin_enabled: Settings.gitlab['signin_enabled'], + gravatar_enabled: Settings.gravatar['enabled'], + sign_in_text: Settings.extra['sign_in_text'], + ) + end +end diff --git a/app/models/event.rb b/app/models/event.rb index 65b4c2edfe..2a6c690ab9 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -174,7 +174,7 @@ class Event < ActiveRecord::Base def valid_push? data[:ref] && ref_name.present? - rescue => ex + rescue false end diff --git a/app/models/group.rb b/app/models/group.rb index 95708d8f48..a0430d102a 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -24,7 +24,7 @@ class Group < Namespace has_many :ldap_group_links, foreign_key: 'group_id', dependent: :destroy validate :avatar_type, if: ->(user) { user.avatar_changed? } - validates :avatar, file_size: { maximum: 100.kilobytes.to_i } + validates :avatar, file_size: { maximum: 200.kilobytes.to_i } mount_uploader :avatar, AttachmentUploader diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 8479d4aecf..d1d522be19 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -48,7 +48,7 @@ class WebHook < ActiveRecord::Base verify: false, basic_auth: auth) end - rescue SocketError, Errno::ECONNREFUSED => e + rescue SocketError, Errno::ECONNREFUSED, Net::OpenTimeout => e logger.error("WebHook Error => #{e}") false end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 197f609f7b..c1e255607b 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -192,7 +192,9 @@ class MergeRequest < ActiveRecord::Base end def automerge!(current_user, commit_message = nil) - MergeRequests::AutoMergeService.new.execute(self, current_user, commit_message) + MergeRequests::AutoMergeService. + new(target_project, current_user). + execute(self, commit_message) end def open? diff --git a/app/models/notification.rb b/app/models/notification.rb index b0f8ed6a4e..1395274173 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -6,12 +6,13 @@ class Notification N_PARTICIPATING = 1 N_WATCH = 2 N_GLOBAL = 3 + N_MENTION = 4 attr_accessor :target class << self def notification_levels - [N_DISABLED, N_PARTICIPATING, N_WATCH] + [N_DISABLED, N_PARTICIPATING, N_WATCH, N_MENTION] end def options_with_labels @@ -19,12 +20,13 @@ class Notification disabled: N_DISABLED, participating: N_PARTICIPATING, watch: N_WATCH, + mention: N_MENTION, global: N_GLOBAL } end def project_notification_levels - [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL] + [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL, N_MENTION] end end @@ -48,6 +50,10 @@ class Notification target.notification_level == N_GLOBAL end + def mention? + target.notification_level == N_MENTION + end + def level target.notification_level end diff --git a/app/models/project.rb b/app/models/project.rb index 297fabadef..26b6651491 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -69,6 +69,7 @@ class Project < ActiveRecord::Base has_one :jenkins_service, dependent: :destroy has_one :buildbox_service, dependent: :destroy has_one :bamboo_service, dependent: :destroy + has_one :teamcity_service, dependent: :destroy has_one :pushover_service, dependent: :destroy has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" @@ -321,7 +322,8 @@ class Project < ActiveRecord::Base end def available_services_names - %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack jira jenkins pushover buildbox bamboo) + %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla + emails_on_push gemnasium slack pushover buildbox bamboo teamcity jira jenkins) end def gitlab_ci? @@ -485,6 +487,10 @@ class Project < ActiveRecord::Base protected_branches_names.include?(branch_name) end + def developers_can_push_to_protected_branch?(branch_name) + protected_branches.any? { |pb| pb.name == branch_name && pb.developers_can_push } + end + def forked? !(forked_project_link.nil? || forked_project_link.forked_from_project.nil?) end diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index a848d74044..6ef4b210c5 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -35,7 +35,7 @@ class HipchatService < Service { type: 'text', name: 'token', placeholder: '' }, { type: 'text', name: 'room', placeholder: '' }, { type: 'text', name: 'server', - placeholder: 'Leave blank for default. https://chat.hipchat.com' } + placeholder: 'Leave blank for default. https://hipchat.example.com' } ] end @@ -47,7 +47,7 @@ class HipchatService < Service def gate options = { api_version: 'v2' } - options[:server_url] = server unless server.nil? + options[:server_url] = server unless server.blank? @gate ||= HipChat::Client.new(token, options) end diff --git a/app/models/project_services/slack_message.rb b/app/models/project_services/slack_message.rb index 28204e5ea6..d0ddb1f162 100644 --- a/app/models/project_services/slack_message.rb +++ b/app/models/project_services/slack_message.rb @@ -1,6 +1,14 @@ require 'slack-notifier' class SlackMessage + attr_reader :after + attr_reader :before + attr_reader :commits + attr_reader :project_name + attr_reader :project_url + attr_reader :ref + attr_reader :username + def initialize(params) @after = params.fetch(:after) @before = params.fetch(:before) @@ -23,14 +31,6 @@ class SlackMessage private - attr_reader :after - attr_reader :before - attr_reader :commits - attr_reader :project_name - attr_reader :project_url - attr_reader :ref - attr_reader :username - def message if new_branch? new_branch_message diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb new file mode 100644 index 0000000000..52b5862e4d --- /dev/null +++ b/app/models/project_services/teamcity_service.rb @@ -0,0 +1,116 @@ +class TeamcityService < CiService + include HTTParty + + prop_accessor :teamcity_url, :build_type, :username, :password + + validates :teamcity_url, presence: true, + format: { with: URI::regexp }, if: :activated? + validates :build_type, presence: true, if: :activated? + validates :username, presence: true, + if: ->(service) { service.password? }, if: :activated? + validates :password, presence: true, + if: ->(service) { service.username? }, if: :activated? + + attr_accessor :response + + after_save :compose_service_hook, if: :activated? + + def compose_service_hook + hook = service_hook || build_service_hook + hook.save + end + + def title + 'JetBrains TeamCity CI' + end + + def description + 'A continuous integration and build server' + end + + def help + 'The build configuration in Teamcity must use the build format '\ + 'number %build.vcs.number% '\ + 'you will also want to configure monitoring of all branches so merge '\ + 'requests build, that setting is in the vsc root advanced settings.' + end + + def to_param + 'teamcity' + end + + def fields + [ + { type: 'text', name: 'teamcity_url', + placeholder: 'TeamCity root URL like https://teamcity.example.com' }, + { type: 'text', name: 'build_type', + placeholder: 'Build configuration ID' }, + { type: 'text', name: 'username', + placeholder: 'A user with permissions to trigger a manual build' }, + { type: 'password', name: 'password' }, + ] + end + + def build_info(sha) + url = URI.parse("#{teamcity_url}/httpAuth/app/rest/builds/"\ + "branch:unspecified:any,number:#{sha}") + auth = { + username: username, + password: password, + } + @response = HTTParty.get("#{url}", verify: false, basic_auth: auth) + end + + def build_page(sha) + build_info(sha) if @response.nil? || !@response.code + + if @response.code != 200 + # If actual build link can't be determined, + # send user to build summary page. + "#{teamcity_url}/viewLog.html?buildTypeId=#{build_type}" + else + # If actual build link is available, go to build result page. + built_id = @response['build']['id'] + "#{teamcity_url}/viewLog.html?buildId=#{built_id}"\ + "&buildTypeId=#{build_type}" + end + end + + def commit_status(sha) + build_info(sha) if @response.nil? || !@response.code + return :error unless @response.code == 200 || @response.code == 404 + + status = if @response.code == 404 + 'Pending' + else + @response['build']['status'] + end + + if status.include?('SUCCESS') + 'success' + elsif status.include?('FAILURE') + 'failed' + elsif status.include?('Pending') + 'pending' + else + :error + end + end + + def execute(data) + auth = { + username: username, + password: password, + } + + branch = data[:ref] + + self.class.post("#{teamcity_url}/httpAuth/app/rest/buildQueue", + body: ""\ + ""\ + '', + headers: { 'Content-type' => 'application/xml' }, + basic_auth: auth + ) + end +end diff --git a/app/models/user.rb b/app/models/user.rb index d9e98cc844..b7cb0f4671 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -51,14 +51,15 @@ require 'file_size_validator' class User < ActiveRecord::Base include Gitlab::ConfigHelper - extend Gitlab::ConfigHelper include TokenAuthenticatable + extend Gitlab::ConfigHelper + extend Gitlab::CurrentSettings default_value_for :admin, false default_value_for :can_create_group, gitlab_config.default_can_create_group default_value_for :can_create_team, false default_value_for :hide_no_ssh_key, false - default_value_for :projects_limit, gitlab_config.default_projects_limit + default_value_for :projects_limit, current_application_settings.default_projects_limit default_value_for :theme_id, gitlab_config.default_theme devise :database_authenticatable, :lockable, :async, diff --git a/app/services/base_service.rb b/app/services/base_service.rb index 0d46eeaa18..bb51795df7 100644 --- a/app/services/base_service.rb +++ b/app/services/base_service.rb @@ -1,4 +1,6 @@ class BaseService + include Gitlab::CurrentSettings + attr_accessor :project, :current_user, :params def initialize(project, user, params = {}) @@ -29,6 +31,10 @@ class BaseService SystemHooksService.new end + def current_application_settings + ApplicationSetting.current + end + private def error(message) diff --git a/app/services/gravatar_service.rb b/app/services/gravatar_service.rb index a69c7c7837..4bee0c26a6 100644 --- a/app/services/gravatar_service.rb +++ b/app/services/gravatar_service.rb @@ -1,6 +1,8 @@ class GravatarService + include Gitlab::CurrentSettings + def execute(email, size = nil) - if gravatar_config.enabled && email.present? + if current_application_settings.gravatar_enabled? && email.present? size = 40 if size.nil? || size <= 0 sprintf gravatar_url, diff --git a/app/services/merge_requests/auto_merge_service.rb b/app/services/merge_requests/auto_merge_service.rb index 20b88d1510..b5d90a74e1 100644 --- a/app/services/merge_requests/auto_merge_service.rb +++ b/app/services/merge_requests/auto_merge_service.rb @@ -5,15 +5,16 @@ module MergeRequests # mark merge request as merged and execute all hooks and notifications # Called when you do merge via GitLab UI class AutoMergeService < BaseMergeService - def execute(merge_request, current_user, commit_message) + def execute(merge_request, commit_message) merge_request.lock_mr if Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message) merge_request.merge - notification.merge_mr(merge_request, current_user) + notification_service.merge_mr(merge_request, current_user) create_merge_event(merge_request, current_user) - execute_project_hooks(merge_request) + create_note(merge_request) + execute_hooks(merge_request) true else diff --git a/app/services/merge_requests/base_merge_service.rb b/app/services/merge_requests/base_merge_service.rb index 700a21ca01..9579573adf 100644 --- a/app/services/merge_requests/base_merge_service.rb +++ b/app/services/merge_requests/base_merge_service.rb @@ -1,21 +1,10 @@ module MergeRequests - class BaseMergeService + class BaseMergeService < MergeRequests::BaseService private - def notification - NotificationService.new - end - def create_merge_event(merge_request, current_user) EventCreateService.new.merge_mr(merge_request, current_user) end - - def execute_project_hooks(merge_request) - if merge_request.project - hook_data = merge_request.to_hook_data(current_user) - merge_request.project.execute_hooks(hook_data, :merge_request_hooks) - end - end end end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 82242a8296..4dc2b206f8 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -13,7 +13,7 @@ module MergeRequests merge_request.target_branch ||= merge_request.target_project.default_branch unless merge_request.target_branch && merge_request.source_branch - return build_failed(merge_request, "You must select source and target branches") + return build_failed(merge_request, nil) end # Generate suggested MR title based on source branch name @@ -62,7 +62,7 @@ module MergeRequests end def build_failed(merge_request, message) - merge_request.errors.add(:base, message) + merge_request.errors.add(:base, message) unless message.nil? merge_request.compare_commits = [] merge_request.can_be_created = false merge_request diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index 680766140b..5de7247d61 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -6,12 +6,13 @@ module MergeRequests # Called when you do merge via command line and push code # to target branch class MergeService < BaseMergeService - def execute(merge_request, current_user, commit_message) + def execute(merge_request, commit_message) merge_request.merge - notification.merge_mr(merge_request, current_user) + notification_service.merge_mr(merge_request, current_user) create_merge_event(merge_request, current_user) - execute_project_hooks(merge_request) + create_note(merge_request) + execute_hooks(merge_request) true rescue diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index baf0936cc3..a6705de61f 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -32,7 +32,9 @@ module MergeRequests merge_requests.uniq.select(&:source_project).each do |merge_request| - MergeRequests::MergeService.new.execute(merge_request, @current_user, nil) + MergeRequests::MergeService. + new(merge_request.target_project, @current_user). + execute(merge_request, nil) end end diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb new file mode 100644 index 0000000000..63431b8247 --- /dev/null +++ b/app/services/notes/update_service.rb @@ -0,0 +1,25 @@ +module Notes + class UpdateService < BaseService + def execute + note = project.notes.find(params[:note_id]) + note.note = params[:note] + if note.save + notification_service.new_note(note) + + # Skip system notes, like status changes and cross-references. + unless note.system + event_service.leave_note(note, note.author) + + # Create a cross-reference note if this Note contains GFM that + # names an issue, merge request, or commit. + note.references.each do |mentioned| + Note.create_cross_reference_note(mentioned, note.noteable, + note.author, note.project) + end + end + end + + note + end + end +end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index d1aadd741e..fb8f812dad 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -144,6 +144,10 @@ class NotificationService # Merge project watchers recipients = recipients.concat(project_watchers(note.project)).compact.uniq + # Reject mention users unless mentioned in comment + recipients = reject_mention_users(recipients - note.mentioned_users, note.project) + recipients = recipients + note.mentioned_users + # Reject mutes users recipients = reject_muted_users(recipients, note.project) @@ -285,13 +289,39 @@ class NotificationService end end + # Remove users with notification level 'Mentioned' + def reject_mention_users(users, project = nil) + users = users.to_a.compact.uniq + + users.reject do |user| + next user.notification.mention? unless project + + tm = project.project_members.find_by(user_id: user.id) + + if !tm && project.group + tm = project.group.group_members.find_by(user_id: user.id) + end + + # reject users who globally set mention notification and has no membership + next user.notification.mention? unless tm + + # reject users who set mention notification in project + next true if tm.notification.mention? + + # reject users who have N_MENTION in project and disabled in global settings + tm.notification.global? && user.notification.mention? + end + end + def new_resource_email(target, project, method) if target.respond_to?(:participants) recipients = target.participants else recipients = [] end + recipients = reject_muted_users(recipients, project) + recipients = reject_mention_users(recipients, project) recipients = recipients.concat(project_watchers(project)).uniq recipients.delete(target.author) @@ -302,6 +332,7 @@ class NotificationService def close_resource_email(target, project, current_user, method) recipients = reject_muted_users([target.author, target.assignee], project) + recipients = reject_mention_users(recipients, project) recipients = recipients.concat(project_watchers(project)).uniq recipients.delete(current_user) @@ -320,6 +351,7 @@ class NotificationService # reject users with disabled notifications recipients = reject_muted_users(recipients, project) + recipients = reject_mention_users(recipients, project) # Reject me from recipients if I reassign an item recipients.delete(current_user) @@ -331,6 +363,7 @@ class NotificationService def reopen_resource_email(target, project, current_user, method, status) recipients = reject_muted_users([target.author, target.assignee], project) + recipients = reject_mention_users(recipients, project) recipients = recipients.concat(project_watchers(project)).uniq recipients.delete(current_user) diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml new file mode 100644 index 0000000000..5ca9585e9a --- /dev/null +++ b/app/views/admin/application_settings/_form.html.haml @@ -0,0 +1,33 @@ += form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| + - if @application_setting.errors.any? + #error_explanation + .alert.alert-danger + - @application_setting.errors.full_messages.each do |msg| + %p= msg + + %fieldset + %legend Features + .form-group + = f.label :signup_enabled, class: 'control-label' + .col-sm-10 + = f.check_box :signup_enabled, class: 'checkbox' + .form-group + = f.label :signin_enabled, class: 'control-label' + .col-sm-10 + = f.check_box :signin_enabled, class: 'checkbox' + .form-group + = f.label :gravatar_enabled, class: 'control-label' + .col-sm-10 + = f.check_box :gravatar_enabled, class: 'checkbox' + %fieldset + %legend Misc + .form-group + = f.label :default_projects_limit, class: 'control-label' + .col-sm-10 + = f.number_field :default_projects_limit, class: 'form-control' + .form-group + = f.label :sign_in_text, class: 'control-label' + .col-sm-10 + = f.text_area :sign_in_text, class: 'form-control' + .form-actions + = f.submit 'Save', class: 'btn btn-primary' diff --git a/app/views/admin/application_settings/show.html.haml b/app/views/admin/application_settings/show.html.haml new file mode 100644 index 0000000000..39b66647a5 --- /dev/null +++ b/app/views/admin/application_settings/show.html.haml @@ -0,0 +1,3 @@ +%h3.page-title Application settings +%hr += render 'form' diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 7427cea7e8..c6badeb4bd 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -104,7 +104,7 @@ %p Sign up %span.light.pull-right - = boolean_to_icon gitlab_config.signup_enabled + = boolean_to_icon signup_enabled? %p LDAP %span.light.pull-right @@ -112,7 +112,7 @@ %p Gravatar %span.light.pull-right - = boolean_to_icon Gitlab.config.gravatar.enabled + = boolean_to_icon gravatar_enabled? %p OmniAuth %span.light.pull-right diff --git a/app/views/admin/keys/show.html.haml b/app/views/admin/keys/show.html.haml new file mode 100644 index 0000000000..5b23027b3a --- /dev/null +++ b/app/views/admin/keys/show.html.haml @@ -0,0 +1 @@ += render "profiles/keys/key_details", admin: true diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 29717aedd8..88e71aa170 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -20,6 +20,8 @@ %a{"data-toggle" => "tab", href: "#groups"} Groups %li %a{"data-toggle" => "tab", href: "#projects"} Projects + %li + %a{"data-toggle" => "tab", href: "#ssh-keys"} SSH keys .tab-content #account.tab-pane.active @@ -217,3 +219,5 @@ - if tm.respond_to? :project = link_to project_team_member_path(project, @user), data: { confirm: remove_from_project_team_message(project, @user) }, remote: true, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from project' do %i.fa.fa-times + #ssh-keys.tab-pane + = render 'profiles/keys/key_table', admin: true diff --git a/app/views/dashboard/_projects_filter.html.haml b/app/views/dashboard/_projects_filter.html.haml index b65e882e69..0e990ccfab 100644 --- a/app/views/dashboard/_projects_filter.html.haml +++ b/app/views/dashboard/_projects_filter.html.haml @@ -1,55 +1,100 @@ -%fieldset - %ul.nav.nav-pills.nav-stacked - = nav_tab :scope, nil do - = link_to projects_dashboard_filter_path(scope: nil) do - All - %span.pull-right - = current_user.authorized_projects.count - = nav_tab :scope, 'personal' do - = link_to projects_dashboard_filter_path(scope: 'personal') do - Personal - %span.pull-right - = current_user.personal_projects.count - = nav_tab :scope, 'joined' do - = link_to projects_dashboard_filter_path(scope: 'joined') do - Joined - %span.pull-right - = current_user.authorized_projects.joined(current_user).count - = nav_tab :scope, 'owned' do - = link_to projects_dashboard_filter_path(scope: 'owned') do - Owned - %span.pull-right - = current_user.owned_projects.count +.dash-projects-filters.append-bottom-20 + .pull-left.append-right-20 + %ul.nav.nav-pills.nav-compact + = nav_tab :scope, nil do + = link_to projects_dashboard_filter_path(scope: nil) do + All + = nav_tab :scope, 'personal' do + = link_to projects_dashboard_filter_path(scope: 'personal') do + Personal + = nav_tab :scope, 'joined' do + = link_to projects_dashboard_filter_path(scope: 'joined') do + Joined + = nav_tab :scope, 'owned' do + = link_to projects_dashboard_filter_path(scope: 'owned') do + Owned -%fieldset - %legend Visibility - %ul.nav.nav-pills.nav-stacked.nav-small.visibility-filter - - Gitlab::VisibilityLevel.values.each do |level| - %li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' } - = link_to projects_dashboard_filter_path(visibility_level: level) do - = visibility_level_icon(level) - = visibility_level_label(level) + .dropdown.inline.append-right-10 + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %i.fa.fa-globe + %span.light Visibility: + - if params[:visibility_level].present? + = visibility_level_label(params[:visibility_level].to_i) + - else + Any + %b.caret + %ul.dropdown-menu + %li + = link_to projects_dashboard_filter_path(visibility_level: nil) do + Any + - Gitlab::VisibilityLevel.values.each do |level| + %li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' } + = link_to projects_dashboard_filter_path(visibility_level: level) do + = visibility_level_icon(level) + = visibility_level_label(level) -- if @groups.present? - %fieldset - %legend Groups - %ul.nav.nav-pills.nav-stacked.nav-small - - @groups.each do |group| - %li{ class: (group.name == params[:group]) ? 'active' : 'light' } - = link_to projects_dashboard_filter_path(group: group.name) do - %i.fa.fa-folder-o - = group.name - %small.pull-right - = group.projects.count + - if @groups.present? + .dropdown.inline.append-right-10 + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %i.fa.fa-group + %span.light Group: + - if params[:group].present? + = Group.find_by(name: params[:group]).name + - else + Any + %b.caret + %ul.dropdown-menu + %li + = link_to projects_dashboard_filter_path(group: nil) do + Any + - @groups.each do |group| + %li{ class: (group.name == params[:group]) ? 'active' : 'light' } + = link_to projects_dashboard_filter_path(group: group.name) do + = group.name + %small.pull-right + = group.projects.count -- if @tags.present? - %fieldset - %legend Tags - %ul.nav.nav-pills.nav-stacked.nav-small - - @tags.each do |tag| - %li{ class: (tag.name == params[:tag]) ? 'active' : 'light' } - = link_to projects_dashboard_filter_path(scope: params[:scope], tag: tag.name) do - %i.fa.fa-tag - = tag.name + - if @tags.present? + .dropdown.inline.append-right-10 + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %i.fa.fa-tags + %span.light Tags: + - if params[:tag].present? + = params[:tag] + - else + Any + %b.caret + %ul.dropdown-menu + %li + = link_to projects_dashboard_filter_path(tag: nil) do + Any + + - @tags.each do |tag| + %li{ class: (tag.name == params[:tag]) ? 'active' : 'light' } + = link_to projects_dashboard_filter_path(tag: tag.name) do + %i.fa.fa-tag + = tag.name + + .pull-right + .dropdown.inline + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %span.light sort: + - if @sort.present? + = @sort.humanize + - else + Name + %b.caret + %ul.dropdown-menu + %li + = link_to projects_dashboard_filter_path(sort: nil) do + Name + = link_to projects_dashboard_filter_path(sort: 'newest') do + = sort_title_recently_created + = link_to projects_dashboard_filter_path(sort: 'oldest') do + = sort_title_oldest_created + = link_to projects_dashboard_filter_path(sort: 'recently_updated') do + = sort_title_recently_updated + = link_to projects_dashboard_filter_path(sort: 'last_updated') do + = sort_title_oldest_updated diff --git a/app/views/dashboard/_zero_authorized_projects.html.haml b/app/views/dashboard/_zero_authorized_projects.html.haml index 5d133cd828..f78ce69ef9 100644 --- a/app/views/dashboard/_zero_authorized_projects.html.haml +++ b/app/views/dashboard/_zero_authorized_projects.html.haml @@ -4,7 +4,7 @@ %div .dashboard-intro-icon %i.fa.fa-bookmark-o - %div + .dashboard-intro-text %p.slead You don't have access to any projects right now. %br @@ -24,7 +24,7 @@ %div .dashboard-intro-icon %i.fa.fa-users - %div + .dashboard-intro-text %p.slead You can create a group for several dependent projects. %br @@ -38,7 +38,7 @@ %div .dashboard-intro-icon %i.fa.fa-globe - %div + .dashboard-intro-text %p.slead There are %strong= @publicish_project_count diff --git a/app/views/dashboard/issues.atom.builder b/app/views/dashboard/issues.atom.builder index 6638131022..72e9e361dc 100644 --- a/app/views/dashboard/issues.atom.builder +++ b/app/views/dashboard/issues.atom.builder @@ -1,5 +1,5 @@ xml.instruct! -xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do +xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.title "#{current_user.name} issues" xml.link href: issues_dashboard_url(:atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml" xml.link href: issues_dashboard_url(private_token: current_user.private_token), rel: "alternate", type: "text/html" diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml index b880acf124..944441669e 100644 --- a/app/views/dashboard/projects.html.haml +++ b/app/views/dashboard/projects.html.haml @@ -1,76 +1,54 @@ %h3.page-title My Projects -.pull-right - .dropdown.inline - %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"} - %span.light sort: - - if @sort.present? - = @sort.humanize - - else - Name - %b.caret - %ul.dropdown-menu - %li - = link_to projects_dashboard_filter_path(sort: nil) do - Name - = link_to projects_dashboard_filter_path(sort: 'newest') do - = sort_title_recently_created - = link_to projects_dashboard_filter_path(sort: 'oldest') do - = sort_title_oldest_created - = link_to projects_dashboard_filter_path(sort: 'recently_updated') do - = sort_title_recently_updated - = link_to projects_dashboard_filter_path(sort: 'last_updated') do - = sort_title_oldest_updated %p.light All projects you have access to are listed here. Public projects are not included here unless you are a member %hr -.row - .col-md-3.hidden-sm.hidden-xs.side-filters - = render "projects_filter" - .col-md-9 - %ul.bordered-list.my-projects.top-list - - @projects.each do |project| - %li.my-project-row - %h4.project-title - .project-access-icon - = visibility_level_icon(project.visibility_level) - = link_to project_path(project), class: dom_class(project) do - = project.name_with_namespace +.side-filters + = render "projects_filter" +.dash-projects + %ul.bordered-list.my-projects.top-list + - @projects.each do |project| + %li.my-project-row + %h4.project-title + .project-access-icon + = visibility_level_icon(project.visibility_level) + = link_to project_path(project), class: dom_class(project) do + = project.name_with_namespace - - if project.forked_from_project -   - %small - %i.fa.fa-code-fork - Forked from: - = link_to project.forked_from_project.name_with_namespace, project_path(project.forked_from_project) + - if project.forked_from_project +   + %small + %i.fa.fa-code-fork + Forked from: + = link_to project.forked_from_project.name_with_namespace, project_path(project.forked_from_project) - - if current_user.can_leave_project?(project) - .pull-right - = link_to leave_project_team_members_path(project), data: { confirm: "Leave project?"}, method: :delete, remote: true, class: "btn-tiny btn remove-row", title: 'Leave project' do - %i.fa.fa-sign-out - Leave - - .project-info + - if current_user.can_leave_project?(project) .pull-right - - if project.archived? - %span.label - %i.fa.fa-archive - Archived - - project.tags.each do |tag| - %span.label.label-info - %i.fa.fa-tag - = tag.name - - if project.description.present? - %p= truncate project.description, length: 100 - .last-activity - %span.light Last activity: - %span.date= project_last_activity(project) + = link_to leave_project_team_members_path(project), data: { confirm: "Leave project?"}, method: :delete, remote: true, class: "btn-tiny btn remove-row", title: 'Leave project' do + %i.fa.fa-sign-out + Leave + + .project-info + .pull-right + - if project.archived? + %span.label + %i.fa.fa-archive + Archived + - project.tags.each do |tag| + %span.label.label-info + %i.fa.fa-tag + = tag.name + - if project.description.present? + %p= truncate project.description, length: 100 + .last-activity + %span.light Last activity: + %span.date= project_last_activity(project) - - if @projects.blank? - %li - .nothing-here-block There are no projects here. - .bottom - = paginate @projects, theme: "gitlab" + - if @projects.blank? + %li + .nothing-here-block There are no projects here. + .bottom + = paginate @projects, theme: "gitlab" diff --git a/app/views/dashboard/show.atom.builder b/app/views/dashboard/show.atom.builder index 70ac66f801..da631ecb33 100644 --- a/app/views/dashboard/show.atom.builder +++ b/app/views/dashboard/show.atom.builder @@ -1,5 +1,5 @@ xml.instruct! -xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do +xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.title "Dashboard feed#{" - #{current_user.name}" if current_user.name.present?}" xml.link href: dashboard_url(:atom), rel: "self", type: "application/atom+xml" xml.link href: dashboard_url, rel: "alternate", type: "text/html" diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml old mode 100755 new mode 100644 diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml old mode 100755 new mode 100644 diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index e819847e5e..ab9085f0ba 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -2,7 +2,7 @@ = f.text_field :login, class: "form-control top", placeholder: "Username or Email", autofocus: "autofocus" = f.password_field :password, class: "form-control bottom", placeholder: "Password" - if devise_mapping.rememberable? - .clearfix.append-bottom-10 + .remember-me %label.checkbox.remember_me{for: "user_remember_me"} = f.check_box :remember_me %span Remember me diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml index bf8a593c25..e986989a72 100644 --- a/app/views/devise/sessions/_new_ldap.html.haml +++ b/app/views/devise/sessions/_new_ldap.html.haml @@ -1,5 +1,4 @@ = form_tag(user_omniauth_callback_path(provider), id: 'new_ldap_user' ) do = text_field_tag :username, nil, {class: "form-control top", placeholder: "LDAP Login", autofocus: "autofocus"} = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} - %br/ = button_tag "LDAP Sign in", class: "btn-save btn" diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index d5e60d53e7..6d8415613d 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -1,51 +1,15 @@ -.login-box - .login-heading - %h3 Sign in - .login-body - - if standard_login_form_only? - %ul.nav.nav-tabs - - if ldap_enabled? - - @ldap_servers.each_with_index do |server, i| - %li{class: (:active if i.zero?)} - = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' - - if kerberos_enabled? - %li{class: (:active unless ldap_enabled?)} - = link_to "Kerberos", "#tab-kerberos", 'data-toggle' => 'tab' - - if gitlab_config.signin_enabled - %li - = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' - .tab-content - - if ldap_enabled? - - @ldap_servers.each_with_index do |server, i| - %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero?)} - = render 'devise/sessions/new_ldap', provider: server['provider_name'] - - if kerberos_enabled? - %div#tab-kerberos.tab-pane{class: (:active unless ldap_enabled?)} - = render 'devise/sessions/new_kerberos', provider: :kerberos - - if gitlab_config.signin_enabled - %div#tab-signin.tab-pane - = render 'devise/sessions/new_base' +%div + = render 'devise/shared/signin_box' - - elsif gitlab_config.signin_enabled - = render 'devise/sessions/new_base' - - else - %div - No authentication methods configured. + - if Gitlab.config.omniauth.enabled && devise_mapping.omniauthable? + .prepend-top-20 + = render 'devise/shared/oauth_box' - = render 'devise/sessions/oauth_providers' if Gitlab.config.omniauth.enabled && devise_mapping.omniauthable? + - if signup_enabled? + .prepend-top-20 + = render 'devise/shared/signup_box' - .login-footer - - if gitlab_config.signup_enabled - %p - %span.light - Don't have an account? - %strong - = link_to "Sign up", new_registration_path(resource_name) - - %p - %span.light Did not receive confirmation email? - = link_to "Send again", new_confirmation_path(resource_name) - - - if extra_config.has_key?('sign_in_text') - %hr - = markdown(extra_config.sign_in_text) +.clearfix.prepend-top-20 + %p + %span.light Did not receive confirmation email? + = link_to "Send again", new_confirmation_path(resource_name) diff --git a/app/views/devise/sessions/_oauth_providers.html.haml b/app/views/devise/shared/_oauth_box.html.haml similarity index 77% rename from app/views/devise/sessions/_oauth_providers.html.haml rename to app/views/devise/shared/_oauth_box.html.haml index 8d6aaefb9f..c2e1373de3 100644 --- a/app/views/devise/sessions/_oauth_providers.html.haml +++ b/app/views/devise/shared/_oauth_box.html.haml @@ -1,7 +1,7 @@ - providers = additional_providers - if providers.present? - .bs-callout.bs-callout-info{:'data-no-turbolink' => 'data-no-turbolink'} - %span Sign in with:   + .login-box{:'data-no-turbolink' => 'data-no-turbolink'} + %span Sign in with   - providers.each do |provider| %span - if default_providers.include?(provider) diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml new file mode 100644 index 0000000000..7e61232802 --- /dev/null +++ b/app/views/devise/shared/_signin_box.html.haml @@ -0,0 +1,31 @@ +.login-box + .login-heading + %h3 Sign in + .login-body + - if ldap_enabled? + %ul.nav.nav-tabs + - @ldap_servers.each_with_index do |server, i| + %li{class: (:active if i.zero?)} + = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' + - if kerberos_enabled? + %li{class: (:active unless ldap_enabled?)} + = link_to "Kerberos", "#tab-kerberos", 'data-toggle' => 'tab' + - if signin_enabled? + %li + = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' + .tab-content + - @ldap_servers.each_with_index do |server, i| + %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero?)} + = render 'devise/sessions/new_ldap', provider: server['provider_name'] + - if kerberos_enabled? + %div#tab-kerberos.tab-pane{class: (:active unless ldap_enabled?)} + = render 'devise/sessions/new_kerberos', provider: :kerberos + - if signin_enabled? + %div#tab-signin.tab-pane + = render 'devise/sessions/new_base' + + - elsif signin_enabled? + = render 'devise/sessions/new_base' + - else + %div + No authentication methods configured. diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml new file mode 100644 index 0000000000..5709c66128 --- /dev/null +++ b/app/views/devise/shared/_signup_box.html.haml @@ -0,0 +1,17 @@ +.login-box + .login-heading + %h3 Sign up + .login-body + = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| + .devise-errors + = devise_error_messages! + %div + = f.text_field :name, class: "form-control top", placeholder: "Name", required: true + %div + = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true + %div + = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true + .form-group#password-strength + = f.password_field :password, class: "form-control bottom", id: "user_password_sign_up", placeholder: "Password", required: true + %div + = f.submit "Sign up", class: "btn-create btn" diff --git a/app/views/groups/_filter.html.haml b/app/views/groups/_filter.html.haml deleted file mode 100644 index 393be3f1d1..0000000000 --- a/app/views/groups/_filter.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -= form_tag group_filter_path(entity), method: 'get' do - %fieldset - %ul.nav.nav-pills.nav-stacked - %li{class: ("active" if (params[:status] == 'active' || !params[:status]))} - = link_to group_filter_path(entity, status: 'active') do - Active - %li{class: ("active" if params[:status] == 'closed')} - = link_to group_filter_path(entity, status: 'closed') do - Closed - %li{class: ("active" if params[:status] == 'all')} - = link_to group_filter_path(entity, status: 'all') do - All diff --git a/app/views/groups/_settings_nav.html.haml b/app/views/groups/_settings_nav.html.haml index 8a2bd37de7..195ca88bed 100644 --- a/app/views/groups/_settings_nav.html.haml +++ b/app/views/groups/_settings_nav.html.haml @@ -2,18 +2,21 @@ = nav_link(path: 'groups#edit') do = link_to edit_group_path(@group) do %i.fa.fa-pencil-square-o - Group + %span + Group = nav_link(path: 'groups#projects') do = link_to projects_group_path(@group) do %i.fa.fa-folder - Projects + %span + Projects - if ldap_enabled? = nav_link(controller: :ldap_group_links) do = link_to group_ldap_group_links_path(@group) do %i.fa.fa-exchange - LDAP Groups + %span + LDAP Groups = nav_link(controller: :audit_events) do = link_to group_audit_events_path(@group) do %i.fa.fa-file-text-o - Audit Events - + %span + Audit Events diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index 2727525f07..7f0b2832ca 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -9,42 +9,38 @@ %hr -.row - .fixed.sidebar-expand-button.hidden-lg.hidden-md - %i.fa.fa-list.fa-2x - .col-md-3.responsive-side - = render 'groups/filter', entity: 'milestone' - .col-md-9 - .panel.panel-default - %ul.well-list - - if @group_milestones.blank? - %li - .nothing-here-block No milestones to show - - else - - @group_milestones.each do |milestone| - %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } - .pull-right - - if can?(current_user, :manage_group, @group) - - if milestone.closed? - = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-small btn-grouped btn-reopen" - - else - = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-small btn-close" - %h4 - = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title) += render 'shared/milestones_filter' +.milestones + .panel.panel-default + %ul.well-list + - if @group_milestones.blank? + %li + .nothing-here-block No milestones to show + - else + - @group_milestones.each do |milestone| + %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } + .pull-right + - if can?(current_user, :manage_group, @group) + - if milestone.closed? + = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-small btn-grouped btn-reopen" + - else + = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-small btn-close" + %h4 + = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title) + %div %div - %div - = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do - = pluralize milestone.issue_count, 'Issue' -   - = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do - = pluralize milestone.merge_requests_count, 'Merge Request' -   - %span.light #{milestone.percent_complete}% complete - .progress.progress-info - .progress-bar{style: "width: #{milestone.percent_complete}%;"} - %div - %br - - milestone.projects.each do |project| - %span.label.label-default - = project.name - = paginate @group_milestones, theme: "gitlab" + = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do + = pluralize milestone.issue_count, 'Issue' +   + = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do + = pluralize milestone.merge_requests_count, 'Merge Request' +   + %span.light #{milestone.percent_complete}% complete + .progress.progress-info + .progress-bar{style: "width: #{milestone.percent_complete}%;"} + %div + %br + - milestone.projects.each do |project| + %span.label.label-default + = project.name + = paginate @group_milestones, theme: "gitlab" diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml index 411d1822be..7bcac56c37 100644 --- a/app/views/groups/milestones/show.html.haml +++ b/app/views/groups/milestones/show.html.haml @@ -1,4 +1,9 @@ -%h3.page-title +%h4.page-title + .issue-box{ class: "issue-box-#{@group_milestone.closed? ? 'closed' : 'open'}" } + - if @group_milestone.closed? + Closed + - else + Open Milestone #{@group_milestone.title} .pull-right - if can?(current_user, :manage_group, @group) @@ -7,46 +12,41 @@ - else = link_to 'Reopen Milestone', group_milestone_path(@group, @group_milestone.safe_title, title: @group_milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-small btn-grouped btn-reopen" +%hr - if (@group_milestone.total_items_count == @group_milestone.closed_items_count) && @group_milestone.active? .alert.alert-success %span All issues for this milestone are closed. You may close the milestone now. -.back-link - = link_to group_milestones_path(@group) do - ← To milestones list - -.issue-box{ class: "issue-box-#{@group_milestone.closed? ? 'closed' : 'open'}" } - .state.clearfix - .state-label - - if @group_milestone.closed? - Closed - - else - Open - - %h4.title - = gfm escape_once(@group_milestone.title) - - .description - - @group_milestone.milestones.each do |milestone| - %hr - %h4 - = link_to "#{milestone.project.name} - #{milestone.title}", project_milestone_path(milestone.project, milestone) - %span.pull-right= milestone.expires_at +.description +%table.table + %thead + %tr + %th Project + %th Open issues + %th State + %th Due date + - @group_milestone.milestones.each do |milestone| + %tr + %td + = link_to "#{milestone.project.name}", project_milestone_path(milestone.project, milestone) + %td + = milestone.issues.opened.count + %td - if milestone.closed? - %span.label.label-danger #{milestone.state} - = preserve do - - if milestone.description.present? - = milestone.description + Closed + - else + Open + %td + = milestone.expires_at - .context - %p - Progress: - #{@group_milestone.closed_items_count} closed - – - #{@group_milestone.open_items_count} open - - .progress.progress-info - .progress-bar{style: "width: #{@group_milestone.percent_complete}%;"} +.context + %p.lead + Progress: + #{@group_milestone.closed_items_count} closed + – + #{@group_milestone.open_items_count} open + .progress.progress-info + .progress-bar{style: "width: #{@group_milestone.percent_complete}%;"} %ul.nav.nav-tabs %li.active diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder index e765ea8338..c78bd1bd26 100644 --- a/app/views/groups/show.atom.builder +++ b/app/views/groups/show.atom.builder @@ -1,5 +1,5 @@ xml.instruct! -xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do +xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.title "Group feed - #{@group.name}" xml.link href: group_path(@group, :atom), rel: "self", type: "application/atom+xml" xml.link href: group_path(@group), rel: "alternate", type: "text/html" diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml index e0e6f7f7fb..82e2107087 100644 --- a/app/views/layouts/_head_panel.html.haml +++ b/app/views/layouts/_head_panel.html.haml @@ -45,3 +45,5 @@ %li.hidden-xs = link_to current_user, class: "profile-pic", id: 'profile-pic' do = image_tag avatar_icon(current_user.email, 26), alt: 'User activity' + += render 'shared/outdated_browser' diff --git a/app/views/layouts/_public_head_panel.html.haml b/app/views/layouts/_public_head_panel.html.haml index 9bfc14d16c..1d5bbb2aad 100644 --- a/app/views/layouts/_public_head_panel.html.haml +++ b/app/views/layouts/_public_head_panel.html.haml @@ -12,11 +12,13 @@ %span.sr-only Toggle navigation %i.fa.fa-bars - .pull-right.hidden-xs - = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-new' + - unless current_controller?('sessions') + .pull-right.hidden-xs + = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-new' - .navbar-collapse.collapse - %ul.nav.navbar-nav - %li.visible-xs - = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes') + .navbar-collapse.collapse + %ul.nav.navbar-nav + %li.visible-xs + = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes') += render 'shared/outdated_browser' diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index 06de03eada..857ebd9b8d 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -1,35 +1,33 @@ !!! 5 %html{ lang: "en"} = render "layouts/head" - %body.ui_basic.login-page - .container + %body.ui_mars.login-page.application + = render "layouts/broadcast" + = render "layouts/public_head_panel", title: '' + .container.navless-container .content - .login-title - %h1= brand_title - %hr - .container - .content - = render "layouts/flash" - .row - .col-md-7.brand-holder - - if brand_item - .brand-image - = brand_image - .brand_text - = brand_text - - else - .brand-image.default-brand-image.hidden-sm.hidden-xs - = image_tag 'brand_logo.png' - .brand_text.hidden-xs - %h2 Open source software to collaborate on code - - %p.lead - Manage git repositories with fine grained access controls that keep your code secure. - Perform code reviews and enhance collaboration with merge requests. - Each project can also have an issue tracker and a wiki. - - .col-md-5 + - unless redirect_from_root? + = render "layouts/flash" + .row.prepend-top-20 + .col-sm-5.pull-right = yield + .col-sm-7.brand-holder.pull-left + %h1 + = brand_title + - if brand_item + = brand_image + = brand_text + - else + %h3 Open source software to collaborate on code + + %p + Manage git repositories with fine grained access controls that keep your code secure. + Perform code reviews and enhance collaboration with merge requests. + Each project can also have an issue tracker and a wiki. + + - if extra_sign_in_text.present? + = markdown(extra_sign_in_text) + %hr .container .footer-links diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml index c3ecab8988..e036456112 100644 --- a/app/views/layouts/nav/_admin.html.haml +++ b/app/views/layouts/nav/_admin.html.haml @@ -44,3 +44,8 @@ %i.fa.fa-image %span Appearance + = nav_link(controller: :application_settings) do + = link_to admin_application_settings_path do + %i.fa.fa-cogs + %span + Settings diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index da1976346d..a2eaa2d83c 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -10,13 +10,13 @@ %span Projects = nav_link(path: 'dashboard#issues') do - = link_to issues_dashboard_path, class: 'shortcuts-issues' do + = link_to assigned_issues_dashboard_path, class: 'shortcuts-issues' do %i.fa.fa-exclamation-circle %span Issues %span.count= current_user.assigned_issues.opened.count = nav_link(path: 'dashboard#merge_requests') do - = link_to merge_requests_dashboard_path, class: 'shortcuts-merge_requests' do + = link_to assigned_mrs_dashboard_path, class: 'shortcuts-merge_requests' do %i.fa.fa-tasks %span Merge Requests diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index 36b48a5d02..cc50b9b570 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -7,7 +7,8 @@ = nav_link(controller: :accounts) do = link_to profile_account_path do %i.fa.fa-gear - Account + %span + Account = nav_link(path: ['profiles#applications', 'applications#edit', 'applications#show', 'applications#new']) do = link_to applications_profile_path do %i.fa.fa-cloud diff --git a/app/views/layouts/project_settings.html.haml b/app/views/layouts/project_settings.html.haml index 810fb4e200..0f20bf38bf 100644 --- a/app/views/layouts/project_settings.html.haml +++ b/app/views/layouts/project_settings.html.haml @@ -5,8 +5,5 @@ = render "layouts/broadcast" = render "layouts/head_panel", title: project_title(@project) = render "layouts/init_auto_complete" - - if can?(current_user, :download_code, @project) - = render 'shared/no_ssh' - - @project_settings_nav = true = render 'layouts/page', sidebar: 'layouts/nav/project' diff --git a/app/views/layouts/projects.html.haml b/app/views/layouts/projects.html.haml index b4b1bcf241..d4ee53db55 100644 --- a/app/views/layouts/projects.html.haml +++ b/app/views/layouts/projects.html.haml @@ -5,6 +5,4 @@ = render "layouts/broadcast" = render "layouts/head_panel", title: project_title(@project) = render "layouts/init_auto_complete" - - if can?(current_user, :download_code, @project) - = render 'shared/no_ssh' = render 'layouts/page', sidebar: 'layouts/nav/project' diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml index 3cf50bf082..d678147ec5 100644 --- a/app/views/notify/repository_push_email.html.haml +++ b/app/views/notify/repository_push_email.html.haml @@ -6,7 +6,9 @@ - @commits.each do |commit| %li %strong #{link_to commit.short_id, project_commit_url(@project, commit)} - %span by #{commit.author_name} + %div + %span by #{commit.author_name} + %i at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")} %pre #{commit.safe_message} %h4 Changes: diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml index e52c55ba32..141e18e45e 100644 --- a/app/views/profiles/keys/_key.html.haml +++ b/app/views/profiles/keys/_key.html.haml @@ -1,10 +1,13 @@ -%li - = link_to profile_key_path(key) do - %strong= key.title - %span - (#{key.fingerprint}) - %span.cgray - added #{time_ago_with_tooltip(key.created_at)} - - - unless key.is_a? LDAPKey - = link_to 'Remove', profile_key_path(key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-small btn-remove delete-key pull-right" +%tr + %td + = link_to path_to_key(key, is_admin) do + %strong= key.title + %td + %span + (#{key.fingerprint}) + %td + %span.cgray + added #{time_ago_with_tooltip(key.created_at)} + %td + - unless key.is_a? LDAPKey + = link_to 'Remove', path_to_key(key, is_admin), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-small btn-remove delete-key pull-right" diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml new file mode 100644 index 0000000000..5e81091919 --- /dev/null +++ b/app/views/profiles/keys/_key_details.html.haml @@ -0,0 +1,23 @@ +- is_admin = defined?(admin) ? true : false +.row + .col-md-4 + .panel.panel-default + .panel-heading + SSH Key + %ul.well-list + %li + %span.light Title: + %strong= @key.title + %li + %span.light Created on: + %strong= @key.created_at.stamp("Aug 21, 2011") + + .col-md-8 + %p + %span.light Fingerprint: + %strong= @key.fingerprint + %pre.well-pre + = @key.key + .pull-right + - unless @key.is_a? LDAPKey + = link_to 'Remove', path_to_key(@key, is_admin), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key" diff --git a/app/views/profiles/keys/_key_table.html.haml b/app/views/profiles/keys/_key_table.html.haml new file mode 100644 index 0000000000..ef0075aad3 --- /dev/null +++ b/app/views/profiles/keys/_key_table.html.haml @@ -0,0 +1,19 @@ +- is_admin = defined?(admin) ? true : false +.panel.panel-default + - if @keys.any? + %table.table + %thead.panel-heading + %tr + %th Title + %th Fingerprint + %th Added at + %th + %tbody + - @keys.each do |key| + = render 'profiles/keys/key', key: key, is_admin: is_admin + - else + .nothing-here-block + - if is_admin + User has no ssh keys + - else + There are no SSH keys with access to your account. diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml index 6240276550..809953960b 100644 --- a/app/views/profiles/keys/index.html.haml +++ b/app/views/profiles/keys/index.html.haml @@ -1,5 +1,5 @@ %h3.page-title - My SSH keys + My SSH keys (#{@keys.count}) .pull-right = link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new" %p.light @@ -9,14 +9,4 @@ = link_to "generate it", help_page_path("ssh", "ssh") %hr - -.panel.panel-default - .panel-heading - SSH Keys (#{@keys.count}) - %ul.well-list#keys-table - = render partial: "key", collection: @keys - - if @keys.blank? - %li - .nothing-here-block There are no SSH keys with access to your account. - - += render 'key_table' diff --git a/app/views/profiles/keys/show.html.haml b/app/views/profiles/keys/show.html.haml index 74cfb64c0a..cfd5329896 100644 --- a/app/views/profiles/keys/show.html.haml +++ b/app/views/profiles/keys/show.html.haml @@ -1,23 +1 @@ -.row - .col-md-4 - .panel.panel-default - .panel-heading - SSH Key - %ul.well-list - %li - %span.light Title: - %strong= @key.title - %li - %span.light Created on: - %strong= @key.created_at.stamp("Aug 21, 2011") - - .col-md-8 - %p - %span.light Fingerprint: - %strong= @key.fingerprint - %pre.well-pre - = @key.key - -.pull-right - - unless @key.is_a? LDAPKey - = link_to 'Remove', profile_key_path(@key), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key" += render "key_details" diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index a044fad8fa..96fe91b9b2 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -15,6 +15,13 @@ Disabled %p You will not get any notifications via email + .radio + = label_tag nil, class: '' do + = radio_button_tag :notification_level, Notification::N_MENTION, @notification.mention?, class: 'trigger-submit' + .level-title + Mention + %p You will receive notifications only for comments where you was @mentioned + .radio = label_tag nil, class: '' do = radio_button_tag :notification_level, Notification::N_PARTICIPATING, @notification.participating?, class: 'trigger-submit' diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml index b02f52a5af..9e2e214b3e 100644 --- a/app/views/projects/_issuable_form.html.haml +++ b/app/views/projects/_issuable_form.html.haml @@ -52,10 +52,11 @@ - else %span.light No open milestones available.   - = link_to 'Create new milestone', new_project_milestone_path(issuable.project), target: :blank + - if can? current_user, :admin_milestone, issuable.project + = link_to 'Create new milestone', new_project_milestone_path(issuable.project), target: :blank .form-group = f.label :label_ids, class: 'control-label' do - %i.icon-tag + %i.fa.fa-tag Labels .col-sm-10 - if issuable.project.labels.any? @@ -64,9 +65,15 @@ - else %span.light No labels yet.   - = link_to 'Create new label', new_project_label_path(issuable.project), target: :blank + - if can? current_user, :admin_label, issuable.project + = link_to 'Create new label', new_project_label_path(issuable.project), target: :blank .form-actions + - if !issuable.project.empty_repo? && contribution_guide_url(issuable.project) && !issuable.persisted? + %p + Please review the + %strong #{link_to 'guidelines for contribution', contribution_guide_url(issuable.project)} + to this repository. - if issuable.new_record? = f.submit "Submit new #{issuable.class.model_name.human.downcase}", class: 'btn btn-create' - else diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml index d57659065a..f279e3c37c 100644 --- a/app/views/projects/commits/_commits.html.haml +++ b/app/views/projects/commits/_commits.html.haml @@ -7,5 +7,5 @@ %p= pluralize(commits.count, 'commit') .col-md-10 %ul.bordered-list - = render commits, project: @project + = render commits, project: project %hr.lists-separator diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index 5717c24c27..b80639763c 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -11,11 +11,9 @@ %ul.breadcrumb.repo-breadcrumb = commits_breadcrumbs - %li.active - commits %div{id: dom_id(@project)} - #commits-list= render "commits" + #commits-list= render "commits", project: @project .clear = spinner diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 23e7691b32..0c5f2ad1f3 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -4,7 +4,7 @@ .diff-file{id: "diff-#{i}", data: {blob_diff_path: blob_diff_path }} .diff-header{id: "file-path-#{hexdigest(diff_file.new_path || diff_file.old_path)}"} - if diff_file.deleted_file - %span= diff_file.old_path + %span="#{diff_file.old_path} deleted" .diff-btn-group - if @commit.parent_ids.present? diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml index 64a28d8da4..2a7b44955c 100644 --- a/app/views/projects/issues/_form.html.haml +++ b/app/views/projects/issues/_form.html.haml @@ -1,12 +1,6 @@ %div.issue-form-holder %h3.page-title= @issue.new_record? ? "New Issue" : "Edit Issue ##{@issue.iid}" %hr - - if @repository.exists? && !@repository.empty? && @repository.contribution_guide && !@issue.persisted? - - contribution_guide_url = project_blob_path(@project, tree_join(@repository.root_ref, @repository.contribution_guide.name)) - .row - .col-sm-10.col-sm-offset-2 - .alert.alert-info - = "Please review the #{link_to "guidelines for contribution", contribution_guide_url} to this repository.".html_safe = form_for [@project, @issue], html: { class: 'form-horizontal issue-form gfm-form' } do |f| = render 'projects/issuable_form', f: f, issuable: @issue diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 76813e688b..ac374532ff 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -9,74 +9,103 @@ %span.pull-right = link_to 'Change branches', new_project_merge_request_path(@project) -= form_for [@project, @merge_request], html: { class: "merge-request-form gfm-form" } do |f| - .panel.panel-default - - .panel-body - .form-group - .light - = f.label :title do - Title * - = f.text_field :title, class: "form-control input-lg js-gfm-input", maxlength: 255, rows: 5, required: true - .form-group - .light - = f.label :description, "Description" += form_for [@project, @merge_request], html: { class: "merge-request-form form-horizontal gfm-form" } do |f| + .merge-request-form-info + .form-group + = f.label :title, class: 'control-label' do + %strong Title * + .col-sm-10 + = f.text_field :title, maxlength: 255, autofocus: true, class: 'form-control pad js-gfm-input', required: true + .form-group.issuable-description + = f.label :description, 'Description', class: 'control-label' + .col-sm-10 = render layout: 'projects/md_preview' do - = render 'projects/zen', f: f, attr: :description, - classes: 'description form-control' - .clearfix.hint - .pull-left Description is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}. - .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. - .error-alert - .form-group - .issue-assignee - = f.label :assignee_id do - %i.fa.fa-user - Assign to - %div - = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id, project_id: @merge_request.target_project_id) -   - = link_to 'Assign to me', '#', class: 'btn assign-to-me-link' - .form-group - .issue-milestone - = f.label :milestone_id do - %i.fa.fa-clock-o - Milestone - %div= f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2'}) - .form-group - = f.label :label_ids do - %i.fa.fa-tag - Labels - %div - = f.collection_select :label_ids, @merge_request.target_project.labels.all, :id, :name, { selected: @merge_request.label_ids }, multiple: true, class: 'select2' + = render 'projects/zen', f: f, attr: :description, classes: 'description form-control' - .panel-footer + .col-sm-12-hint + .pull-left + Parsed with + #{link_to 'Gitlab Flavored Markdown', help_page_path('markdown', 'markdown'), target: '_blank'}. + .pull-right + Attach images (JPG, PNG, GIF) by dragging & dropping + or #{link_to 'selecting them', '#', class: 'markdown-selector'}. + + .clearfix + .error-alert + %hr + .form-group + .issue-assignee + = f.label :assignee_id, class: 'control-label' do + %i.fa.fa-user + Assign to + .col-sm-10 + = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id, project_id: @merge_request.target_project_id) +   + = link_to 'Assign to me', '#', class: 'btn assign-to-me-link' + .form-group + .issue-milestone + = f.label :milestone_id, class: 'control-label' do + %i.fa.fa-clock-o + Milestone + .col-sm-10 + - if milestone_options(@merge_request).present? + = f.select(:milestone_id, milestone_options(@merge_request), {include_blank: 'Select milestone'}, {class: 'select2'}) + - else + %span.light No open milestones available. +   + - if can? current_user, :admin_milestone, @merge_request.target_project + = link_to 'Create new milestone', new_project_milestone_path(@merge_request.target_project), target: :blank + .form-group + = f.label :label_ids, class: 'control-label' do + %i.fa.fa-tag + Labels + .col-sm-10 + - if @merge_request.target_project.labels.any? + = f.collection_select :label_ids, @merge_request.target_project.labels.all, :id, :name, {selected: @merge_request.label_ids}, multiple: true, class: 'select2' + - else + %span.light No labels yet. +   + - if can? current_user, :admin_label, @merge_request.target_project + = link_to 'Create new label', new_project_label_path(@merge_request.target_project), target: :blank + + .form-actions - if contribution_guide_url(@target_project) %p Please review the - %strong #{link_to "guidelines for contribution", contribution_guide_url(@target_project)} + %strong #{link_to 'guidelines for contribution', contribution_guide_url(@target_project)} to this repository. = f.hidden_field :source_project_id + = f.hidden_field :source_branch = f.hidden_field :target_project_id = f.hidden_field :target_branch - = f.hidden_field :source_branch - = f.submit 'Submit merge request', class: "btn btn-create" + = f.submit 'Submit merge request', class: 'btn btn-create' -.mr-compare - = render "projects/commits/commit_list" - - %h4 Changes - - if @diffs.present? - = render "projects/diffs/diffs", diffs: @diffs, project: @project - - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE - .bs-callout.bs-callout-danger - %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits. - %p To preserve performance the line changes are not shown. - - else - .bs-callout.bs-callout-danger - %h4 This comparison includes huge diff. - %p To preserve performance the line changes are not shown. +.mr-compare.merge-request + %ul.nav.nav-tabs.merge-request-tabs + %li.commits-tab{data: {action: 'commits'}} + = link_to url_for(params) do + %i.fa.fa-history + Commits + %span.badge= @commits.size + %li.diffs-tab{data: {action: 'diffs'}} + = link_to url_for(params) do + %i.fa.fa-list-alt + Changes + %span.badge= @diffs.size + .commits.tab-content + = render "projects/commits/commits", project: @project + .diffs.tab-content + - if @diffs.present? + = render "projects/diffs/diffs", diffs: @diffs, project: @project + - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE + .bs-callout.bs-callout-danger + %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits. + %p To preserve performance the line changes are not shown. + - else + .bs-callout.bs-callout-danger + %h4 This comparison includes a huge diff. + %p To preserve performance the line changes are not shown. :javascript $('.assign-to-me-link').on('click', function(e){ @@ -85,3 +114,9 @@ }); window.project_image_path_upload = "#{upload_image_project_path @project}"; + +:javascript + var merge_request + merge_request = new MergeRequest({ + action: 'commits' + }); diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 74ef819a7a..f8d2673335 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -42,7 +42,7 @@ %span.badge= @merge_request.mr_and_commit_notes.count %li.commits-tab{data: {action: 'commits'}} = link_to project_merge_request_path(@project, @merge_request), title: 'Commits' do - %i.fa.fa-database + %i.fa.fa-history Commits %span.badge= @commits.size %li.diffs-tab{data: {action: 'diffs'}} diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml index a658740387..3b7f283daf 100644 --- a/app/views/projects/merge_requests/show/_commits.html.haml +++ b/app/views/projects/merge_requests/show/_commits.html.haml @@ -1,30 +1 @@ -- if @commits.present? - .panel.panel-default - .panel-heading - %i.fa.fa-list - Commits (#{@commits.count}) - .commits.mr-commits - - if @commits.count > 8 - %ul.first-commits.well-list - - @commits.first(8).each do |commit| - = render "projects/commits/commit", commit: commit, project: @merge_request.source_project - %li.bottom - 8 of #{@commits.count} commits displayed. - %strong - %a.show-all-commits Click here to show all - - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE - %ul.all-commits.hide.well-list - - @commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE).each do |commit| - = render "projects/commits/inline_commit", commit: commit, project: @merge_request.source_project - %li - other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden to prevent performance issues. - - else - %ul.all-commits.hide.well-list - - @commits.each do |commit| - = render "projects/commits/inline_commit", commit: commit, project: @merge_request.source_project - - - else - %ul.well-list - - @commits.each do |commit| - = render "projects/commits/commit", commit: commit, project: @merge_request.source_project - += render "projects/commits/commits", project: @merge_request.source_project diff --git a/app/views/projects/merge_requests/show/_mr_accept.html.haml b/app/views/projects/merge_requests/show/_mr_accept.html.haml index 3e9acb72b4..ed86070116 100644 --- a/app/views/projects/merge_requests/show/_mr_accept.html.haml +++ b/app/views/projects/merge_requests/show/_mr_accept.html.haml @@ -13,30 +13,27 @@ .automerge_widget.can_be_merged.hide .clearfix = form_for [:automerge, @project, @merge_request], remote: true, method: :post do |f| - %h4 - You can accept this request automatically. - .accept-merge-holder.clearfix - .accept-group - .pull-left - = f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request" - - if can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && !@merge_request.for_fork? - .remove_branch_holder.pull-left - = label_tag :should_remove_source_branch, class: "checkbox" do - = check_box_tag :should_remove_source_branch - Remove source-branch - - if @merge_request.target_project.merge_requests_rebase_enabled && can_rebase?(@merge_request.target_project, @merge_request.target_branch) - .remove_branch_holder.pull-left - = label_tag :should_rebase, class: "checkbox" do - = check_box_tag :should_rebase - Rebase before merge - .js-toggle-container - %label - %i.fa.fa-edit - = link_to "modify merge commit message", "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message" - .js-toggle-content.hide - = render 'shared/commit_message_container', params: params, - text: @merge_request.merge_commit_message, - rows: 14, hint: true + .accept-merge-holder.clearfix.js-toggle-container + .accept-action + = f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request" + - if can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && !@merge_request.for_fork? + .accept-control + = label_tag :should_remove_source_branch, class: "checkbox" do + = check_box_tag :should_remove_source_branch + Remove source-branch + - if @merge_request.target_project.merge_requests_rebase_enabled && can_rebase?(@merge_request.target_project, @merge_request.target_branch) + .accept-control.remove_branch_holder + = label_tag :should_rebase, class: "checkbox" do + = check_box_tag :should_rebase + Rebase before merge + .accept-control + = link_to "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message" do + %i.fa.fa-edit + Modify commit message + .js-toggle-content.hide.prepend-top-20 + = render 'shared/commit_message_container', params: params, + text: @merge_request.merge_commit_message, + rows: 14, hint: true %hr .light diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml index 0db0b114d6..04a1b9243d 100644 --- a/app/views/projects/milestones/index.html.haml +++ b/app/views/projects/milestones/index.html.haml @@ -7,27 +7,15 @@ %i.fa.fa-plus New Milestone - .row - .fixed.sidebar-expand-button.hidden-lg.hidden-md.hidden-xs - %i.fa.fa-list.fa-2x - .col-md-3.responsive-side - %ul.nav.nav-pills.nav-stacked - %li{class: ("active" if (params[:f] == "active" || !params[:f]))} - = link_to project_milestones_path(@project, f: "active") do - Active - %li{class: ("active" if params[:f] == "closed")} - = link_to project_milestones_path(@project, f: "closed") do - Closed - %li{class: ("active" if params[:f] == "all")} - = link_to project_milestones_path(@project, f: "all") do - All - .col-md-9 - .panel.panel-default - %ul.well-list - = render @milestones += render 'shared/milestones_filter' - - if @milestones.blank? - %li - .nothing-here-block No milestones to show +.milestones + .panel.panel-default + %ul.well-list + = render @milestones - = paginate @milestones, theme: "gitlab" + - if @milestones.blank? + %li + .nothing-here-block No milestones to show + + = paginate @milestones, theme: "gitlab" diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index cd62e4811a..031b5a3189 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -1,5 +1,5 @@ = render "projects/issues_nav" -%h3.page-title +%h4.page-title .issue-box{ class: issue_box_class(@milestone) } - if @milestone.closed? Closed @@ -8,52 +8,44 @@ - else Open Milestone ##{@milestone.iid} - .pull-right.creator - %small= @milestone.expires_at + %small.creator + = @milestone.expires_at + .pull-right + - if can?(current_user, :admin_milestone, @project) + = link_to edit_project_milestone_path(@project, @milestone), class: "btn btn-grouped" do + %i.fa.fa-pencil-square-o + Edit + - if @milestone.active? + = link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-grouped" + - else + = link_to 'Reopen Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped" %hr - if @milestone.issues.any? && @milestone.can_be_closed? .alert.alert-success %span All issues for this milestone are closed. You may close milestone now. -.row - .col-sm-9 - %h3.issue-title - = gfm escape_once(@milestone.title) - %div - - if @milestone.description.present? - .description - .wiki - = preserve do - = markdown @milestone.description - %hr - .context - %p.lead - Progress: - #{@milestone.closed_items_count} closed - – - #{@milestone.open_items_count} open -   - %span.light #{@milestone.percent_complete}% complete - %span.pull-right= @milestone.expires_at - .progress.progress-info - .progress-bar{style: "width: #{@milestone.percent_complete}%;"} - - .col-sm-3 - %div - - if can?(current_user, :admin_milestone, @project) - = link_to edit_project_milestone_path(@project, @milestone), class: "btn btn-block" do - %i.fa.fa-pencil-square-o - Edit - - if @milestone.active? - = link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-block" - - else - = link_to 'Reopen Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-block" - = link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-block", title: "New Issue" do - %i.fa.fa-plus - New Issue - = link_to 'Browse Issues', project_issues_path(@milestone.project, milestone_id: @milestone.id), class: "btn edit-milestone-link btn-block" +%h3.issue-title + = gfm escape_once(@milestone.title) +%div + - if @milestone.description.present? + .description + .wiki + = preserve do + = markdown @milestone.description +%hr +.context + %p.lead + Progress: + #{@milestone.closed_items_count} closed + – + #{@milestone.open_items_count} open +   + %span.light #{@milestone.percent_complete}% complete + %span.pull-right= @milestone.expires_at + .progress.progress-info + .progress-bar{style: "width: #{@milestone.percent_complete}%;"} %ul.nav.nav-tabs @@ -71,6 +63,10 @@ %span.badge= @users.count .pull-right + = link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do + %i.fa.fa-plus + New Issue + = link_to 'Browse Issues', project_issues_path(@milestone.project, milestone_id: @milestone.id), class: "btn edit-milestone-link btn-grouped" .tab-content .tab-pane.active#tab-issues diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml new file mode 100644 index 0000000000..e422799f55 --- /dev/null +++ b/app/views/projects/protected_branches/_branches_list.html.haml @@ -0,0 +1,34 @@ +- unless @branches.empty? + %br + %h4 Already Protected: + %table.table.protected-branches-list + %thead + %tr.no-border + %th Branch + %th Developers can push + %th Last commit + %th + + %tbody + - @branches.each do |branch| + - @url = project_protected_branch_path(@project, branch) + %tr + %td + = link_to project_commits_path(@project, branch.name) do + %strong= branch.name + - if @project.root_ref?(branch.name) + %span.label.label-info default + %td + = check_box_tag "developers_can_push", branch.id, branch.developers_can_push, "data-url" => @url + %td + - if commit = branch.commit + = link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do + = commit.short_id + · + #{time_ago_with_tooltip(commit.committed_date)} + - else + (branch was removed from repository) + %td + .pull-right + - if can? current_user, :admin_project, @project + = link_to 'Unprotect', [@project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-small" diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml index 227a2f9a06..2164c874c7 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -22,29 +22,14 @@ = f.label :name, "Branch", class: 'control-label' .col-sm-10 = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: "Select branch"}, {class: "select2"}) + .form-group + = f.label :developers_can_push, class: 'control-label' do + Developers can push + .col-sm-10 + .checkbox + = f.check_box :developers_can_push + %span.descr Allow developers to push to this branch .form-actions = f.submit 'Protect', class: "btn-create btn" -- unless @branches.empty? - %h5 Already Protected: - %ul.bordered-list.protected-branches-list - - @branches.each do |branch| - %li - %h4 - = link_to project_commits_path(@project, branch.name) do - %strong= branch.name - - if @project.root_ref?(branch.name) - %span.label.label-info default - %span.label.label-success - %i.fa.fa-lock - .pull-right - - if can? current_user, :admin_project, @project - = link_to 'Unprotect', [@project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-small" += render 'branches_list' - - if commit = branch.commit - = link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do - = commit.short_id - %span.light - = gfm escape_once(truncate(commit.title, length: 40)) - #{time_ago_with_tooltip(commit.committed_date)} - - else - (branch was removed from repository) diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 9b06ebe95a..af6e4567c1 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -1,3 +1,6 @@ +- if current_user && can?(current_user, :download_code, @project) + = render 'shared/no_ssh' + = render "home_panel" - readme = @repository.readme diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index f37c086716..111484c831 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -19,13 +19,15 @@ %code [Link Title](page-slug) \. - .form-group + .form-group.wiki-content = f.label :content, class: 'control-label' .col-sm-10 - = render 'projects/zen', f: f, attr: :content, classes: 'description form-control' - .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 images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. + = render layout: 'projects/md_preview' do + = render 'projects/zen', f: f, attr: :content, classes: 'description form-control' + .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 images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. + .clearfix .error-alert .form-group diff --git a/app/views/shared/_milestones_filter.html.haml b/app/views/shared/_milestones_filter.html.haml new file mode 100644 index 0000000000..8c2fd16692 --- /dev/null +++ b/app/views/shared/_milestones_filter.html.haml @@ -0,0 +1,16 @@ +.fixed.sidebar-expand-button.hidden-lg.hidden-md + %i.fa.fa-list.fa-2x +.responsive-side.milestones-filters.append-bottom-10 + %ul.nav.nav-pills.nav-compact + %li{class: ("active" if params[:state].blank? || params[:state] == 'opened')} + = link_to milestones_filter_path(state: 'opened') do + %i.fa.fa-exclamation-circle + Open + %li{class: ("active" if params[:state] == 'closed')} + = link_to milestones_filter_path(state: 'closed') do + %i.fa.fa-check-circle + Closed + %li{class: ("active" if params[:state] == 'all')} + = link_to milestones_filter_path(state: 'all') do + %i.fa.fa-compass + All diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml index e70eb4d01b..8e6f802fd3 100644 --- a/app/views/shared/_no_ssh.html.haml +++ b/app/views/shared/_no_ssh.html.haml @@ -1,14 +1,8 @@ - if cookies[:hide_no_ssh_message].blank? && current_user.require_ssh_key? && !current_user.hide_no_ssh_key - .no-ssh-key-message - .container - You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_profile_key_path} to your profile - .pull-right.hidden-xs - = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'hide-no-ssh-message', remote: true - | - = link_to 'Remind later', '#', class: 'hide-no-ssh-message' - .links-xs.visible-xs - = link_to "Add key", new_profile_key_path - | - = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'hide-no-ssh-message', remote: true - | - = link_to 'Later', '#', class: 'hide-no-ssh-message' + .no-ssh-key-message.alert.alert-warning.hidden-xs + You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_profile_key_path} to your profile + + .pull-right + = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put + | + = link_to 'Remind later', '#', class: 'hide-no-ssh-message' diff --git a/app/views/shared/_outdated_browser.html.haml b/app/views/shared/_outdated_browser.html.haml new file mode 100644 index 0000000000..0eba1fe075 --- /dev/null +++ b/app/views/shared/_outdated_browser.html.haml @@ -0,0 +1,8 @@ +- if outdated_browser? + - link = "https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/requirements.md#supported-web-browsers" + .browser-alert + GitLab may not work properly because you are using an outdated web browser. + %br + Please install a + = link_to 'supported web browser', link + for a better experience. diff --git a/app/views/users/show.atom.builder b/app/views/users/show.atom.builder index b7216a8876..8fe30b2363 100644 --- a/app/views/users/show.atom.builder +++ b/app/views/users/show.atom.builder @@ -1,5 +1,5 @@ xml.instruct! -xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do +xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.title "Activity feed for #{@user.name}" xml.link href: user_url(@user, :atom), rel: "self", type: "application/atom+xml" xml.link href: user_url(@user), rel: "alternate", type: "text/html" diff --git a/app/workers/project_service_worker.rb b/app/workers/project_service_worker.rb index cc0a7f2566..64d39c4d3f 100644 --- a/app/workers/project_service_worker.rb +++ b/app/workers/project_service_worker.rb @@ -4,6 +4,7 @@ class ProjectServiceWorker sidekiq_options queue: :project_web_hook def perform(hook_id, data) + data = data.with_indifferent_access Service.find(hook_id).execute(data) end end diff --git a/app/workers/project_web_hook_worker.rb b/app/workers/project_web_hook_worker.rb index 9f9b9b1df5..73085c046b 100644 --- a/app/workers/project_web_hook_worker.rb +++ b/app/workers/project_web_hook_worker.rb @@ -4,6 +4,7 @@ class ProjectWebHookWorker sidekiq_options queue: :project_web_hook def perform(hook_id, data) - WebHook.find(hook_id).execute data + data = data.with_indifferent_access + WebHook.find(hook_id).execute(data) end end diff --git a/config/routes.rb b/config/routes.rb index c24b28dde8..64bf07cb1d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -81,6 +81,7 @@ Gitlab::Application.routes.draw do # namespace :admin do resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do + resources :keys, only: [:show, :destroy] member do put :team_update put :block @@ -118,6 +119,7 @@ Gitlab::Application.routes.draw do end end + resource :application_settings, only: [:show, :update] root to: "dashboard#index" end @@ -241,7 +243,8 @@ Gitlab::Application.routes.draw do end end - match "/compare/:from...:to" => "compare#show", as: "compare", via: [:get, :post], constraints: {from: /.+/, to: /.+/} + get '/compare/:from...:to' => 'compare#show', :as => 'compare', + :constraints => {from: /.+/, to: /.+/} resources :snippets, constraints: {id: /\d+/} do member do @@ -285,7 +288,7 @@ Gitlab::Application.routes.draw do 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 :protected_branches, only: [:index, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } + resources :protected_branches, only: [:index, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } resources :refs, only: [] do collection do diff --git a/db/migrate/20141226080412_add_developers_can_push_to_protected_branches.rb b/db/migrate/20141226080412_add_developers_can_push_to_protected_branches.rb new file mode 100644 index 0000000000..70e7272f7f --- /dev/null +++ b/db/migrate/20141226080412_add_developers_can_push_to_protected_branches.rb @@ -0,0 +1,5 @@ +class AddDevelopersCanPushToProtectedBranches < ActiveRecord::Migration + def change + add_column :protected_branches, :developers_can_push, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20150108073740_create_application_settings.rb b/db/migrate/20150108073740_create_application_settings.rb new file mode 100644 index 0000000000..651e35fdf7 --- /dev/null +++ b/db/migrate/20150108073740_create_application_settings.rb @@ -0,0 +1,13 @@ +class CreateApplicationSettings < ActiveRecord::Migration + def change + create_table :application_settings do |t| + t.integer :default_projects_limit + t.boolean :signup_enabled + t.boolean :signin_enabled + t.boolean :gravatar_enabled + t.text :sign_in_text + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 0c27304467..df0714226c 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: 20141230100055) do +ActiveRecord::Schema.define(version: 20150108073740) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -41,6 +41,16 @@ ActiveRecord::Schema.define(version: 20141230100055) do add_index "audit_events", ["entity_id", "entity_type"], name: "index_audit_events_on_entity_id_and_entity_type", using: :btree add_index "audit_events", ["type"], name: "index_audit_events_on_type", using: :btree + create_table "application_settings", force: true do |t| + t.integer "default_projects_limit" + t.boolean "signup_enabled" + t.boolean "signin_enabled" + t.boolean "gravatar_enabled" + t.text "sign_in_text" + t.datetime "created_at" + t.datetime "updated_at" + end + create_table "broadcast_messages", force: true do |t| t.text "message", null: false t.datetime "starts_at" @@ -380,10 +390,11 @@ ActiveRecord::Schema.define(version: 20141230100055) do add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree create_table "protected_branches", force: true do |t| - t.integer "project_id", null: false - t.string "name", null: false + t.integer "project_id", null: false + t.string "name", null: false t.datetime "created_at" t.datetime "updated_at" + t.boolean "developers_can_push", default: false, null: false end add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree diff --git a/doc/api/notes.md b/doc/api/notes.md index b5256ac803..c22e493562 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -78,6 +78,21 @@ Parameters: - `issue_id` (required) - The ID of an issue - `body` (required) - The content of a note +### Modify existing issue note + +Modify existing note of an issue. + +``` +PUT /projects/:id/issues/:issue_id/notes/:note_id +``` + +Parameters: + +- `id` (required) - The ID of a project +- `issue_id` (required) - The ID of an issue +- `note_id` (required) - The ID of a note +- `body` (required) - The content of a note + ## Snippets ### List all snippet notes @@ -137,7 +152,22 @@ POST /projects/:id/snippets/:snippet_id/notes Parameters: - `id` (required) - The ID of a project -- `snippet_id` (required) - The ID of an snippet +- `snippet_id` (required) - The ID of a snippet +- `body` (required) - The content of a note + +### Modify existing snippet note + +Modify existing note of a snippet. + +``` +PUT /projects/:id/snippets/:snippet_id/notes/:note_id +``` + +Parameters: + +- `id` (required) - The ID of a project +- `snippet_id` (required) - The ID of a snippet +- `note_id` (required) - The ID of a note - `body` (required) - The content of a note ## Merge Requests @@ -199,3 +229,18 @@ Parameters: - `id` (required) - The ID of a project - `merge_request_id` (required) - The ID of a merge request - `body` (required) - The content of a note + +### Modify existing merge request note + +Modify existing note of a merge request. + +``` +PUT /projects/:id/merge_requests/:merge_request_id/notes/:note_id +``` + +Parameters: + +- `id` (required) - The ID of a project +- `merge_request_id` (required) - The ID of a merge request +- `note_id` (required) - The ID of a note +- `body` (required) - The content of a note diff --git a/doc/api/projects.md b/doc/api/projects.md index 880946bd99..38193e22a3 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -11,6 +11,8 @@ GET /projects Parameters: - `archived` (optional) - if passed, limit by archived status +- `order_by` (optional) - Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields +- `sort` (optional) - Return requests sorted in `asc` or `desc` order ```json [ @@ -628,9 +630,11 @@ GET /projects/search/:query Parameters: -- query (required) - A string contained in the project name -- per_page (optional) - number of projects to return per page -- page (optional) - the page to retrieve +- `query` (required) - A string contained in the project name +- `per_page` (optional) - number of projects to return per page +- `page` (optional) - the page to retrieve +- `order_by` (optional) - Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields +- `sort` (optional) - Return requests sorted in `asc` or `desc` order ## Git Hooks (EE only) diff --git a/doc/operations/README.md b/doc/operations/README.md index 31b1b583b0..f1456c6c8e 100644 --- a/doc/operations/README.md +++ b/doc/operations/README.md @@ -1,3 +1,4 @@ # GitLab operations - [Sidekiq MemoryKiller](sidekiq_memory_killer.md) +- [Cleaning up Redis sessions](cleaning_up_redis_sessions.md) diff --git a/doc/operations/cleaning_up_redis_sessions.md b/doc/operations/cleaning_up_redis_sessions.md new file mode 100644 index 0000000000..93521e976d --- /dev/null +++ b/doc/operations/cleaning_up_redis_sessions.md @@ -0,0 +1,52 @@ +# Cleaning up stale Redis sessions + +Since version 6.2, GitLab stores web user sessions as key-value pairs in Redis. +Prior to GitLab 7.3, user sessions did not automatically expire from Redis. If +you have been running a large GitLab server (thousands of users) since before +GitLab 7.3 we recommend cleaning up stale sessions to compact the Redis +database after you upgrade to GitLab 7.3. You can also perform a cleanup while +still running GitLab 7.2 or older, but in that case new stale sessions will +start building up again after you clean up. + +In GitLab versions prior to 7.3.0, the session keys in Redis are 16-byte +hexadecimal values such as '976aa289e2189b17d7ef525a6702ace9'. Starting with +GitLab 7.3.0, the keys are +prefixed with 'session:gitlab:', so they would look like +'session:gitlab:976aa289e2189b17d7ef525a6702ace9'. Below we describe how to +remove the keys in the old format. + +First we define a shell function with the proper Redis connection details. + +``` +rcli() { + # This example works for Omnibus installations of GitLab 7.3 or newer. For an + # installation from source you will have to change the socket path and the + # path to redis-cli. + sudo /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket "$@" +} + +# test the new shell function; the response should be PONG +rcli ping +``` + +Now we do a search to see if there are any session keys in the old format for +us to clean up. + +``` +# returns the number of old-format session keys in Redis +rcli keys '*' | grep '^[a-f0-9]\{32\}$' | wc -l +``` + +If the number is larger than zero, you can proceed to expire the keys from +Redis. If the number is zero there is nothing to clean up. + +``` +# Tell Redis to expire each matched key after 600 seconds. +rcli keys '*' | grep '^[a-f0-9]\{32\}$' | awk '{ print "expire", $0, 600 }' | rcli +# This will print '(integer) 1' for each key that gets expired. +``` + +Over the next 15 minutes (10 minutes expiry time plus 5 minutes Redis +background save interval) your Redis database will be compacted. If you are +still using GitLab 7.2, users who are not clicking around in GitLab during the +10 minute expiry window will be signed out of GitLab. diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md index e21384d21d..c9928e11b2 100644 --- a/doc/permissions/permissions.md +++ b/doc/permissions/permissions.md @@ -8,7 +8,6 @@ If a user is a GitLab administrator they receive all permissions. ## Project - | Action | Guest | Reporter | Developer | Master | Owner | |---------------------------------------|---------|------------|-------------|----------|--------| | Create new issue | ✓ | ✓ | ✓ | ✓ | ✓ | @@ -29,6 +28,7 @@ If a user is a GitLab administrator they receive all permissions. | Add new team members | | | | ✓ | ✓ | | Push to protected branches | | | | ✓ | ✓ | | Enable/disable branch protection | | | | ✓ | ✓ | +| Turn on/off prot. branch push for devs| | | | ✓ | ✓ | | Rewrite/remove git tags | | | | ✓ | ✓ | | Edit project | | | | ✓ | ✓ | | Add deploy keys to project | | | | ✓ | ✓ | @@ -37,7 +37,7 @@ If a user is a GitLab administrator they receive all permissions. | Transfer project to another namespace | | | | | ✓ | | Remove project | | | | | ✓ | | Force push to protected branches | | | | | | -| Remove protected branches | | | | | | +| Remove protected branches | | | | | | ## Group @@ -49,4 +49,4 @@ If a user is a GitLab administrator they receive all permissions. | Manage group members | | | | | ✓ | | Remove group | | | | | ✓ | -Any user can remove himself from a group, unless he is the last Owner of the group. +Any user can remove themselves from a group, unless they are the last Owner of the group. diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md index 20a69a211d..ec46af5fe3 100644 --- a/doc/project_services/project_services.md +++ b/doc/project_services/project_services.md @@ -16,3 +16,4 @@ __Project integrations with external services for continuous integration and mor - PivotalTracker - Pushover - Slack +- TeamCity \ No newline at end of file diff --git a/doc/workflow/README.md b/doc/workflow/README.md index da09fb0a34..421c95e723 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -1,3 +1,5 @@ +# Workflow + - [Workflow](workflow.md) - [Project Features](project_features.md) - [Authorization for merge requests](authorization_for_merge_requests.md) @@ -7,3 +9,4 @@ - [GitLab Flow](gitlab_flow.md) - [Notifications](notifications.md) - [Migrating from SVN to GitLab](migrating_from_svn.md) +- [Protected branches](protected_branches.md) diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md index 267451e213..78aff66a63 100644 --- a/doc/workflow/gitlab_flow.md +++ b/doc/workflow/gitlab_flow.md @@ -1,6 +1,6 @@ ![GitLab Flow](gitlab_flow.png) -# Introduction +## Introduction Version management with git makes branching and merging much easier than older versioning systems such as SVN. This allows a wide variety of branching strategies and workflows. @@ -29,9 +29,9 @@ People have a hard time figuring out which branch they should develop on or depl Frequently the reaction to this problem is to adopt a standardized pattern such as [git flow](http://nvie.com/posts/a-successful-git-branching-model/) and [GitHub flow](http://scottchacon.com/2011/08/31/github-flow.html) We think there is still room for improvement and will detail a set of practices we call GitLab flow. -# Git flow and its problems +## Git flow and its problems -[![Git Flow timeline by Vincent Driessen, used with persmission](gitdashflow.png) +[![Git Flow timeline by Vincent Driessen, used with permission](gitdashflow.png) Git flow was one of the first proposals to use git branches and it has gotten a lot of attention. It advocates a master branch and a separate develop branch as well as supporting branches for features, releases and hotfixes. @@ -50,7 +50,7 @@ Frequently developers make a mistake and for example changes are only merged int The root cause of these errors is that git flow is too complex for most of the use cases. And doing releases doesn't automatically mean also doing hotfixes. -# GitHub flow as a simpler alternative +## GitHub flow as a simpler alternative ![Master branch with feature branches merged in](github_flow.png) @@ -62,13 +62,13 @@ Merging everything into the master branch and deploying often means you minimize But this flow still leaves a lot of questions unanswered regarding deployments, environments, releases and integrations with issues. With GitLab flow we offer additional guidance for these questions. -# Production branch with GitLab flow +## Production branch with GitLab flow ![Master branch and production branch with arrow that indicate deployments](production_branch.png) GitHub flow does assume you are able to deploy to production every time you merge a feature branch. This is possible for SaaS applications but are many cases where this is not possible. -One would be a situation where you are not in control of the exact release moment, for example an iOS application that needs to pass AppStore validation. +One would be a situation where you are not in control of the exact release moment, for example an iOS application that needs to pass App Store validation. Another example is when you have deployment windows (workdays from 10am to 4pm when the operations team is at full capacity) but you also merge code at other times. In these cases you can make a production branch that reflects the deployed code. You can deploy a new version by merging in master to the production branch. @@ -78,7 +78,7 @@ This time is pretty accurate if you automatically deploy your production branch. If you need a more exact time you can have your deployment script create a tag on each deployment. This flow prevents the overhead of releasing, tagging and merging that is common to git flow. -# Environment branches with GitLab flow +## Environment branches with GitLab flow ![Multiple branches with the code cascading from one to another](environment_branches.png) @@ -93,7 +93,7 @@ If master is good to go (it should be if you a practicing [continuous delivery]( If this is not possible because more manual testing is required you can send merge requests from the feature branch to the downstream branches. An 'extreme' version of environment branches are setting up an environment for each feature branch as done by [Teatro](http://teatro.io/). -# Release branches with GitLab flow +## Release branches with GitLab flow ![Master and multiple release branches that vary in length with cherrypicks from master](release_branches.png) @@ -109,7 +109,7 @@ Every time a bug-fix is included in a release branch the patch version is raised Some projects also have a stable branch that points to the same commit as the latest released branch. In this flow it is not common to have a production branch (or git flow master branch). -# Merge/pull requests with GitLab flow +## Merge/pull requests with GitLab flow ![Merge request with line comments](mr_inline_comments.png) @@ -134,7 +134,7 @@ If the assigned person does not feel comfortable they can close the merge reques In GitLab it is common to protect the long-lived branches (e.g. the master branch) so that normal developers [can't modify these protected branches](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/permissions/permissions.md). So if you want to merge it into a protected branch you assign it to someone with master authorizations. -# Issues with GitLab flow +## Issues with GitLab flow ![Merge request with the branch name 15-require-a-password-to-change-it and assignee field shown](merge_request.png) @@ -168,7 +168,7 @@ In this case it is no problem to reuse the same branch name since it was deleted At any time there is at most one branch for every issue. It is possible that one feature branch solves more than one issue. -# Linking and closing issues from merge requests +## Linking and closing issues from merge requests ![Merge request showing the linked issues that will be closed](close_issue_mr.png) @@ -181,7 +181,7 @@ If you only want to make the reference without closing the issue you can also ju If you have an issue that spans across multiple repositories, the best thing is to create an issue for each repository and link all issues to a parent issue. -# Squashing commits with rebase +## Squashing commits with rebase ![Vim screen showing the rebase view](rebase.png) @@ -189,7 +189,7 @@ With git you can use an interactive rebase (rebase -i) to squash multiple commit This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical. However you should never rebase commits you have pushed to a remote server. Somebody can have referred to the commits or cherry-picked them. -When you rebase you change the identifier (SHA1) of the commit and this is confusing. +When you rebase you change the identifier (SHA-1) of the commit and this is confusing. If you do that the same change will be known under multiple identifiers and this can cause much confusion. If people already reviewed your code it will be hard for them to review only the improvements you made since then if you have rebased everything into one commit. @@ -207,7 +207,7 @@ If you revert a merge and you change your mind, revert the revert instead of mer Being able to revert a merge is a good reason always to create a merge commit when you merge manually with the `--no-ff` option. Git management software will always create a merge commit when you accept a merge request. -# Do not order commits with rebase +## Do not order commits with rebase ![List of sequential merge commits](merge_commits.png) @@ -231,8 +231,8 @@ The last reason for creating merge commits is having long lived branches that yo Martin Fowler, in [his article about feature branches](http://martinfowler.com/bliki/FeatureBranch.html) talks about this Continuous Integration (CI). At GitLab we are guilty of confusing CI with branch testing. Quoting Martin Fowler: "I've heard people say they are doing CI because they are running builds, perhaps using a CI server, on every branch with every commit. That's continuous building, and a Good Thing, but there's no integration, so it's not CI.". -The solution to prevent many merge commits is to keep your feature branches shortlived, the vast majority should take less than one day of work. -If your feature branches commenly take more than a day of work, look into ways to create smaller units of work and/or use [feature toggles](http://martinfowler.com/bliki/FeatureToggle.html). +The solution to prevent many merge commits is to keep your feature branches short-lived, the vast majority should take less than one day of work. +If your feature branches commonly take more than a day of work, look into ways to create smaller units of work and/or use [feature toggles](http://martinfowler.com/bliki/FeatureToggle.html). As for the long running branches that take more than one day there are two strategies. In a CI strategy you can merge in master at the start of the day to prevent painful merges at a later time. In a synchronization point strategy you only merge in from well defined points in time, for example a tagged release. @@ -254,7 +254,7 @@ Developing software happen in small messy steps and it is OK to have your histor You can use tools to view the network graphs of commits and understand the messy history that created your code. If you rebase code the history is incorrect, and there is no way for tools to remedy this because they can't deal with changing commit identifiers. -# Voting on merge requests +## Voting on merge requests ![Voting slider in GitLab](voting_slider.png) @@ -262,7 +262,7 @@ It is common to voice approval or disapproval by using +1 or -1 emoticons. In GitLab the +1 and -1 are aggregated and shown at the top of the merge request. As a rule of thumb anything that doesn't have two times more +1's than -1's is suspect and should not be merged yet. -# Pushing and removing branches +## Pushing and removing branches ![Remove checkbox for branch in merge requests](remove_checkbox.png) @@ -276,7 +276,7 @@ This ensures that the branch overview in the repository management software show This also ensures that when someone reopens the issue a new branch with the same name can be used without problem. When you reopen an issue you need to create a new merge request. -# Committing often and with the right message +## Committing often and with the right message ![Good and bad commit message](good_commit.png) @@ -292,7 +292,7 @@ Some words that are bad commit messages because they don't contain munch informa The word fix or fixes is also a red flag, unless it comes after the commit sentence and references an issue number. To see more information about the formatting of commit messages please see this great [blog post by Tim Pope](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). -# Testing before merging +## Testing before merging ![Merge requests showing the test states, red, yellow and green](ci_mr.png) @@ -309,7 +309,7 @@ If there are no merge conflicts and the feature branches are short lived the ris If there are merge conflicts you merge the master branch into the feature branch and the CI server will rerun the tests. If you have long lived feature branches that last for more than a few days you should make your issues smaller. -# Merging in other code +## Merging in other code ![Shell output showing git pull output](git_pull.png) diff --git a/doc/workflow/protected_branches.md b/doc/workflow/protected_branches.md new file mode 100644 index 0000000000..805f7f8d35 --- /dev/null +++ b/doc/workflow/protected_branches.md @@ -0,0 +1,33 @@ +# Protected branches + +Permission in GitLab are fundamentally defined around the idea of having read or write permission to the repository and branches. + +To prevent people from messing with history or pushing code without review, we've created protected branches. + +A protected branch does three simple things: + +* it prevents pushes from everybody except users with Master permission +* it prevents anyone from force pushing to the branch +* it prevents anyone from deleting the branch + +You can make any branch a protected branch. GitLab makes the master branch a protected branch by default. + +To protect a branch, user needs to have at least a Master permission level, see [permissions document](permissions/permissions.md). + +![protected branches page](protected_branches/protected_branches1.png) + +Navigate to project settings page and select `protected branches`. From the `Branch` dropdown menu select the branch you want to protect. + +Some workflows, like [GitLab workflow](gitlab_flow.md), require all users with write access to submit a Merge request in order to get the code into a protected branch. + +Since Masters and Owners can already push to protected branches, that means Developers cannot push to protected branch and need to submit a Merge request. + +However, there are workflows where that is not needed and only protecting from force pushes and branch removal is useful. + +For those workflows, you can allow everyone with write access to push to a protected branch by selecting `Developers can push` check box. + +On already protected branches you can also allow developers to push to the repository by selecting the `Developers can push` check box. + +![Developers can push](protected_branches/protected_branches2.png) + + diff --git a/doc/workflow/protected_branches/protected_branches1.png b/doc/workflow/protected_branches/protected_branches1.png new file mode 100644 index 0000000000..5c2a3de5f7 Binary files /dev/null and b/doc/workflow/protected_branches/protected_branches1.png differ diff --git a/doc/workflow/protected_branches/protected_branches2.png b/doc/workflow/protected_branches/protected_branches2.png new file mode 100644 index 0000000000..2dca354136 Binary files /dev/null and b/doc/workflow/protected_branches/protected_branches2.png differ diff --git a/features/admin/settings.feature b/features/admin/settings.feature new file mode 100644 index 0000000000..8799c053ea --- /dev/null +++ b/features/admin/settings.feature @@ -0,0 +1,9 @@ +@admin +Feature: Admin Settings + Background: + Given I sign in as an admin + And I visit admin settings page + + Scenario: Change application settings + When I disable gravatars and save form + Then I should be see gravatar disabled diff --git a/features/admin/users.feature b/features/admin/users.feature index 278f6a43e9..1a8720dd77 100644 --- a/features/admin/users.feature +++ b/features/admin/users.feature @@ -35,3 +35,13 @@ Feature: Admin Users And I see the secondary email When I click remove secondary email Then I should not see secondary email anymore + + Scenario: Show user keys + Given user "Pete" with ssh keys + And I visit admin users page + And click on user "Pete" + Then I should see key list + And I click on the key title + Then I should see key details + And I click on remove key + Then I should see the key removed diff --git a/features/project/service.feature b/features/project/service.feature index 4fa6870da7..04e7512b3c 100644 --- a/features/project/service.feature +++ b/features/project/service.feature @@ -72,3 +72,10 @@ Feature: Project Services And I click Atlassian Bamboo CI service link And I fill Atlassian Bamboo CI settings Then I should see Atlassian Bamboo CI service settings saved + + Scenario: Activate jetBrains TeamCity CI service + When I visit project "Shop" services page + And I click jetBrains TeamCity CI service link + And I fill jetBrains TeamCity CI settings + Then I should see jetBrains TeamCity CI service settings saved + diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature index b7d70881d5..6ea64f7009 100644 --- a/features/project/source/browse_files.feature +++ b/features/project/source/browse_files.feature @@ -50,6 +50,16 @@ Feature: Project Source Browse Files And I click button "Edit" Then I can edit code + Scenario: If the file is binary the edit link is hidden + Given I visit a binary file in the repo + Then I cannot see the edit button + + Scenario: If I don't have edit permission the edit link is disabled + Given public project "Community" + And I visit project "Community" source page + And I click on ".gitignore" file in repo + Then The edit button is disabled + @javascript Scenario: I can edit and commit file Given I click on ".gitignore" file in repo diff --git a/features/steps/admin/settings.rb b/features/steps/admin/settings.rb new file mode 100644 index 0000000000..e8168e85de --- /dev/null +++ b/features/steps/admin/settings.rb @@ -0,0 +1,16 @@ +class Spinach::Features::AdminSettings < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedAdmin + include Gitlab::CurrentSettings + + step 'I disable gravatars and save form' do + uncheck 'Gravatar enabled' + click_button 'Save' + end + + step 'I should be see gravatar disabled' do + current_application_settings.gravatar_enabled.should be_false + page.should have_content 'Application settings saved successfully' + end +end diff --git a/features/steps/admin/users.rb b/features/steps/admin/users.rb index 546c1bf2a1..e138309724 100644 --- a/features/steps/admin/users.rb +++ b/features/steps/admin/users.rb @@ -82,4 +82,36 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps page.should have_content 'Account' page.should have_content 'Personal projects limit' end + + step 'user "Pete" with ssh keys' do + user = create(:user, name: 'Pete') + create(:key, user: user, title: "ssh-rsa Key1", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4FIEBXGi4bPU8kzxMefudPIJ08/gNprdNTaO9BR/ndy3+58s2HCTw2xCHcsuBmq+TsAqgEidVq4skpqoTMB+Uot5Uzp9z4764rc48dZiI661izoREoKnuRQSsRqUTHg5wrLzwxlQbl1MVfRWQpqiz/5KjBC7yLEb9AbusjnWBk8wvC1bQPQ1uLAauEA7d836tgaIsym9BrLsMVnR4P1boWD3Xp1B1T/ImJwAGHvRmP/ycIqmKdSpMdJXwxcb40efWVj0Ibbe7ii9eeoLdHACqevUZi6fwfbymdow+FeqlkPoHyGg3Cu4vD/D8+8cRc7mE/zGCWcQ15Var83Tczour Key1") + create(:key, user: user, title: "ssh-rsa Key2", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2") + end + + step 'click on user "Pete"' do + click_link 'Pete' + end + + step 'I should see key list' do + page.should have_content 'ssh-rsa Key2' + page.should have_content 'ssh-rsa Key1' + end + + step 'I click on the key title' do + click_link 'ssh-rsa Key2' + end + + step 'I should see key details' do + page.should have_content 'ssh-rsa Key2' + page.should have_content 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2' + end + + step 'I click on remove key' do + click_link 'Remove' + end + + step 'I should see the key removed' do + page.should_not have_content 'ssh-rsa Key2' + end end diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 7a021fe5d8..405d74332d 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -226,7 +226,6 @@ class Spinach::Features::Groups < Spinach::FeatureSteps end step 'I should see group milestone with descriptions and expiry date' do - page.should have_content('Lorem Ipsum is simply dummy text of the printing and typesetting industry') page.should have_content('expires at Aug 20, 2114') end diff --git a/features/steps/profile/ssh_keys.rb b/features/steps/profile/ssh_keys.rb index d1e87d4070..ea912e5b4d 100644 --- a/features/steps/profile/ssh_keys.rb +++ b/features/steps/profile/ssh_keys.rb @@ -37,9 +37,7 @@ class Spinach::Features::ProfileSshKeys < Spinach::FeatureSteps end step 'I should not see "Work" ssh key' do - within "#keys-table" do - page.should_not have_content "Work" - end + page.should_not have_content "Work" end step 'I have ssh key "ssh-rsa Work"' do diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index c6b1686ccf..68babcbe10 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -113,7 +113,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps click_link 'Commits' end - within '.mr-commits' do + within '.commits' do click_link Commit.truncate_sha(sample_commit.id) end end @@ -156,7 +156,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end step 'merge request is mergeable' do - page.should have_content 'You can accept this request automatically' + page.should have_button 'Accept Merge Request' end step 'I modify merge commit message' do @@ -275,7 +275,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end step 'I click Side-by-side Diff tab' do - click_link 'Side-by-side' + find('a', text: 'Side-by-side').trigger('click') end step 'I should see comments on the side-by-side diff page' do diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb index b51644d91a..f3b3570a0e 100644 --- a/features/steps/project/services.rb +++ b/features/steps/project/services.rb @@ -15,6 +15,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps page.should have_content 'Assembla' page.should have_content 'Pushover' page.should have_content 'Atlassian Bamboo' + page.should have_content 'JetBrains TeamCity' end step 'I click gitlab-ci service link' do @@ -187,4 +188,23 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps find_field('Build key').value.should == 'KEY' find_field('Username').value.should == 'user' end + + step 'I click JetBrains TeamCity CI service link' do + click_link 'JetBrains TeamCity CI' + end + + step 'I fill JetBrains TeamCity CI settings' do + check 'Active' + fill_in 'Teamcity url', with: 'http://teamcity.example.com' + fill_in 'Build type', with: 'GitlabTest_Build' + fill_in 'Username', with: 'user' + fill_in 'Password', with: 'verySecret' + click_button 'Save' + end + + step 'I should see JetBrains TeamCity CI service settings saved' do + find_field('Teamcity url').value.should == 'http://teamcity.example.com' + find_field('Build type').value.should == 'GitlabTest_Build' + find_field('Username').value.should == 'user' + end end diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index ddd501d4f8..805e6ff0ea 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -48,6 +48,14 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps click_link 'Edit' end + step 'I cannot see the edit button' do + page.should_not have_link 'edit' + end + + step 'The edit button is disabled' do + page.should have_css '.disabled', text: 'Edit' + end + step 'I can edit code' do set_new_content evaluate_script('editor.getValue()').should == new_gitignore_content diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index 0822ec6188..197eebfd0e 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -1,6 +1,7 @@ module SharedPaths include Spinach::DSL include RepoHelpers + include DashboardHelper step 'I visit new project page' do visit new_project_path @@ -79,11 +80,11 @@ module SharedPaths end step 'I visit dashboard issues page' do - visit issues_dashboard_path + visit assigned_issues_dashboard_path end step 'I visit dashboard merge requests page' do - visit merge_requests_dashboard_path + visit assigned_mrs_dashboard_path end step 'I visit dashboard search page' do @@ -181,6 +182,11 @@ module SharedPaths step 'I visit admin email page' do visit admin_email_path end + + step 'I visit admin settings page' do + visit admin_application_settings_path + end + # ---------------------------------------- # Generic Project # ---------------------------------------- @@ -197,6 +203,11 @@ module SharedPaths visit project_tree_path(@project, root_ref) end + step 'I visit a binary file in the repo' do + visit project_blob_path(@project, File.join( + root_ref, 'files/images/logo-black.png')) + end + step "I visit my project's commits page" do visit project_commits_path(@project, root_ref, {limit: 5}) end @@ -403,6 +414,11 @@ module SharedPaths visit project_path(project) end + step 'I visit project "Community" source page' do + project = Project.find_by(name: 'Community') + visit project_tree_path(project, root_ref) + end + step 'I visit project "Internal" page' do project = Project.find_by(name: "Internal") visit project_path(project) diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 6ec1a753a6..b52d786e02 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -14,7 +14,8 @@ module API # Example Request: # GET /projects/:id/repository/branches get ":id/repository/branches" do - present user_project.repository.branches.sort_by(&:name), with: Entities::RepoObject, project: user_project + branches = user_project.repository.branches.sort_by(&:name) + present branches, with: Entities::RepoObject, project: user_project end # Get a single branch @@ -26,7 +27,7 @@ module API # GET /projects/:id/repository/branches/:branch get ':id/repository/branches/:branch', requirements: { branch: /.*/ } do @branch = user_project.repository.branches.find { |item| item.name == params[:branch] } - not_found!("Branch does not exist") if @branch.nil? + not_found!("Branch") unless @branch present @branch, with: Entities::RepoObject, project: user_project end @@ -43,7 +44,7 @@ module API authorize_admin_project @branch = user_project.repository.find_branch(params[:branch]) - not_found! unless @branch + not_found!("Branch") unless @branch protected_branch = user_project.protected_branches.find_by(name: @branch.name) user_project.protected_branches.create(name: @branch.name) unless protected_branch @@ -63,7 +64,7 @@ module API authorize_admin_project @branch = user_project.repository.find_branch(params[:branch]) - not_found! unless @branch + not_found!("Branch does not exist") unless @branch protected_branch = user_project.protected_branches.find_by(name: @branch.name) protected_branch.destroy if protected_branch diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 6c5391b98c..0de4e720ff 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -108,7 +108,7 @@ module API if note.save present note, with: Entities::CommitNote else - not_found! + render_api_error!("Failed to save note #{note.errors.messages}", 400) end end end diff --git a/lib/api/files.rb b/lib/api/files.rb index 84e1d31178..e6e71bac36 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -35,7 +35,7 @@ module API file_path = attrs.delete(:file_path) commit = user_project.repository.commit(ref) - not_found! "Commit" unless commit + not_found! 'Commit' unless commit blob = user_project.repository.blob_at(commit.sha, file_path) @@ -53,7 +53,7 @@ module API commit_id: commit.id, } else - render_api_error!('File not found', 404) + not_found! 'File' end end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 027d83c598..7214358833 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -62,7 +62,7 @@ module API end present @group, with: Entities::Group else - not_found! + render_api_error!("Failed to save group #{@group.errors.messages}", 400) end end @@ -105,7 +105,7 @@ module API if result present group else - not_found! + render_api_error!("Failed to transfer project #{project.errors.messages}", 400) end end end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 2f2342840f..62c26ef76c 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -42,7 +42,7 @@ module API def user_project @project ||= find_project(params[:id]) - @project || not_found! + @project || not_found!("Project") end def find_project(id) diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index a365f1db00..81038d05f1 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -233,7 +233,7 @@ module API if note.save present note, with: Entities::MRNote else - render_validation_error!(note) + render_api_error!("Failed to save note #{note.errors.messages}", 400) end end end diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index a4fdb752d6..2ea49359df 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -48,7 +48,7 @@ module API if milestone.valid? present milestone, with: Entities::Milestone else - not_found! + render_api_error!("Failed to create milestone #{milestone.errors.messages}", 400) end end @@ -72,7 +72,7 @@ module API if milestone.valid? present milestone, with: Entities::Milestone else - not_found! + render_api_error!("Failed to update milestone #{milestone.errors.messages}", 400) end end end diff --git a/lib/api/notes.rb b/lib/api/notes.rb index 0ef9a3c4be..3726be7c53 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -61,9 +61,42 @@ module API if @note.valid? present @note, with: Entities::Note else - not_found! + not_found!("Note #{@note.errors.messages}") end end + + # Modify existing +noteable+ note + # + # Parameters: + # id (required) - The ID of a project + # noteable_id (required) - The ID of an issue or snippet + # node_id (required) - The ID of a note + # body (required) - New content of a note + # Example Request: + # PUT /projects/:id/issues/:noteable_id/notes/:note_id + # PUT /projects/:id/snippets/:noteable_id/notes/:node_id + put ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do + required_attributes! [:body] + + authorize! :admin_note, user_project.notes.find(params[:note_id]) + + opts = { + note: params[:body], + note_id: params[:note_id], + noteable_type: noteables_str.classify, + noteable_id: params[noteable_id_str] + } + + @note = ::Notes::UpdateService.new(user_project, current_user, + opts).execute + + if @note.valid? + present @note, with: Entities::Note + else + render_api_error!("Failed to save note #{note.errors.messages}", 400) + end + end + end end end diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb index 7d056b9bf5..be9850367b 100644 --- a/lib/api/project_hooks.rb +++ b/lib/api/project_hooks.rb @@ -53,7 +53,7 @@ module API if @hook.errors[:url].present? error!("Invalid url given", 422) end - not_found! + not_found!("Project hook #{@hook.errors.messages}") end end @@ -82,7 +82,7 @@ module API if @hook.errors[:url].present? error!("Invalid url given", 422) end - not_found! + not_found!("Project hook #{@hook.errors.messages}") end end diff --git a/lib/api/project_members.rb b/lib/api/project_members.rb index 7d83c1bebd..8fe774da2f 100644 --- a/lib/api/project_members.rb +++ b/lib/api/project_members.rb @@ -9,7 +9,7 @@ module API if errors[:access_level].any? error!(errors[:access_level], 422) end - not_found! + not_found!(errors) end end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 2da4e68c4c..d08c758fb9 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -22,6 +22,15 @@ module API # GET /projects get do @projects = current_user.authorized_projects + sort = params[:sort] == 'desc' ? 'desc' : 'asc' + + @projects = case params["order_by"] + when 'id' then @projects.reorder("id #{sort}") + when 'name' then @projects.reorder("name #{sort}") + when 'created_at' then @projects.reorder("created_at #{sort}") + when 'last_activity_at' then @projects.reorder("last_activity_at #{sort}") + else @projects + end # If the archived parameter is passed, limit results accordingly if params[:archived].present? @@ -37,7 +46,17 @@ module API # Example Request: # GET /projects/owned get '/owned' do - @projects = paginate current_user.owned_projects + sort = params[:sort] == 'desc' ? 'desc' : 'asc' + @projects = current_user.owned_projects + @projects = case params["order_by"] + when 'id' then @projects.reorder("id #{sort}") + when 'name' then @projects.reorder("name #{sort}") + when 'created_at' then @projects.reorder("created_at #{sort}") + when 'last_activity_at' then @projects.reorder("last_activity_at #{sort}") + else @projects + end + + @projects = paginate @projects present @projects, with: Entities::Project end @@ -47,7 +66,17 @@ module API # GET /projects/all get '/all' do authenticated_as_admin! - @projects = paginate Project + sort = params[:sort] == 'desc' ? 'desc' : 'asc' + + @projects = case params["order_by"] + when 'id' then Project.order("id #{sort}") + when 'name' then Project.order("name #{sort}") + when 'created_at' then Project.order("created_at #{sort}") + when 'last_activity_at' then Project.order("last_activity_at #{sort}") + else Project + end + + @projects = paginate @projects present @projects, with: Entities::Project end @@ -198,7 +227,7 @@ module API render_api_error!("Project already forked", 409) end else - not_found! + not_found!("Source Project") end end @@ -251,6 +280,16 @@ module API ids = current_user.authorized_projects.map(&:id) visibility_levels = [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ] projects = Project.where("(id in (?) OR visibility_level in (?)) AND (name LIKE (?))", ids, visibility_levels, "%#{params[:query]}%") + sort = params[:sort] == 'desc' ? 'desc' : 'asc' + + projects = case params["order_by"] + when 'id' then projects.order("id #{sort}") + when 'name' then projects.order("name #{sort}") + when 'created_at' then projects.order("created_at #{sort}") + when 'last_activity_at' then projects.order("last_activity_at #{sort}") + else projects + end + present paginate(projects), with: Entities::Project end diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index a1a7721b28..03a556a2c5 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -133,7 +133,7 @@ module API env['api.format'] = :binary present data else - not_found! + not_found!('File') end end diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb new file mode 100644 index 0000000000..f3b9dcacde --- /dev/null +++ b/lib/gitlab/current_settings.rb @@ -0,0 +1,22 @@ +module Gitlab + module CurrentSettings + def current_application_settings + if ActiveRecord::Base.connection.table_exists?('application_settings') + ApplicationSetting.current || + ApplicationSetting.create_from_defaults + else + fake_application_settings + end + end + + def fake_application_settings + OpenStruct.new( + default_projects_limit: Settings.gitlab['default_projects_limit'], + signup_enabled: Settings.gitlab['signup_enabled'], + signin_enabled: Settings.gitlab['signin_enabled'], + gravatar_enabled: Settings.gravatar['enabled'], + sign_in_text: Settings.extra['sign_in_text'], + ) + end + end +end diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 6e59e2dcad..0f0c4bd35a 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -79,16 +79,8 @@ module Gitlab oldrev, newrev, ref = change.split(' ') action = if project.protected_branch?(branch_name(ref)) - # we dont allow force push to protected branch - if forced_push?(project, oldrev, newrev) - :force_push_code_to_protected_branches - # and we dont allow remove of protected branch - elsif newrev == Gitlab::Git::BLANK_SHA - :remove_protected_branches - else - :push_code_to_protected_branches - end - elsif project.repository.tag_names.include?(tag_name(ref)) + protected_branch_action(project, oldrev, newrev, branch_name(ref)) + elsif protected_tag?(project, tag_name(ref)) # Prevent any changes to existing git tag unless user has permissions :admin_project else @@ -171,6 +163,24 @@ module Gitlab private + def protected_branch_action(project, oldrev, newrev, branch_name) + # we dont allow force push to protected branch + if forced_push?(project, oldrev, newrev) + :force_push_code_to_protected_branches + # and we dont allow remove of protected branch + elsif newrev == Gitlab::Git::BLANK_SHA + :remove_protected_branches + elsif project.developers_can_push_to_protected_branch?(branch_name) + :push_code + else + :push_code_to_protected_branches + end + end + + def protected_tag?(project, tag_name) + project.repository.tag_names.include?(tag_name) + end + def user_allowed?(user) Gitlab::UserAccess.allowed?(user) end diff --git a/lib/gitlab/popen.rb b/lib/gitlab/popen.rb index e2fbafb389..fea4d2d55d 100644 --- a/lib/gitlab/popen.rb +++ b/lib/gitlab/popen.rb @@ -21,6 +21,9 @@ module Gitlab @cmd_output = "" @cmd_status = 0 Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| + # We are not using stdin so we should close it, in case the command we + # are running waits for input. + stdin.close @cmd_output << stdout.read @cmd_output << stderr.read @cmd_status = wait_thr.value.exitstatus diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb index 746b6fc1ac..de4f94fff2 100644 --- a/spec/features/atom/users_spec.rb +++ b/spec/features/atom/users_spec.rb @@ -24,11 +24,12 @@ describe "User Feed", feature: true do end it "should have issue opened event" do - body.should have_content("#{user.name} opened issue ##{issue.iid}") + expect(body).to have_content("#{safe_name} opened issue ##{issue.iid}") end it "should have issue comment event" do - body.should have_content("#{user.name} commented on issue ##{issue.iid}") + expect(body). + to have_content("#{safe_name} commented on issue ##{issue.iid}") end end end @@ -40,4 +41,8 @@ describe "User Feed", feature: true do def note_event(note, user) EventCreateService.new.leave_note(note, user) end + + def safe_name + html_escape(user.name) + end end diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb index bdf7b59114..4a76e89fd3 100644 --- a/spec/features/profile_spec.rb +++ b/spec/features/profile_spec.rb @@ -9,7 +9,7 @@ describe "Profile account page", feature: true do describe "when signup is enabled" do before do - Gitlab.config.gitlab.stub(:signup_enabled).and_return(true) + ApplicationSetting.any_instance.stub(signup_enabled?: true) visit profile_account_path end @@ -23,7 +23,7 @@ describe "Profile account page", feature: true do describe "when signup is disabled" do before do - Gitlab.config.gitlab.stub(:signup_enabled).and_return(false) + ApplicationSetting.any_instance.stub(signup_enabled?: false) visit profile_account_path end diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index a1206989d3..e2b631001c 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'Users', feature: true do describe "GET /users/sign_up" do before do - Gitlab.config.gitlab.stub(:signup_enabled).and_return(true) + ApplicationSetting.any_instance.stub(signup_enabled?: true) end it "should create a new user account" do diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 07dd33b211..9cdbc846b1 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -87,7 +87,7 @@ describe ApplicationHelper do let(:user_email) { 'user@email.com' } it "should return a generic avatar path when Gravatar is disabled" do - Gitlab.config.gravatar.stub(:enabled).and_return(false) + ApplicationSetting.any_instance.stub(gravatar_enabled?: false) gravatar_icon(user_email).should match('no_avatar.png') end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 63822bd478..c387797c8c 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -129,6 +129,13 @@ describe Gitlab::GitAccess do } end + def self.updated_permissions_matrix + updated_permissions_matrix = permissions_matrix.dup + updated_permissions_matrix[:developer][:push_protected_branch] = true + updated_permissions_matrix[:developer][:push_all] = true + updated_permissions_matrix + end + permissions_matrix.keys.each do |role| describe "#{role} access" do before { protect_feature_branch } @@ -143,6 +150,23 @@ describe Gitlab::GitAccess do end end end + + context "with enabled developers push to protected branches " do + updated_permissions_matrix.keys.each do |role| + describe "#{role} access" do + before { create(:protected_branch, name: 'feature', developers_can_push: true, project: project) } + before { project.team << [user, role] } + + updated_permissions_matrix[role].each do |action, allowed| + context action do + subject { access.push_access_check(user, project, changes[action]) } + + it { subject.allowed?.should allowed ? be_true : be_false } + end + end + end + end + end end describe "pass_git_hooks?" do diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb new file mode 100644 index 0000000000..039775dddd --- /dev/null +++ b/spec/models/application_setting_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe ApplicationSetting, models: true do + it { ApplicationSetting.create_from_defaults.should be_valid } +end diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb index cbbd1e7de5..5921b3e069 100644 --- a/spec/requests/api/fork_spec.rb +++ b/spec/requests/api/fork_spec.rb @@ -44,7 +44,7 @@ describe API::API, api: true do it 'should fail on missing project access for the project to fork' do post api("/projects/fork/#{project.id}", user3) response.status.should == 404 - json_response['message'].should == '404 Not Found' + json_response['message'].should == '404 Project Not Found' end it 'should fail if forked project exists in the user namespace' do @@ -58,7 +58,7 @@ describe API::API, api: true do it 'should fail if project to fork from does not exist' do post api('/projects/fork/424242', user) response.status.should == 404 - json_response['message'].should == '404 Not Found' + json_response['message'].should == '404 Project Not Found' end end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index e63d4ad774..87c28a1b82 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -98,7 +98,8 @@ describe API::API, api: true do it "should not create group, duplicate" do post api("/groups", admin), {name: "Duplicate Test", path: group2.path} - response.status.should == 404 + response.status.should == 400 + response.message.should == "Bad Request" end it "should return 400 bad request error if name not given" do diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 7aa53787ae..429824e829 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -131,4 +131,58 @@ describe API::API, api: true do post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!' end end + + describe 'PUT /projects/:id/noteable/:noteable_id/notes/:note_id' do + context 'when noteable is an Issue' do + it 'should return modified note' do + put api("/projects/#{project.id}/issues/#{issue.id}/"\ + "notes/#{issue_note.id}", user), body: 'Hello!' + response.status.should == 200 + json_response['body'].should == 'Hello!' + end + + it 'should return a 404 error when note id not found' do + put api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user), + body: 'Hello!' + response.status.should == 404 + end + + it 'should return a 400 bad request error if body not given' do + put api("/projects/#{project.id}/issues/#{issue.id}/"\ + "notes/#{issue_note.id}", user) + response.status.should == 400 + end + end + + context 'when noteable is a Snippet' do + it 'should return modified note' do + put api("/projects/#{project.id}/snippets/#{snippet.id}/"\ + "notes/#{snippet_note.id}", user), body: 'Hello!' + response.status.should == 200 + json_response['body'].should == 'Hello!' + end + + it 'should return a 404 error when note id not found' do + put api("/projects/#{project.id}/snippets/#{snippet.id}/"\ + "notes/123", user), body: "Hello!" + response.status.should == 404 + end + end + + context 'when noteable is a Merge Request' do + it 'should return modified note' do + put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\ + "notes/#{merge_request_note.id}", user), body: 'Hello!' + response.status.should == 200 + json_response['body'].should == 'Hello!' + end + + it 'should return a 404 error when note id not found' do + put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\ + "notes/123", user), body: "Hello!" + response.status.should == 404 + end + end + end + end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index b570805304..047d52825c 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -289,7 +289,7 @@ describe API::API, api: true do it "should return a 404 error if not found" do get api("/projects/42", user) response.status.should == 404 - json_response['message'].should == '404 Not Found' + json_response['message'].should == '404 Project Not Found' end it "should return a 404 error if user is not a member" do @@ -340,7 +340,7 @@ describe API::API, api: true do it "should return a 404 error if not found" do get api("/projects/42/events", user) response.status.should == 404 - json_response['message'].should == '404 Not Found' + json_response['message'].should == '404 Project Not Found' end it "should return a 404 error if user is not a member" do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 1ecc79ea7e..dec488c6d0 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -186,7 +186,7 @@ describe API::API, api: true do describe "GET /users/sign_up" do context 'enabled' do before do - Gitlab.config.gitlab.stub(:signup_enabled).and_return(true) + ApplicationSetting.any_instance.stub(signup_enabled?: true) end it "should return sign up page if signup is enabled" do @@ -197,7 +197,7 @@ describe API::API, api: true do context 'disabled' do before do - Gitlab.config.gitlab.stub(:signup_enabled).and_return(false) + ApplicationSetting.any_instance.stub(signup_enabled?: false) end it "should redirect to sign in page if signup is disabled" do diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 9f29415205..35c7aac94d 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -47,10 +47,10 @@ describe MergeRequests::RefreshService do reload_mrs end - it { @merge_request.notes.should be_empty } + it { @merge_request.notes.last.note.should include('changed to merged') } it { @merge_request.should be_merged } it { @fork_merge_request.should be_merged } - it { @fork_merge_request.notes.should be_empty } + it { @fork_merge_request.notes.last.note.should include('changed to merged') } end context 'push to fork repo source branch' do @@ -61,7 +61,7 @@ describe MergeRequests::RefreshService do it { @merge_request.notes.should be_empty } it { @merge_request.should be_open } - it { @fork_merge_request.notes.should_not be_empty } + it { @fork_merge_request.notes.last.note.should include('new commit') } it { @fork_merge_request.should be_open } end @@ -84,7 +84,7 @@ describe MergeRequests::RefreshService do reload_mrs end - it { @merge_request.notes.should be_empty } + it { @merge_request.notes.last.note.should include('changed to merged') } it { @merge_request.should be_merged } it { @fork_merge_request.should be_open } it { @fork_merge_request.notes.should be_empty } diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index f8377650e0..e305536f7e 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -116,6 +116,7 @@ describe NotificationService do should_email(note.noteable.assignee_id) should_not_email(note.author_id) + should_not_email(@u_mentioned.id) should_not_email(@u_disabled.id) should_not_email(@u_not_mentioned.id) notification.new_note(note) @@ -168,6 +169,12 @@ describe NotificationService do notification.new_note(note) end + it do + @u_committer.update_attributes(notification_level: Notification::N_MENTION) + should_not_email(@u_committer.id, note) + notification.new_note(note) + end + def should_email(user_id, n) Notify.should_receive(:note_commit_email).with(user_id, n.id) end @@ -190,11 +197,18 @@ describe NotificationService do it do should_email(issue.assignee_id) should_email(@u_watcher.id) + should_not_email(@u_mentioned.id) should_not_email(@u_participating.id) should_not_email(@u_disabled.id) notification.new_issue(issue, @u_disabled) end + it do + issue.assignee.update_attributes(notification_level: Notification::N_MENTION) + should_not_email(issue.assignee_id) + notification.new_issue(issue, @u_disabled) + end + def should_email(user_id) Notify.should_receive(:new_issue_email).with(user_id, issue.id) end @@ -391,7 +405,7 @@ describe NotificationService do @u_watcher = create(:user, notification_level: Notification::N_WATCH) @u_participating = create(:user, notification_level: Notification::N_PARTICIPATING) @u_disabled = create(:user, notification_level: Notification::N_DISABLED) - @u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_PARTICIPATING) + @u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_MENTION) @u_committer = create(:user, username: 'committer') @u_not_mentioned = create(:user, username: 'regular', notification_level: Notification::N_PARTICIPATING) diff --git a/vendor/assets/javascripts/chart-lib.min.js b/vendor/assets/javascripts/chart-lib.min.js old mode 100755 new mode 100644 diff --git a/vendor/plugins/.gitkeep b/vendor/plugins/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000