diff --git a/CHANGELOG b/CHANGELOG index 1252b06795..15c7be48ab 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,19 @@ v 7.1.0 - Add @all mention for comments - Dont show reply button if user is not signed in - Expose more information for issues with webhook + - Add a mention of the merge request into the default merge request commit message + - Imrpove code highlight, introduce support for more languages like Go, Clojure, Erlang etc + - Fix concurrency issue in repository download + - Dont allow repository name start with ? + - Improve email threading (Pierre de La Morinerie) + - Cleaner help page + - Group milestones + - Improved email notifications + - Contributors API (sponsored by Mobbr) + - Fix LDAP TLS authentication (Boris HUISGEN) + - Show VERSION information on project sidebar + - Improve branch removal logic when accept MR + - Fix bug where comment form is spawned inside the Reply button v 7.0.0 - The CPU no longer overheats when you hold down the spacebar diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a08ffa449d..e7e4cbf39a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,7 +42,7 @@ Please send a merge request with a tested solution or a merge request with a fai 1. **Observed behavior** 1. **Relevant logs and/or screenshots:** Please use code blocks (\`\`\`) to format console output, logs, and code as it's very hard to read otherwise. 1. **Output of checks** - * Results of GitLab [Application Check](doc/install/installation.md#check-application-status) (`sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production`); we will only investigate if the tests are passing + * Results of GitLab [Application Check](doc/install/installation.md#check-application-status) (`sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true`); we will only investigate if the tests are passing * Version of GitLab you are running; we will only investigate issues in the latest stable and development releases as per the [maintenance policy](MAINTENANCE.md) * Add the last commit sha1 of the GitLab version you used to replicate the issue (obtainable from the help page) * Describe your setup (use relevant parts from `sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`) diff --git a/Gemfile b/Gemfile index 02f5389e30..608c1894a6 100644 --- a/Gemfile +++ b/Gemfile @@ -10,8 +10,6 @@ end gem "rails", "~> 4.1.0" -gem "protected_attributes" - # Make links from text gem 'rails_autolink', '~> 1.1' @@ -23,8 +21,8 @@ gem "mysql2", group: :mysql gem "pg", group: :postgres # Auth -gem "devise", '3.0.4' -gem "devise-async", '0.8.0' +gem "devise", '3.2.4' +gem "devise-async", '0.9.0' gem 'omniauth', "~> 1.1.3" gem 'omniauth-google-oauth2' gem 'omniauth-twitter' @@ -49,7 +47,6 @@ gem "gitlab-linguist", "~> 3.0.0", require: "linguist" # API gem "grape", "~> 0.6.1" -# Replace with rubygems when nesteted entities get released gem "grape-entity", "~> 0.4.2" gem 'rack-cors', require: 'rack/cors' diff --git a/Gemfile.lock b/Gemfile.lock index ab556bc117..c589aa6346 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -40,7 +40,7 @@ GEM axiom-types (0.0.5) descendants_tracker (~> 0.0.1) ice_nine (~> 0.9) - bcrypt-ruby (3.1.2) + bcrypt (3.1.7) better_errors (1.0.1) coderay (>= 1.0.0) erubis (>= 2.6.6) @@ -94,13 +94,14 @@ GEM default_value_for (3.0.0) activerecord (>= 3.2.0, < 5.0) descendants_tracker (0.0.3) - devise (3.0.4) - bcrypt-ruby (~> 3.0) + devise (3.2.4) + bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 3.2.6, < 5) + thread_safe (~> 0.1) warden (~> 1.2.3) - devise-async (0.8.0) - devise (>= 2.2, < 3.2) + devise-async (0.9.0) + devise (~> 3.2) diff-lcs (1.2.5) diffy (3.0.3) docile (1.1.1) @@ -175,7 +176,7 @@ GEM mime-types (~> 1.19) gitlab_emoji (0.0.1.1) emoji (~> 1.0.1) - gitlab_git (6.0.0) + gitlab_git (6.0.1) activesupport (~> 4.0) charlock_holmes (~> 0.6) gitlab-grit (~> 2.6) @@ -282,7 +283,7 @@ GEM method_source (0.8.2) mime-types (1.25.1) mini_portile (0.6.0) - minitest (5.3.4) + minitest (5.3.5) multi_json (1.10.1) multi_xml (0.5.5) multipart-post (1.2.0) @@ -331,8 +332,6 @@ GEM websocket-driver (>= 0.2.0) polyglot (0.3.4) posix-spawn (0.3.8) - protected_attributes (1.0.5) - activemodel (>= 4.0.1, < 5.0) pry (0.9.12.4) coderay (~> 1.0) method_source (~> 0.8) @@ -586,8 +585,8 @@ DEPENDENCIES d3_rails (~> 3.1.4) database_cleaner default_value_for (~> 3.0.0) - devise (= 3.0.4) - devise-async (= 0.8.0) + devise (= 3.2.4) + devise-async (= 0.9.0) diffy (~> 3.0.3) dropzonejs-rails email_spec @@ -636,7 +635,6 @@ DEPENDENCIES org-ruby pg poltergeist (~> 1.5.1) - protected_attributes pry quiet_assets (~> 1.0.1) rack-attack diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 5b124554c3..a28f76ddd1 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -23,7 +23,7 @@ #= require g.raphael-min #= require g.bar-min #= require branch-graph -#= require highlightjs.min +#= require highlight.pack #= require ace/ace #= require d3 #= require underscore diff --git a/app/assets/javascripts/extensions/array.js b/app/assets/javascripts/extensions/array.js index 7fccc9c9d5..24f9e00097 100644 --- a/app/assets/javascripts/extensions/array.js +++ b/app/assets/javascripts/extensions/array.js @@ -4,4 +4,4 @@ Array.prototype.first = function() { Array.prototype.last = function() { return this[this.length-1]; -} \ No newline at end of file +} diff --git a/app/assets/javascripts/groups.js.coffee b/app/assets/javascripts/groups.js.coffee index 7850eb14e7..49d6605980 100644 --- a/app/assets/javascripts/groups.js.coffee +++ b/app/assets/javascripts/groups.js.coffee @@ -14,4 +14,4 @@ $ -> $('.js-group-avatar-input').bind "change", -> form = $(this).closest("form") filename = $(this).val().replace(/^.*[\\\/]/, '') - form.find(".js-avatar-filename").text(filename) \ No newline at end of file + form.find(".js-avatar-filename").text(filename) diff --git a/app/assets/javascripts/markdown_area.js.coffee b/app/assets/javascripts/markdown_area.js.coffee index e71fd1fbf3..516a40b25c 100644 --- a/app/assets/javascripts/markdown_area.js.coffee +++ b/app/assets/javascripts/markdown_area.js.coffee @@ -182,4 +182,4 @@ $(document).ready -> $(".div-dropzone").click() return - return \ No newline at end of file + return diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 91a4ccaba4..607b109dc0 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -376,7 +376,7 @@ class Notes ### replyToDiscussionNote: (e) => form = $(".js-new-note-form") - replyLink = $(e.target) + replyLink = $(e.target).closest(".js-discussion-reply-button") replyLink.hide() # insert the form after the button diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee index b37a78ac19..0e99921f89 100644 --- a/app/assets/javascripts/profile.js.coffee +++ b/app/assets/javascripts/profile.js.coffee @@ -27,4 +27,4 @@ $ -> filename = $(this).val().replace(/^.*[\\\/]/, '') form.find(".js-avatar-filename").text(filename) - $('.profile-groups-avatars').tooltip("placement": "top") \ No newline at end of file + $('.profile-groups-avatars').tooltip("placement": "top") diff --git a/app/assets/stylesheets/generic/buttons.scss b/app/assets/stylesheets/generic/buttons.scss index 36fc771a9d..046e5040fb 100644 --- a/app/assets/stylesheets/generic/buttons.scss +++ b/app/assets/stylesheets/generic/buttons.scss @@ -152,16 +152,16 @@ } &.btn-close { - color: #B94A48; - font-weight: bold; + color: $bg_danger; + border-color: $border_danger; &:hover { color: #B94A48; } } &.btn-reopen { - color: #468847; - font-weight: bold; + color: $bg_success; + border-color: $border_success; &:hover { color: #468847; } diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss index f00d024f38..9b14839011 100644 --- a/app/assets/stylesheets/sections/commits.scss +++ b/app/assets/stylesheets/sections/commits.scss @@ -177,10 +177,18 @@ li.commit { .commit-row-description { font-size: 14px; - border-left: 1px solid #e5e5e5; - padding: 0 15px 0 7px; + border-left: 1px solid #EEE; + padding: 10px 15px; margin: 5px 0 10px 5px; + background: #f9f9f9; display: none; + + pre { + border: none; + background: inherit; + padding: 0; + margin: 0; + } } .commit-row-info { diff --git a/app/assets/stylesheets/sections/groups.scss b/app/assets/stylesheets/sections/groups.scss index 60ec79acad..e49fe1a9dd 100644 --- a/app/assets/stylesheets/sections/groups.scss +++ b/app/assets/stylesheets/sections/groups.scss @@ -7,3 +7,7 @@ .member-search-form { float: left; } + +.milestone-row { + @include str-truncated(90%); +} diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/sections/header.scss index 29cb0f4b87..e0e0d60c38 100644 --- a/app/assets/stylesheets/sections/header.scss +++ b/app/assets/stylesheets/sections/header.scss @@ -53,13 +53,9 @@ header { font-size: 18px; .app_logo { margin-left: -15px; } + .title { - display: inline-block; - overflow: hidden; - text-overflow: ellipsis; - vertical-align: top; - white-space: nowrap; - max-width: 70%; + @include str-truncated(70%); } .navbar-collapse { @@ -130,6 +126,7 @@ header { margin: 0; margin-left: 5px; @include header-font; + @include str-truncated(37%); } .profile-pic { @@ -254,7 +251,7 @@ header { .search .search-input { width: 300px; &:focus { - width: 400px; + width: 330px; } } @@ -262,7 +259,7 @@ header { .search .search-input { width: 200px; &:focus { - width: 300px; + width: 230px; } } } diff --git a/app/assets/stylesheets/sections/tree.scss b/app/assets/stylesheets/sections/tree.scss index b08f94f55a..678a6cd716 100644 --- a/app/assets/stylesheets/sections/tree.scss +++ b/app/assets/stylesheets/sections/tree.scss @@ -28,6 +28,7 @@ } td { border-color: #F1F1F1 !important; + border-bottom: 1px solid; } &:hover { td { diff --git a/app/controllers/admin/broadcast_messages_controller.rb b/app/controllers/admin/broadcast_messages_controller.rb index 9a70ef9d19..e1643bb34b 100644 --- a/app/controllers/admin/broadcast_messages_controller.rb +++ b/app/controllers/admin/broadcast_messages_controller.rb @@ -6,7 +6,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController end def create - @broadcast_message = BroadcastMessage.new(params[:broadcast_message]) + @broadcast_message = BroadcastMessage.new(broadcast_message_params) if @broadcast_message.save redirect_to admin_broadcast_messages_path, notice: 'Broadcast Message was successfully created.' @@ -29,4 +29,11 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController def broadcast_messages @broadcast_messages ||= BroadcastMessage.order("starts_at DESC").page(params[:page]) end + + def broadcast_message_params + params.require(:broadcast_message).permit( + :alert_type, :color, :ends_at, :font, + :message, :starts_at + ) + end end diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 1a523d081d..0388997ec6 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -20,7 +20,7 @@ class Admin::GroupsController < Admin::ApplicationController end def create - @group = Group.new(params[:group]) + @group = Group.new(group_params) @group.path = @group.name.dup.parameterize if @group.name if @group.save @@ -32,7 +32,7 @@ class Admin::GroupsController < Admin::ApplicationController end def update - if @group.update_attributes(params[:group]) + if @group.update_attributes(group_params) redirect_to [:admin, @group], notice: 'Group was successfully updated.' else render "edit" @@ -56,4 +56,8 @@ class Admin::GroupsController < Admin::ApplicationController def group @group = Group.find_by(path: params[:id]) end + + def group_params + params.require(:group).permit(:name, :description, :path, :avatar) + end end diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb index c5bf76f8c3..0a463239d7 100644 --- a/app/controllers/admin/hooks_controller.rb +++ b/app/controllers/admin/hooks_controller.rb @@ -5,7 +5,7 @@ class Admin::HooksController < Admin::ApplicationController end def create - @hook = SystemHook.new(params[:hook]) + @hook = SystemHook.new(hook_params) if @hook.save redirect_to admin_hooks_path, notice: 'Hook was successfully created.' @@ -37,4 +37,8 @@ class Admin::HooksController < Admin::ApplicationController redirect_to :back end + + def hook_params + params.require(:hook).permit(:url) + end end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index f0040bf5e8..44c93471df 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -13,7 +13,7 @@ class Admin::UsersController < Admin::ApplicationController end def new - @user = User.build_user + @user = User.new end def edit @@ -37,15 +37,12 @@ class Admin::UsersController < Admin::ApplicationController end def create - admin = params[:user].delete("admin") - opts = { force_random_password: true, password_expires_at: Time.now } - @user = User.build_user(params[:user].merge(opts), as: :admin) - @user.admin = (admin && admin.to_i > 0) + @user = User.new(user_params.merge(opts)) @user.created_by_id = current_user.id @user.generate_password @user.skip_confirmation! @@ -62,19 +59,15 @@ class Admin::UsersController < Admin::ApplicationController end def update - admin = params[:user].delete("admin") - - if params[:user][:password].blank? - params[:user].delete(:password) - params[:user].delete(:password_confirmation) - end - - if admin.present? - user.admin = !admin.to_i.zero? + if params[:user][:password].present? + user_params.merge( + password: params[:user][:password], + password_confirmation: params[:user][:password_confirmation], + ) end respond_to do |format| - if user.update_attributes(params[:user], as: :admin) + if user.update_attributes(user_params) user.confirm! format.html { redirect_to [:admin, user], notice: 'User was successfully updated.' } format.json { head :ok } @@ -115,4 +108,13 @@ class Admin::UsersController < Admin::ApplicationController def user @user ||= User.find_by!(username: params[:id]) end + + def user_params + params.require(:user).permit( + :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 + ) + end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index ac0a61b621..6090c88534 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,6 +1,7 @@ require 'gon' class ApplicationController < ActionController::Base + before_filter :authenticate_user_from_token! before_filter :authenticate_user! before_filter :reject_blocked! before_filter :check_password_expiration @@ -28,6 +29,25 @@ class ApplicationController < ActionController::Base protected + # From https://github.com/plataformatec/devise/wiki/How-To:-Simple-Token-Authentication-Example + # https://gist.github.com/josevalim/fb706b1e933ef01e4fb6 + def authenticate_user_from_token! + user_token = if params[:authenticity_token].presence + params[:authenticity_token].presence + elsif params[:private_token].presence + params[:private_token].presence + end + user = user_token && User.find_by_authentication_token(user_token.to_s) + + if user + # Notice we are passing store false, so the user is not + # actually stored in the session and a token is needed + # for every request. If you want the token to work as a + # sign in token, you can simply remove store: false. + sign_in user, store: false + end + end + def log_exception(exception) application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace application_trace.map!{ |t| " #{t}\n" } @@ -227,8 +247,7 @@ class ApplicationController < ActionController::Base end def configure_permitted_parameters - devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:username, :email, :password, :login, :remember_me) } - devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:username, :email, :name, :password, :password_confirmation) } + devise_parameter_sanitizer.sanitize(:sign_in) { |u| u.permit(:username, :email, :password, :login, :remember_me) } end def hexdigest(string) diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb new file mode 100644 index 0000000000..860d8e0392 --- /dev/null +++ b/app/controllers/groups/milestones_controller.rb @@ -0,0 +1,56 @@ +class Groups::MilestonesController < ApplicationController + layout 'group' + + before_filter :authorize_group_milestone!, only: :update + + def index + project_milestones = case params[:status] + when 'all'; status + when 'closed'; status('closed') + else status('active') + end + @group_milestones = Milestones::GroupService.new(project_milestones).execute + @group_milestones = Kaminari.paginate_array(@group_milestones).page(params[:page]).per(30) + end + + def show + project_milestones = Milestone.where(project_id: group.projects).order("due_date ASC") + @group_milestone = Milestones::GroupService.new(project_milestones).milestone(title) + end + + def update + project_milestones = Milestone.where(project_id: group.projects).order("due_date ASC") + @group_milestones = Milestones::GroupService.new(project_milestones).milestone(title) + + @group_milestones.milestones.each do |milestone| + Milestones::UpdateService.new(milestone.project, current_user, params[:milestone]).execute(milestone) + end + + respond_to do |format| + format.js + format.html do + redirect_to group_milestones_path(group) + end + end + end + + private + + def group + @group ||= Group.find_by(path: params[:group_id]) + end + + def title + params[:title] + end + + def status(state = nil) + conditions = { project_id: group.projects } + conditions.reverse_merge!(state: state) if state + Milestone.where(conditions).order("title ASC") + end + + def authorize_group_milestone! + return render_404 unless can?(current_user, :manage_group, group) + end +end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 32419bb751..d586ad671e 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -22,7 +22,7 @@ class GroupsController < ApplicationController end def create - @group = Group.new(params[:group]) + @group = Group.new(group_params) @group.path = @group.name.dup.parameterize if @group.name if @group.save @@ -86,7 +86,7 @@ class GroupsController < ApplicationController end def update - if @group.update_attributes(params[:group]) + if @group.update_attributes(group_params) redirect_to edit_group_path(@group), notice: 'Group was successfully updated.' else render action: "edit" @@ -161,4 +161,8 @@ class GroupsController < ApplicationController params[:state] = 'opened' if params[:state].blank? params[:group_id] = @group.id end + + def group_params + params.require(:group).permit(:name, :description, :path, :avatar, :ldap_access, :ldap_cn) + end end diff --git a/app/controllers/profiles/emails_controller.rb b/app/controllers/profiles/emails_controller.rb index 40c352dab0..f3f0e69b83 100644 --- a/app/controllers/profiles/emails_controller.rb +++ b/app/controllers/profiles/emails_controller.rb @@ -7,7 +7,7 @@ class Profiles::EmailsController < ApplicationController end def create - @email = current_user.emails.new(params[:email]) + @email = current_user.emails.new(email_params) flash[:alert] = @email.errors.full_messages.first unless @email.save @@ -23,4 +23,10 @@ class Profiles::EmailsController < ApplicationController format.js { render nothing: true } end end + + private + + def email_params + params.require(:email).permit(:email) + end end diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index 4d830916f4..753e000c4c 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -15,7 +15,7 @@ class Profiles::KeysController < ApplicationController end def create - @key = current_user.keys.new(params[:key]) + @key = current_user.keys.new(key_params) if @key.save redirect_to profile_key_path(@key) @@ -53,4 +53,9 @@ class Profiles::KeysController < ApplicationController end end + private + + def key_params + params.require(:key).permit(:title, :key) + end end diff --git a/app/controllers/profiles/passwords_controller.rb b/app/controllers/profiles/passwords_controller.rb index df6954554e..0d93f5cbfd 100644 --- a/app/controllers/profiles/passwords_controller.rb +++ b/app/controllers/profiles/passwords_controller.rb @@ -11,8 +11,8 @@ class Profiles::PasswordsController < ApplicationController end def create - new_password = params[:user][:password] - new_password_confirmation = params[:user][:password_confirmation] + new_password = user_params[:password] + new_password_confirmation = user_params[:password_confirmation] result = @user.update_attributes( password: new_password, @@ -31,11 +31,11 @@ class Profiles::PasswordsController < ApplicationController end def update - password_attributes = params[:user].select do |key, value| + password_attributes = user_params.select do |key, value| %w(password password_confirmation).include?(key.to_s) end - unless @user.valid_password?(params[:user][:current_password]) + unless @user.valid_password?(user_params[:current_password]) redirect_to edit_profile_password_path, alert: 'You must provide a valid current password' return end @@ -74,4 +74,8 @@ class Profiles::PasswordsController < ApplicationController def authorize_change_password! return render_404 if @user.ldap_user? end + + def user_params + params.require(:user).permit(:current_password, :password, :password_confirmation) + end end diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 9c9a129b26..e877f9b904 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -14,9 +14,9 @@ class ProfilesController < ApplicationController end def update - params[:user].delete(:email) if @user.ldap_user? + user_params.except!(:email) if @user.ldap_user? - if @user.update_attributes(params[:user]) + if @user.update_attributes(user_params) flash[:notice] = "Profile was successfully updated" else flash[:alert] = "Failed to update profile" @@ -41,7 +41,7 @@ class ProfilesController < ApplicationController end def update_username - @user.update_attributes(username: params[:user][:username]) + @user.update_attributes(username: user_params[:username]) respond_to do |format| format.js @@ -57,4 +57,12 @@ class ProfilesController < ApplicationController def authorize_change_username! return render_404 unless @user.can_change_username? end + + def user_params + params.require(:user).permit( + :email, :password, :password_confirmation, :bio, :name, :username, + :skype, :linkedin, :twitter, :website_url, :color_scheme_id, :theme_id, + :avatar, :hide_no_ssh_key, + ) + end end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index a1a8bed09f..db3d173b98 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -30,8 +30,12 @@ class Projects::BlobController < Projects::ApplicationController def blob @blob ||= @repository.blob_at(@commit.id, @path) - return not_found! unless @blob - - @blob + if @blob + @blob + elsif tree.entries.any? + redirect_to project_tree_path(@project, File.join(@ref, @path)) and return + else + return not_found! + end end end diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb index 6e1a76ff41..d20937ea8e 100644 --- a/app/controllers/projects/deploy_keys_controller.rb +++ b/app/controllers/projects/deploy_keys_controller.rb @@ -22,7 +22,7 @@ class Projects::DeployKeysController < Projects::ApplicationController end def create - @key = DeployKey.new(params[:deploy_key]) + @key = DeployKey.new(deploy_key_params) if @key.valid? && @project.deploy_keys << @key redirect_to project_deploy_keys_path(@project) @@ -58,4 +58,8 @@ class Projects::DeployKeysController < Projects::ApplicationController def available_keys @available_keys ||= current_user.accessible_deploy_keys end + + def deploy_key_params + params.require(:deploy_key).permit(:key, :title) + end end diff --git a/app/controllers/projects/edit_tree_controller.rb b/app/controllers/projects/edit_tree_controller.rb index be611892bb..ca83b21f42 100644 --- a/app/controllers/projects/edit_tree_controller.rb +++ b/app/controllers/projects/edit_tree_controller.rb @@ -28,8 +28,6 @@ class Projects::EditTreeController < Projects::BaseTreeController def preview @content = params[:content] - #FIXME workaround https://github.com/gitlabhq/gitlabhq/issues/5936 - @content += "\n" if @blob.data.end_with?("\n") diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', include_diff_info: true) diff --git a/app/controllers/projects/git_hooks_controller.rb b/app/controllers/projects/git_hooks_controller.rb index c4eb39b53c..c774b4233a 100644 --- a/app/controllers/projects/git_hooks_controller.rb +++ b/app/controllers/projects/git_hooks_controller.rb @@ -14,7 +14,7 @@ class Projects::GitHooksController < Projects::ApplicationController def update @pre_receive_hook = project.git_hook - @pre_receive_hook.update_attributes(params[:git_hook]) + @pre_receive_hook.update_attributes(git_hook_params) if @pre_receive_hook.valid? redirect_to project_git_hooks_path(@project) @@ -22,4 +22,11 @@ class Projects::GitHooksController < Projects::ApplicationController render :index end end + + private + + # Only allow a trusted parameter "white list" through. + def git_hook_params + params.require(:git_hook).permit(:deny_delete_tag, :delete_branch_regex, :commit_message_regex, :force_push_regex) + end end diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index c43d26385f..268e19f26e 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -12,7 +12,7 @@ class Projects::HooksController < Projects::ApplicationController end def create - @hook = @project.hooks.new(params[:hook]) + @hook = @project.hooks.new(hook_params) @hook.save if @hook.valid? @@ -40,4 +40,8 @@ class Projects::HooksController < Projects::ApplicationController def hook @hook ||= @project.hooks.find(params[:id]) end + + def hook_params + params.require(:hook).permit(:url, :push_events, :issues_events, :merge_requests_events, :tag_push_events) + end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index ffe65cb41c..4dc9384c99 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -42,7 +42,11 @@ class Projects::IssuesController < Projects::ApplicationController end def new - @issue = @project.issues.new(params[:issue]) + params[:issue] ||= ActionController::Parameters.new( + assignee_id: "" + ) + + @issue = @project.issues.new(issue_params) respond_with(@issue) end @@ -59,7 +63,7 @@ class Projects::IssuesController < Projects::ApplicationController end def create - @issue = Issues::CreateService.new(project, current_user, params[:issue]).execute + @issue = Issues::CreateService.new(project, current_user, issue_params).execute respond_to do |format| format.html do @@ -76,7 +80,7 @@ class Projects::IssuesController < Projects::ApplicationController end def update - @issue = Issues::UpdateService.new(project, current_user, params[:issue]).execute(issue) + @issue = Issues::UpdateService.new(project, current_user, issue_params).execute(issue) respond_to do |format| format.js @@ -144,4 +148,11 @@ class Projects::IssuesController < Projects::ApplicationController raise ActiveRecord::RecordNotFound.new end end + + def issue_params + params.require(:issue).permit( + :title, :assignee_id, :position, :description, + :milestone_id, :label_list, :state_event + ) + end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 476b1fe4dd..f47ea5625f 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -60,7 +60,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def new - @merge_request = MergeRequest.new(params[:merge_request]) + params[:merge_request] ||= ActionController::Parameters.new( + source_project: @project + ) + + @merge_request = MergeRequest.new(merge_request_params) @merge_request.source_project = @project unless @merge_request.source_project @merge_request.target_project ||= (@project.forked_from_project || @project) @target_branches = @merge_request.target_project.nil? ? [] : @merge_request.target_project.repository.branch_names @@ -111,7 +115,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def create @target_branches ||= [] - @merge_request = MergeRequests::CreateService.new(project, current_user, params[:merge_request]).execute + @merge_request = MergeRequests::CreateService.new(project, current_user, merge_request_params).execute if @merge_request.valid? redirect_to project_merge_request_path(@merge_request.target_project, @merge_request), notice: 'Merge request was successfully created.' @@ -123,7 +127,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def update - @merge_request = MergeRequests::UpdateService.new(project, current_user, params[:merge_request]).execute(@merge_request) + @merge_request = MergeRequests::UpdateService.new(project, current_user, merge_request_params).execute(@merge_request) if @merge_request.valid? respond_to do |format| @@ -264,4 +268,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController can?(current_user, action, project) end + + def merge_request_params + params.require(:merge_request).permit( + :title, :assignee_id, :source_project_id, :source_branch, + :target_project_id, :target_branch, :milestone_id, + :state_event, :description, :label_list + ) + end end diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index c38c77d6b8..d338cdedfa 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -37,7 +37,7 @@ class Projects::MilestonesController < Projects::ApplicationController end def create - @milestone = Milestones::CreateService.new(project, current_user, params[:milestone]).execute + @milestone = Milestones::CreateService.new(project, current_user, milestone_params).execute if @milestone.save redirect_to project_milestone_path(@project, @milestone) @@ -47,7 +47,7 @@ class Projects::MilestonesController < Projects::ApplicationController end def update - @milestone = Milestones::UpdateService.new(project, current_user, params[:milestone]).execute(milestone) + @milestone = Milestones::UpdateService.new(project, current_user, milestone_params).execute(milestone) respond_to do |format| format.js @@ -105,4 +105,8 @@ class Projects::MilestonesController < Projects::ApplicationController def module_enabled return render_404 unless @project.issues_enabled end + + def milestone_params + params.require(:milestone).permit(:title, :description, :due_date, :state_event) + end end diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 66cc1a3dec..2154b6ed2e 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -21,7 +21,7 @@ class Projects::NotesController < Projects::ApplicationController end def create - @note = Notes::CreateService.new(project, current_user, params[:note]).execute + @note = Notes::CreateService.new(project, current_user, note_params).execute respond_to do |format| format.json { render_note_json(@note) } @@ -30,7 +30,7 @@ class Projects::NotesController < Projects::ApplicationController end def update - note.update_attributes(params[:note]) + note.update_attributes(note_params) note.reset_events_cache respond_to do |format| @@ -109,4 +109,11 @@ class Projects::NotesController < Projects::ApplicationController def authorize_admin_note! return access_denied! unless can?(current_user, :admin_note, note) end + + def note_params + params.require(:note).permit( + :note, :noteable, :noteable_id, :noteable_type, :project_id, + :attachment, :line_code, :commit_id + ) + end end diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb index e39e97af8d..bd31b1d3c5 100644 --- a/app/controllers/projects/protected_branches_controller.rb +++ b/app/controllers/projects/protected_branches_controller.rb @@ -11,7 +11,7 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController end def create - @project.protected_branches.create(params[:protected_branch]) + @project.protected_branches.create(protected_branch_params) redirect_to project_protected_branches_path(@project) end @@ -23,4 +23,10 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController format.js { render nothing: true } end end + + private + + def protected_branch_params + params.require(:protected_branch).permit(:name) + end end diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb index 28fed8b0e3..f76ddb34bc 100644 --- a/app/controllers/projects/repositories_controller.rb +++ b/app/controllers/projects/repositories_controller.rb @@ -22,6 +22,7 @@ class Projects::RepositoriesController < Projects::ApplicationController if file_path # Send file to user + response.headers["Content-Length"] = File.open(file_path).size.to_s send_file file_path else render_404 diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 6db22186c1..0a9731a6a7 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -16,7 +16,7 @@ class Projects::ServicesController < Projects::ApplicationController end def update - if @service.update_attributes(params[:service]) + if @service.update_attributes(service_params) redirect_to edit_project_service_path(@project, @service.to_param) else render 'edit' @@ -36,4 +36,11 @@ class Projects::ServicesController < Projects::ApplicationController def service @service ||= @project.services.find { |service| service.to_param == params[:id] } end + + def service_params + params.require(:service).permit( + :title, :token, :type, :active, :api_key, :subdomain, + :room, :recipients, :project_url, :username, :password, :api_version + ) + end end diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index f93f2d5f9b..2502697311 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -25,7 +25,7 @@ class Projects::SnippetsController < Projects::ApplicationController end def create - @snippet = @project.snippets.build(params[:project_snippet]) + @snippet = @project.snippets.build(snippet_params) @snippet.author = current_user if @snippet.save @@ -39,7 +39,7 @@ class Projects::SnippetsController < Projects::ApplicationController end def update - if @snippet.update_attributes(params[:project_snippet]) + if @snippet.update_attributes(snippet_params) redirect_to project_snippet_path(@project, @snippet) else respond_with(@snippet) @@ -86,4 +86,8 @@ class Projects::SnippetsController < Projects::ApplicationController def module_enabled return render_404 unless @project.snippets_enabled end + + def snippet_params + params.require(:project_snippet).permit(:title, :content, :file_name, :private) + end end diff --git a/app/controllers/projects/team_members_controller.rb b/app/controllers/projects/team_members_controller.rb index 44068878cd..1de5bac9ee 100644 --- a/app/controllers/projects/team_members_controller.rb +++ b/app/controllers/projects/team_members_controller.rb @@ -27,7 +27,7 @@ class Projects::TeamMembersController < Projects::ApplicationController def update @user_project_relation = project.users_projects.find_by(user_id: member) - @user_project_relation.update_attributes(params[:team_member]) + @user_project_relation.update_attributes(member_params) unless @user_project_relation.valid? flash[:alert] = "User should have at least one role" @@ -67,4 +67,8 @@ class Projects::TeamMembersController < Projects::ApplicationController def member @member ||= User.find_by(username: params[:id]) end + + def member_params + params.require(:team_member).permit(:user_id, :project_access) + end end diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index 30c94ec6da..4d033b3684 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -1,7 +1,14 @@ # Controller for viewing a repository's file structure class Projects::TreeController < Projects::BaseTreeController def show - return not_found! if tree.entries.empty? + + if tree.entries.empty? + if @repository.blob_at(@commit.id, @path) + redirect_to project_blob_path(@project, File.join(@ref, @path)) and return + else + return not_found! + end + end respond_to do |format| format.html diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 0d15b458b7..b39e689342 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -20,7 +20,7 @@ class ProjectsController < ApplicationController end def create - @project = ::Projects::CreateService.new(current_user, params[:project]).execute + @project = ::Projects::CreateService.new(current_user, project_params).execute flash[:notice] = 'Project was successfully created.' if @project.saved? respond_to do |format| @@ -29,7 +29,7 @@ class ProjectsController < ApplicationController end def update - status = ::Projects::UpdateService.new(@project, current_user, params).execute + status = ::Projects::UpdateService.new(@project, current_user, project_params).execute respond_to do |format| if status @@ -44,7 +44,7 @@ class ProjectsController < ApplicationController end def transfer - ::Projects::TransferService.new(project, current_user, params[:project]).execute + ::Projects::TransferService.new(project, current_user, project_params).execute end def show @@ -85,7 +85,7 @@ class ProjectsController < ApplicationController redirect_to import_project_path(@project) end - @project.import_url = params[:project][:import_url] + @project.import_url = project_params[:import_url] if @project.save @project.reload @@ -185,4 +185,12 @@ class ProjectsController < ApplicationController def user_layout current_user ? "projects" : "public_projects" end + + def project_params + params.require(:project).permit( + :name, :path, :description, :issues_tracker, :label_list, + :issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, + :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :merge_requests_template + ) + end end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 5f18bac82e..9e70978992 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -13,7 +13,14 @@ class RegistrationsController < Devise::RegistrationsController def build_resource(hash=nil) super - self.resource.with_defaults + end + + def after_sign_up_path_for resource + new_user_session_path + end + + def after_inactive_sign_up_path_for resource + new_user_session_path end private @@ -21,4 +28,8 @@ class RegistrationsController < Devise::RegistrationsController def signup_enabled? redirect_to new_user_session_path unless Gitlab.config.gitlab.signup_enabled end + + def sign_up_params + params.require(:user).permit(:username, :email, :name, :password, :password_confirmation) + end end diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 4fe98f804d..e75db61e68 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -51,7 +51,7 @@ class SnippetsController < ApplicationController end def create - @snippet = PersonalSnippet.new(params[:personal_snippet]) + @snippet = PersonalSnippet.new(snippet_params) @snippet.author = current_user if @snippet.save @@ -65,7 +65,7 @@ class SnippetsController < ApplicationController end def update - if @snippet.update_attributes(params[:personal_snippet]) + if @snippet.update_attributes(snippet_params) redirect_to snippet_path(@snippet) else respond_with @snippet @@ -109,4 +109,8 @@ class SnippetsController < ApplicationController def set_title @title = 'Snippets' end + + def snippet_params + params.require(:personal_snippet).permit(:title, :content, :file_name, :private) + end end diff --git a/app/controllers/users_groups_controller.rb b/app/controllers/users_groups_controller.rb index b9bdc18952..a35a12a866 100644 --- a/app/controllers/users_groups_controller.rb +++ b/app/controllers/users_groups_controller.rb @@ -14,7 +14,7 @@ class UsersGroupsController < ApplicationController def update @member = @group.users_groups.find(params[:id]) - @member.update_attributes(params[:users_group]) + @member.update_attributes(member_params) end def destroy @@ -41,4 +41,8 @@ class UsersGroupsController < ApplicationController return render_404 end end + + def member_params + params.require(:users_group).permit(:group_access, :user_id) + end end diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb index ea055694cd..bef82d7f0f 100644 --- a/app/finders/notes_finder.rb +++ b/app/finders/notes_finder.rb @@ -14,7 +14,7 @@ class NotesFinder project.issues.find(target_id).notes.inc_author.fresh when "merge_request" project.merge_requests.find(target_id).mr_and_commit_notes.inc_author.fresh - when "snippet" + when "snippet", "project_snippet" project.snippets.find(target_id).notes.fresh else raise 'invalid target_type' diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 4d27cf2851..7100d67935 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -180,6 +180,17 @@ module CommitsHelper return old_lines, new_lines end + def link_to_browse_code(project, commit) + if current_controller?(:projects, :commits) + if @repo.blob_at(commit.id, @path) + return link_to "Browse File »", project_blob_path(project, tree_join(commit.id, @path)), class: "pull-right" + elsif @path.present? + return link_to "Browse Dir »", project_tree_path(project, tree_join(commit.id, @path)), class: "pull-right" + end + end + link_to "Browse Code »", project_tree_path(project, commit), class: "pull-right" + end + protected # Private: Returns a link to a person. If the person has a matching user and diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb index d5712ab337..c4e33e3308 100644 --- a/app/helpers/dashboard_helper.rb +++ b/app/helpers/dashboard_helper.rb @@ -35,4 +35,42 @@ module DashboardHelper path << "?#{options.to_param}" path end + + def assigned_entities_count(current_user, entity, scope = nil) + items = current_user.send("assigned_" + entity.pluralize).opened + + if scope.kind_of?(Group) + items = items.of_group(scope) + elsif scope.kind_of?(Project) + items = items.of_projects(scope) + end + + items.count + end + + def authored_entities_count(current_user, entity, scope = nil) + items = current_user.send(entity.pluralize).opened + + if scope.kind_of?(Group) + items = items.of_group(scope) + elsif scope.kind_of?(Project) + items = items.of_projects(scope) + end + + items.count + end + + def authorized_entities_count(current_user, entity, scope = nil) + items = entity.classify.constantize.opened + + if scope.kind_of?(Group) + items = items.of_group(scope) + elsif scope.kind_of?(Project) + items = items.of_projects(scope) + else + items = items.of_projects(current_user.authorized_projects) + end + + items.count + end end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index f0530c7482..c7e8fdad7a 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -64,7 +64,16 @@ module EventsHelper project_issue_url(event.project, event.issue) elsif event.merge_request? project_merge_request_url(event.project, event.merge_request) - + elsif event.note? + if event.note_target + if event.note_commit? + project_commit_path(event.project, event.note_commit_id, anchor: dom_id(event.target)) + elsif event.note_project_snippet? + project_snippet_path(event.project, event.note_target) + else + event_note_target_path(event) + end + end elsif event.push? if event.push_with_commits? if event.commits_count > 1 @@ -83,6 +92,10 @@ module EventsHelper render "events/event_issue", issue: event.issue elsif event.push? render "events/event_push", event: event + elsif event.merge_request? + render "events/event_merge_request", merge_request: event.merge_request + elsif event.note? + render "events/event_note", note: event.note end end diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index c7f769cee6..c112e98508 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -138,7 +138,7 @@ module GitlabMarkdownHelper # If we are at doc/api/README.md and the README.md contains relative links like [Users](users.md) # this takes the request path(doc/api/README.md), and replaces the README.md with users.md so the path looks like doc/api/users.md # If we are at doc/api and the README.md shown in below the tree view - # this takes the rquest path(doc/api) and adds users.md so the path looks like doc/api/users.md + # this takes the request path(doc/api) and adds users.md so the path looks like doc/api/users.md def build_nested_path(path, request_path) return request_path if path == "" return path unless request_path diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index cfc9a572ca..0dc53dedeb 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -31,6 +31,17 @@ module GroupsHelper end 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 end diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb index a096df9dc0..e534623596 100644 --- a/app/mailers/emails/issues.rb +++ b/app/mailers/emails/issues.rb @@ -4,10 +4,10 @@ module Emails @issue = Issue.find(issue_id) @project = @issue.project @target_url = project_issue_url(@project, @issue) - set_message_id("issue_#{issue_id}") - mail(from: sender(@issue.author_id), - to: recipient(recipient_id), - subject: subject("#{@issue.title} (##{@issue.iid})")) + mail_new_thread(@issue, + from: sender(@issue.author_id), + to: recipient(recipient_id), + subject: subject("#{@issue.title} (##{@issue.iid})")) end def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id, updated_by_user_id) @@ -15,10 +15,10 @@ module Emails @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id @project = @issue.project @target_url = project_issue_url(@project, @issue) - set_reference("issue_#{issue_id}") - mail(from: sender(updated_by_user_id), - to: recipient(recipient_id), - subject: subject("#{@issue.title} (##{@issue.iid})")) + mail_answer_thread(@issue, + from: sender(updated_by_user_id), + to: recipient(recipient_id), + subject: subject("#{@issue.title} (##{@issue.iid})")) end def closed_issue_email(recipient_id, issue_id, updated_by_user_id) @@ -26,10 +26,10 @@ module Emails @project = @issue.project @updated_by = User.find updated_by_user_id @target_url = project_issue_url(@project, @issue) - set_reference("issue_#{issue_id}") - mail(from: sender(updated_by_user_id), - to: recipient(recipient_id), - subject: subject("#{@issue.title} (##{@issue.iid})")) + mail_answer_thread(@issue, + from: sender(updated_by_user_id), + to: recipient(recipient_id), + subject: subject("#{@issue.title} (##{@issue.iid})")) end def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id) @@ -38,10 +38,10 @@ module Emails @project = @issue.project @updated_by = User.find updated_by_user_id @target_url = project_issue_url(@project, @issue) - set_reference("issue_#{issue_id}") - mail(from: sender(updated_by_user_id), - to: recipient(recipient_id), - subject: subject("#{@issue.title} (##{@issue.iid})")) + mail_answer_thread(@issue, + from: sender(updated_by_user_id), + to: recipient(recipient_id), + subject: subject("#{@issue.title} (##{@issue.iid})")) end end end diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index ea5671c450..935987e2ed 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -4,10 +4,10 @@ module Emails @merge_request = MergeRequest.find(merge_request_id) @project = @merge_request.project @target_url = project_merge_request_url(@project, @merge_request) - set_message_id("merge_request_#{merge_request_id}") - mail(from: sender(@merge_request.author_id), - to: recipient(recipient_id), - subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) + mail_new_thread(@merge_request, + from: sender(@merge_request.author_id), + to: recipient(recipient_id), + subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) end def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id) @@ -15,10 +15,10 @@ module Emails @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id @project = @merge_request.project @target_url = project_merge_request_url(@project, @merge_request) - set_reference("merge_request_#{merge_request_id}") - mail(from: sender(updated_by_user_id), - to: recipient(recipient_id), - subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) + mail_answer_thread(@merge_request, + from: sender(updated_by_user_id), + to: recipient(recipient_id), + subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) end def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) @@ -26,20 +26,32 @@ module Emails @updated_by = User.find updated_by_user_id @project = @merge_request.project @target_url = project_merge_request_url(@project, @merge_request) - set_reference("merge_request_#{merge_request_id}") - mail(from: sender(updated_by_user_id), - to: recipient(recipient_id), - subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) + mail_answer_thread(@merge_request, + from: sender(updated_by_user_id), + to: recipient(recipient_id), + subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) end def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) @merge_request = MergeRequest.find(merge_request_id) @project = @merge_request.project @target_url = project_merge_request_url(@project, @merge_request) + mail_answer_thread(@merge_request, + from: sender(updated_by_user_id), + to: recipient(recipient_id), + subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) + end + + def merge_request_status_email(recipient_id, merge_request_id, status, updated_by_user_id) + @merge_request = MergeRequest.find(merge_request_id) + @mr_status = status + @project = @merge_request.project + @updated_by = User.find updated_by_user_id + @target_url = project_merge_request_url(@project, @merge_request) set_reference("merge_request_#{merge_request_id}") mail(from: sender(updated_by_user_id), to: recipient(recipient_id), - subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) + subject: subject("#{@merge_request.title} (##{@merge_request.iid}) #{@mr_status}")) end end diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index b7fedbd370..ef9af726a6 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -5,9 +5,10 @@ module Emails @commit = @note.noteable @project = @note.project @target_url = project_commit_url(@project, @commit, anchor: "note_#{@note.id}") - mail(from: sender(@note.author_id), - to: recipient(recipient_id), - subject: subject("#{@commit.title} (#{@commit.short_id})")) + mail_answer_thread(@commit, + from: sender(@note.author_id), + to: recipient(recipient_id), + subject: subject("#{@commit.title} (#{@commit.short_id})")) end def note_issue_email(recipient_id, note_id) @@ -15,10 +16,10 @@ module Emails @issue = @note.noteable @project = @note.project @target_url = project_issue_url(@project, @issue, anchor: "note_#{@note.id}") - set_reference("issue_#{@issue.id}") - mail(from: sender(@note.author_id), - to: recipient(recipient_id), - subject: subject("#{@issue.title} (##{@issue.iid})")) + mail_answer_thread(@issue, + from: sender(@note.author_id), + to: recipient(recipient_id), + subject: subject("#{@issue.title} (##{@issue.iid})")) end def note_merge_request_email(recipient_id, note_id) @@ -26,10 +27,10 @@ module Emails @merge_request = @note.noteable @project = @note.project @target_url = project_merge_request_url(@project, @merge_request, anchor: "note_#{@note.id}") - set_reference("merge_request_#{@merge_request.id}") - mail(from: sender(@note.author_id), - to: recipient(recipient_id), - subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) + mail_answer_thread(@merge_request, + from: sender(@note.author_id), + to: recipient(recipient_id), + subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) end end end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 84a0da0129..bd438bab89 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -1,4 +1,6 @@ class Notify < ActionMailer::Base + include ActionDispatch::Routing::PolymorphicRoutes + include Emails::Issues include Emails::MergeRequests include Emails::Notes @@ -53,14 +55,6 @@ class Notify < ActionMailer::Base end end - # Set the Message-ID header field - # - # local_part - The local part of the message ID - # - def set_message_id(local_part) - headers["Message-ID"] = "<#{local_part}@#{Gitlab.config.gitlab.host}>" - end - # Set the References header field # # local_part - The local part of the referenced message ID @@ -93,4 +87,40 @@ class Notify < ActionMailer::Base subject << extra.join(' | ') if extra.present? subject end + + # Return a string suitable for inclusion in the 'Message-Id' mail header. + # + # The message-id is generated from the unique URL to a model object. + def message_id(model) + model_name = model.class.model_name.singular_route_key + "<#{model_name}_#{model.id}@#{Gitlab.config.gitlab.host}>" + end + + # Send an email that starts a new conversation thread, + # with headers suitable for grouping by thread in email clients. + # + # See: mail_answer_thread + def mail_new_thread(model, headers = {}, &block) + headers['Message-ID'] = message_id(model) + mail(headers, &block) + end + + # Send an email that responds to an existing conversation thread, + # with headers suitable for grouping by thread in email clients. + # + # For grouping emails by thread, email clients heuristics require the answers to: + # + # * have a subject that begin by 'Re: ' + # * have a 'In-Reply-To' or 'References' header that references the original 'Message-ID' + # + def mail_answer_thread(model, headers = {}, &block) + headers['In-Reply-To'] = message_id(model) + headers['References'] = message_id(model) + + if (headers[:subject]) + headers[:subject].prepend('Re: ') + end + + mail(headers, &block) + end end diff --git a/app/models/appearance.rb b/app/models/appearance.rb index 05809f4a07..5d8edf4990 100644 --- a/app/models/appearance.rb +++ b/app/models/appearance.rb @@ -1,6 +1,4 @@ class Appearance < ActiveRecord::Base - attr_accessible :title, :description, :logo - validates :title, presence: true validates :description, presence: true validates :logo, file_size: { maximum: 1000.kilobytes.to_i } diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb index ce8b7973cd..4d0c04bcc3 100644 --- a/app/models/broadcast_message.rb +++ b/app/models/broadcast_message.rb @@ -14,8 +14,6 @@ # class BroadcastMessage < ActiveRecord::Base - attr_accessible :alert_type, :color, :ends_at, :font, :message, :starts_at - validates :message, presence: true validates :starts_at, presence: true validates :ends_at, presence: true diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb new file mode 100644 index 0000000000..9b88ec1cc3 --- /dev/null +++ b/app/models/concerns/token_authenticatable.rb @@ -0,0 +1,31 @@ +module TokenAuthenticatable + extend ActiveSupport::Concern + + module ClassMethods + def find_by_authentication_token(authentication_token = nil) + if authentication_token + where(authentication_token: authentication_token).first + end + end + end + + def ensure_authentication_token + if authentication_token.blank? + self.authentication_token = generate_authentication_token + end + end + + def reset_authentication_token! + self.authentication_token = generate_authentication_token + save + end + + private + + def generate_authentication_token + loop do + token = Devise.friendly_token + break token unless self.class.unscoped.where(authentication_token: token).first + end + end +end diff --git a/app/models/deploy_keys_project.rb b/app/models/deploy_keys_project.rb index 739d749830..f23d8205dd 100644 --- a/app/models/deploy_keys_project.rb +++ b/app/models/deploy_keys_project.rb @@ -10,13 +10,10 @@ # class DeployKeysProject < ActiveRecord::Base - attr_accessible :key_id, :project_id - belongs_to :project belongs_to :deploy_key validates :deploy_key_id, presence: true validates :deploy_key_id, uniqueness: { scope: [:project_id], message: "already exists in project" } - validates :project_id, presence: true end diff --git a/app/models/email.rb b/app/models/email.rb index 9068c2b87b..57f476bd51 100644 --- a/app/models/email.rb +++ b/app/models/email.rb @@ -10,16 +10,8 @@ # class Email < ActiveRecord::Base - attr_accessible :email, :user_id - - # - # Relations - # belongs_to :user - # - # Validations - # validates :user_id, presence: true validates :email, presence: true, email: { strict_mode: true }, uniqueness: true validate :unique_email, if: ->(email) { email.email_changed? } diff --git a/app/models/event.rb b/app/models/event.rb index 1a8d55c54b..8681c2e4d9 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -15,9 +15,6 @@ # class Event < ActiveRecord::Base - attr_accessible :project, :action, :data, :author_id, :project_id, - :target_id, :target_type - default_scope { where.not(author_id: nil) } CREATED = 1 @@ -33,6 +30,7 @@ class Event < ActiveRecord::Base delegate :name, :email, to: :author, prefix: true, allow_nil: true delegate :title, to: :issue, prefix: true, allow_nil: true delegate :title, to: :merge_request, prefix: true, allow_nil: true + delegate :title, to: :note, prefix: true, allow_nil: true belongs_to :author, class_name: "User" belongs_to :project @@ -150,6 +148,10 @@ class Event < ActiveRecord::Base target if target_type == "MergeRequest" end + def note + target if target_type == "Note" + end + def action_name if closed? "closed" diff --git a/app/models/forked_project_link.rb b/app/models/forked_project_link.rb index 17add270f6..9b0c6263a9 100644 --- a/app/models/forked_project_link.rb +++ b/app/models/forked_project_link.rb @@ -10,10 +10,6 @@ # class ForkedProjectLink < ActiveRecord::Base - attr_accessible :forked_from_project_id, :forked_to_project_id - - # Relations belongs_to :forked_to_project, class_name: Project belongs_to :forked_from_project, class_name: Project - end diff --git a/app/models/git_hook.rb b/app/models/git_hook.rb index 59a405a0b9..f45f57aa99 100644 --- a/app/models/git_hook.rb +++ b/app/models/git_hook.rb @@ -1,6 +1,4 @@ class GitHook < ActiveRecord::Base - attr_accessible :deny_delete_tag, :delete_branch_regex, :commit_message_regex, :force_push_regex - belongs_to :project validates :project, presence: true diff --git a/app/models/group.rb b/app/models/group.rb index 27905d1b6f..a3a4110a03 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -19,19 +19,14 @@ require 'file_size_validator' class Group < Namespace has_many :users_groups, dependent: :destroy has_many :users, through: :users_groups - has_many :project_group_links, dependent: :destroy has_many :shared_projects, through: :project_group_links, source: :project - attr_accessible :ldap_cn, :ldap_access - validates :ldap_access, inclusion: { in: UsersGroup.group_access_roles.values }, presence: true, if: ->(group) { group.ldap_cn.present? } - attr_accessible :avatar - validate :avatar_type, if: ->(user) { user.avatar_changed? } validates :avatar, file_size: { maximum: 100.kilobytes.to_i } diff --git a/app/models/group_milestone.rb b/app/models/group_milestone.rb new file mode 100644 index 0000000000..013f8db00a --- /dev/null +++ b/app/models/group_milestone.rb @@ -0,0 +1,95 @@ +class GroupMilestone + + def initialize(title, milestones) + @title = title + @milestones = milestones + end + + def title + @title + end + + def safe_title + @title.parameterize + end + + def milestones + @milestones + end + + def projects + milestones.map { |milestone| milestone.project } + end + + def issue_count + milestones.map { |milestone| milestone.issues.count }.sum + end + + def merge_requests_count + milestones.map { |milestone| milestone.merge_requests.count }.sum + end + + def open_items_count + milestones.map { |milestone| milestone.open_items_count }.sum + end + + def closed_items_count + milestones.map { |milestone| milestone.closed_items_count }.sum + end + + def total_items_count + milestones.map { |milestone| milestone.total_items_count }.sum + end + + def percent_complete + ((closed_items_count * 100) / total_items_count).abs + rescue ZeroDivisionError + 100 + end + + def state + state = milestones.map { |milestone| milestone.state } + + if state.count('active') == state.size + 'active' + else + 'closed' + end + end + + def active? + state == 'active' + end + + def closed? + state == 'closed' + end + + def issues + @group_issues ||= milestones.map { |milestone| milestone.issues }.flatten.group_by(&:state) + end + + def merge_requests + @group_merge_requests ||= milestones.map { |milestone| milestone.merge_requests }.flatten.group_by(&:state) + end + + def participants + milestones.map { |milestone| milestone.participants.uniq }.reject(&:empty?).flatten + end + + def opened_issues + issues.values_at("opened", "reopened").compact.flatten + end + + def closed_issues + issues['closed'] + end + + def opened_merge_requests + merge_requests.values_at("opened", "reopened").compact.flatten + end + + def closed_merge_requests + merge_requests.values_at("closed", "merged", "locked").compact.flatten + end +end diff --git a/app/models/issue.rb b/app/models/issue.rb index f0c2e55227..a116a9354c 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -33,9 +33,6 @@ class Issue < ActiveRecord::Base scope :of_group, ->(group) { where(project_id: group.project_ids) } scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) } - attr_accessible :title, :assignee_id, :position, :description, - :milestone_id, :label_list, :state_event - acts_as_taggable_on :labels scope :cared, ->(user) { where(assignee_id: user) } diff --git a/app/models/key.rb b/app/models/key.rb index 3ee89259b3..9b8c7aa46c 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -19,8 +19,6 @@ class Key < ActiveRecord::Base belongs_to :user - attr_accessible :key, :title - before_validation :strip_white_space, :generate_fingerpint validates :title, presence: true, length: { within: 0..255 } diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index bfea209bf6..597e02d498 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -36,10 +36,6 @@ class MergeRequest < ActiveRecord::Base delegate :commits, :diffs, :last_commit, :last_commit_short_sha, to: :merge_request_diff, prefix: nil - attr_accessible :title, :assignee_id, :source_project_id, :source_branch, - :target_project_id, :target_branch, :milestone_id, - :state_event, :description, :label_list - attr_accessor :should_remove_source_branch # When this attribute is true some MR validation is ignored @@ -62,11 +58,11 @@ class MergeRequest < ActiveRecord::Base transition closed: :reopened end - event :lock do + event :lock_mr do transition [:reopened, :opened] => :locked end - event :unlock do + event :unlock_mr do transition locked: :reopened end @@ -297,6 +293,8 @@ class MergeRequest < ActiveRecord::Base message << title.to_s message << "\n\n" message << description.to_s + message << "\n\n" + message << "See merge request !#{iid}" message end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 7dce71a677..d3c07555b0 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -22,8 +22,6 @@ class MergeRequestDiff < ActiveRecord::Base belongs_to :merge_request - attr_accessible :state, :st_commits, :st_diffs - delegate :target_branch, :source_branch, to: :merge_request, prefix: nil state_machine :state, initial: :empty do diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 39ab0b536a..8fd3e56d2e 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -16,8 +16,6 @@ class Milestone < ActiveRecord::Base include InternalId - attr_accessible :title, :description, :due_date, :state_event - belongs_to :project has_many :issues has_many :merge_requests diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 446e5f04c6..b19b72906e 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -16,8 +16,6 @@ class Namespace < ActiveRecord::Base include Gitlab::ShellAdapter - attr_accessible :name, :description, :path - has_many :projects, dependent: :destroy belongs_to :owner, class_name: "User" @@ -25,12 +23,12 @@ class Namespace < ActiveRecord::Base validates :name, presence: true, uniqueness: true, length: { within: 0..255 }, format: { with: Gitlab::Regex.name_regex, - message: "only letters, digits, spaces & '_' '-' '.' allowed." } + message: Gitlab::Regex.name_regex_message } validates :description, length: { within: 0..255 } validates :path, uniqueness: { case_sensitive: false }, presence: true, length: { within: 1..255 }, exclusion: { in: Gitlab::Blacklist.path }, format: { with: Gitlab::Regex.path_regex, - message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } + message: Gitlab::Regex.path_regex_message } delegate :name, to: :owner, allow_nil: true, prefix: true diff --git a/app/models/note.rb b/app/models/note.rb index 94d45aa43d..ed4829b2b3 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -25,8 +25,6 @@ class Note < ActiveRecord::Base default_value_for :system, false - attr_accessible :note, :noteable, :noteable_id, :noteable_type, :project_id, - :attachment, :line_code, :commit_id attr_mentionable :note belongs_to :project @@ -63,13 +61,13 @@ class Note < ActiveRecord::Base def create_status_change_note(noteable, project, author, status, source) body = "_Status changed to #{status}#{' by ' + source.gfm_reference if source}_" - create({ + create( noteable: noteable, project: project, author: author, note: body, system: true - }, without_protection: true) + ) end # +noteable+ was referenced from +mentioner+, by including GFM in either +mentioner+'s description or an associated Note. @@ -88,7 +86,7 @@ class Note < ActiveRecord::Base note_options.merge!(noteable: noteable) end - create(note_options, without_protection: true) + create(note_options) end def create_milestone_change_note(noteable, project, author, milestone) @@ -98,13 +96,13 @@ class Note < ActiveRecord::Base "_Milestone changed to #{milestone.title}_" end - create({ + create( noteable: noteable, project: project, author: author, note: body, system: true - }, without_protection: true) + ) end def create_assignee_change_note(noteable, project, author, assignee) @@ -116,7 +114,7 @@ class Note < ActiveRecord::Base author: author, note: body, system: true - }, without_protection: true) + }) end def discussions_from_notes(notes) diff --git a/app/models/project.rb b/app/models/project.rb index be6bba4dbb..a2247ba1d4 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -27,24 +27,20 @@ class Project < ActiveRecord::Base include Gitlab::ShellAdapter include Gitlab::VisibilityLevel + include Gitlab::ConfigHelper + extend Gitlab::ConfigHelper extend Enumerize default_value_for :archived, false - default_value_for :issues_enabled, true - default_value_for :merge_requests_enabled, true - default_value_for :wiki_enabled, true + default_value_for :visibility_level, gitlab_config_features.visibility_level + default_value_for :issues_enabled, gitlab_config_features.issues + default_value_for :merge_requests_enabled, gitlab_config_features.merge_requests + default_value_for :wiki_enabled, gitlab_config_features.wiki default_value_for :wall_enabled, false - default_value_for :snippets_enabled, true + default_value_for :snippets_enabled, gitlab_config_features.snippets ActsAsTaggableOn.strict_case_match = true - attr_accessible :name, :path, :description, :issues_tracker, :label_list, - :issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, - :wiki_enabled, :visibility_level, :import_url, :last_activity_at, - :merge_requests_template, as: [:default, :admin] - - attr_accessible :namespace_id, :creator_id, as: :admin - acts_as_taggable_on :labels, :issues_default_labels attr_accessor :new_default_branch @@ -101,13 +97,16 @@ class Project < ActiveRecord::Base validates :description, length: { maximum: 2000 }, allow_blank: true validates :name, presence: true, length: { within: 0..255 }, format: { with: Gitlab::Regex.project_name_regex, - message: "only letters, digits, spaces & '_' '-' '.' allowed. Letter or digit should be first" } + message: Gitlab::Regex.project_regex_message } validates :path, presence: true, length: { within: 0..255 }, exclusion: { in: Gitlab::Blacklist.path }, format: { with: Gitlab::Regex.path_regex, - message: "only letters, digits & '_' '-' '.' allowed. Letter or digit should be first" } + message: Gitlab::Regex.path_regex_message } validates :issues_enabled, :merge_requests_enabled, :wiki_enabled, inclusion: { in: [true, false] } + validates :visibility_level, + exclusion: { in: gitlab_config.restricted_visibility_levels }, + if: -> { gitlab_config.restricted_visibility_levels.any? } validates :issues_tracker_id, length: { maximum: 255 }, allow_blank: true validates :namespace, presence: true validates_uniqueness_of :name, scope: :namespace_id @@ -251,7 +250,7 @@ class Project < ActiveRecord::Base end def check_limit - unless creator.can_create_project? + unless creator.can_create_project? or namespace.kind == 'group' errors[:limit_reached] << ("Your project limit is #{creator.projects_limit} projects! Please contact your administrator to increase it") end rescue @@ -263,7 +262,7 @@ class Project < ActiveRecord::Base end def web_url - [Gitlab.config.gitlab.url, path_with_namespace].join("/") + [gitlab_config.url, path_with_namespace].join("/") end def web_url_without_protocol @@ -403,7 +402,11 @@ class Project < ActiveRecord::Base services.each do |service| # Call service hook only if it is active - service.execute(data) if service.active + begin + service.execute(data) if service.active + rescue => e + logger.error(e) + end end end @@ -488,7 +491,7 @@ class Project < ActiveRecord::Base end def http_url_to_repo - [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') + [gitlab_config.url, "/", path_with_namespace, ".git"].join('') end # Check if current branch name is marked as protected in the system diff --git a/app/models/project_hook.rb b/app/models/project_hook.rb index 6db6767a88..21867a9316 100644 --- a/app/models/project_hook.rb +++ b/app/models/project_hook.rb @@ -18,8 +18,6 @@ class ProjectHook < WebHook belongs_to :project - attr_accessible :push_events, :issues_events, :merge_requests_events, :tag_push_events - scope :push_hooks, -> { where(push_events: true) } scope :tag_push_hooks, -> { where(tag_push_events: true) } scope :issue_hooks, -> { where(issues_events: true) } diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb index 06e9d6118d..9a8cbb32ac 100644 --- a/app/models/project_services/assembla_service.rb +++ b/app/models/project_services/assembla_service.rb @@ -18,8 +18,6 @@ # class AssemblaService < Service - attr_accessible :subdomain - include HTTParty validates :token, presence: true, if: :activated? diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb index 19030ecffa..83e1bac1ef 100644 --- a/app/models/project_services/campfire_service.rb +++ b/app/models/project_services/campfire_service.rb @@ -18,8 +18,6 @@ # class CampfireService < Service - attr_accessible :subdomain, :room - validates :token, presence: true, if: :activated? def title diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb index 04775c4f2b..be5bab4ec3 100644 --- a/app/models/project_services/emails_on_push_service.rb +++ b/app/models/project_services/emails_on_push_service.rb @@ -18,8 +18,6 @@ # class EmailsOnPushService < Service - attr_accessible :recipients - validates :recipients, presence: true, if: :activated? def title diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb index ef395e0ec6..58ddce4528 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -18,8 +18,6 @@ # class GitlabCiService < CiService - attr_accessible :project_url - validates :project_url, presence: true, if: :activated? validates :token, presence: true, if: :activated? diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index d62f61856d..9c6fe7dab2 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -18,8 +18,6 @@ # class HipchatService < Service - attr_accessible :room - validates :token, presence: true, if: :activated? def title diff --git a/app/models/project_services/jenkins_service.rb b/app/models/project_services/jenkins_service.rb index 44b34a405b..2fc8b62c34 100644 --- a/app/models/project_services/jenkins_service.rb +++ b/app/models/project_services/jenkins_service.rb @@ -18,8 +18,6 @@ # class JenkinsService < CiService - attr_accessible :project_url - validates :project_url, presence: true, if: :activated? delegate :execute, to: :service_hook, prefix: nil diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index c1ae4eb250..96faea5536 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -20,9 +20,7 @@ # api_version :string(255) class JiraService < Service - include HTTParty - attr_accessible :project_url, :username, :password, :api_version validates :username, :password, presence: true, if: :activated? before_validation :set_api_version diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index 50fd62def1..7e54188abf 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -18,9 +18,6 @@ # class SlackService < Service - attr_accessible :room - attr_accessible :subdomain - validates :room, presence: true, if: :activated? validates :subdomain, presence: true, if: :activated? validates :token, presence: true, if: :activated? diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index d2b2b1218d..1b06dd7752 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -12,8 +12,6 @@ class ProtectedBranch < ActiveRecord::Base include Gitlab::ShellAdapter - attr_accessible :name - belongs_to :project validates :name, presence: true validates :project, presence: true diff --git a/app/models/repository.rb b/app/models/repository.rb index 22c16abe48..89bf12ce9e 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -242,4 +242,25 @@ class Repository branches end end + + def contributors + log = graph_log.group_by { |i| i[:author_email] } + + log.map do |email, contributions| + contributor = Gitlab::Contributor.new + contributor.email = email + + contributions.each do |contribution| + if contributor.name.blank? + contributor.name = contribution[:author_name] + end + + contributor.commits += 1 + contributor.additions += contribution[:additions] || 0 + contributor.deletions += contribution[:deletions] || 0 + end + + contributor + end + end end diff --git a/app/models/service.rb b/app/models/service.rb index d655937079..0dc6d514b4 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -22,8 +22,6 @@ class Service < ActiveRecord::Base default_value_for :active, false - attr_accessible :title, :token, :type, :active, :api_key - belongs_to :project has_one :service_hook diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 9e4409daa1..2c38e7939b 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -18,8 +18,6 @@ class Snippet < ActiveRecord::Base include Linguist::BlobHelper - attr_accessible :title, :content, :file_name, :expires_at, :private - default_value_for :private, true belongs_to :author, class_name: "User" diff --git a/app/models/user.rb b/app/models/user.rb index 9660d8d059..c6c16e2677 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -50,31 +50,25 @@ require 'carrierwave/orm/activerecord' require 'file_size_validator' class User < ActiveRecord::Base + include Gitlab::ConfigHelper + extend Gitlab::ConfigHelper + include TokenAuthenticatable + default_value_for :admin, false - default_value_for :can_create_group, true + 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 :theme_id, gitlab_config.default_theme - devise :database_authenticatable, :token_authenticatable, :lockable, :async, + devise :database_authenticatable, :lockable, :async, :recoverable, :rememberable, :trackable, :validatable, :omniauthable, :confirmable, :registerable - attr_accessible :email, :password, :password_confirmation, :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, - as: [:default, :admin] - - attr_accessible :projects_limit, :can_create_group, - as: :admin - attr_accessor :force_random_password # Virtual attribute for authenticating by either username or email attr_accessor :login - # Add login to attr_accessible - attr_accessible :login - - # # Relations # @@ -120,7 +114,7 @@ class User < ActiveRecord::Base validates :username, presence: true, uniqueness: { case_sensitive: false }, exclusion: { in: Gitlab::Blacklist.path }, format: { with: Gitlab::Regex.username_regex, - message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } + message: Gitlab::Regex.username_regex_message } validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true validate :namespace_uniq, if: ->(user) { user.username_changed? } @@ -223,20 +217,8 @@ class User < ActiveRecord::Base where('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i).first end - def build_user(attrs = {}, options= {}) - if options[:as] == :admin - User.new(defaults.merge(attrs.symbolize_keys), options) - else - User.new(attrs, options).with_defaults - end - end - - def defaults - { - projects_limit: Gitlab.config.gitlab.default_projects_limit, - can_create_group: Gitlab.config.gitlab.default_can_create_group, - theme_id: Gitlab.config.gitlab.default_theme - } + def build_user(attrs = {}) + User.new(attrs) end end @@ -315,7 +297,7 @@ class User < ActiveRecord::Base end def can_change_username? - Gitlab.config.gitlab.username_changing_enabled + gitlab_config.username_changing_enabled end def can_create_project? @@ -490,7 +472,7 @@ class User < ActiveRecord::Base def avatar_url(size = nil) if avatar.present? - URI::join(Gitlab.config.gitlab.url, avatar.url).to_s + URI::join(gitlab_config.url, avatar.url).to_s else GravatarService.new.execute(email, size) end diff --git a/app/models/users_group.rb b/app/models/users_group.rb index 242c8abb3c..270f968ef6 100644 --- a/app/models/users_group.rb +++ b/app/models/users_group.rb @@ -19,8 +19,6 @@ class UsersGroup < ActiveRecord::Base Gitlab::Access.options_with_owner end - attr_accessible :group_access, :user_id - belongs_to :user belongs_to :group diff --git a/app/models/users_project.rb b/app/models/users_project.rb index 6495bed4e6..60bdf7a3cf 100644 --- a/app/models/users_project.rb +++ b/app/models/users_project.rb @@ -16,8 +16,6 @@ class UsersProject < ActiveRecord::Base include Notifiable include Gitlab::Access - attr_accessible :user, :user_id, :project_access - belongs_to :user belongs_to :project @@ -126,7 +124,7 @@ class UsersProject < ActiveRecord::Base author_id: self.user.id ) - notification_service.new_team_member(self) + notification_service.new_team_member(self) unless owner? system_hook_service.execute_hooks_for(self, :create) end diff --git a/app/models/web_hook.rb b/app/models/web_hook.rb index 76854da5c3..6cf0c1f683 100644 --- a/app/models/web_hook.rb +++ b/app/models/web_hook.rb @@ -22,8 +22,6 @@ class WebHook < ActiveRecord::Base default_value_for :issues_events, false default_value_for :merge_requests_events, false - attr_accessible :url - # HTTParty timeout default_timeout 10 diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index d431e03552..9e9ce06a5f 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -25,7 +25,10 @@ module Files file_path = path unless file_name =~ Gitlab::Regex.path_regex - return error("Your changes could not be committed, because file name contains not allowed characters") + return error( + 'Your changes could not be committed, because the file name ' + + Gitlab::Regex.path_regex_message + ) end blob = repository.blob_at_branch(ref, file_path) diff --git a/app/services/issues/reopen_service.rb b/app/services/issues/reopen_service.rb index b23d56258a..1e5c398516 100644 --- a/app/services/issues/reopen_service.rb +++ b/app/services/issues/reopen_service.rb @@ -4,6 +4,7 @@ module Issues if issue.reopen event_service.reopen_issue(issue, current_user) create_note(issue) + notification_service.reopen_issue(issue, current_user) execute_hooks(issue, 'reopen') end diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index 169e1e95b4..a0e5714443 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -1,7 +1,7 @@ module Issues class UpdateService < Issues::BaseService def execute(issue) - state = params.delete('state_event') || params.delete(:state_event) + state = params[:state_event] case state when 'reopen' @@ -10,7 +10,7 @@ module Issues Issues::CloseService.new(project, current_user, {}).execute(issue) end - if params.present? && issue.update_attributes(params) + if params.present? && issue.update_attributes(params.except(:state_event)) issue.reset_events_cache if issue.previous_changes.include?('milestone_id') diff --git a/app/services/merge_requests/auto_merge_service.rb b/app/services/merge_requests/auto_merge_service.rb index e35c03275f..20b88d1510 100644 --- a/app/services/merge_requests/auto_merge_service.rb +++ b/app/services/merge_requests/auto_merge_service.rb @@ -6,7 +6,7 @@ module MergeRequests # Called when you do merge via GitLab UI class AutoMergeService < BaseMergeService def execute(merge_request, current_user, commit_message) - merge_request.lock + merge_request.lock_mr if Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message) merge_request.merge @@ -17,11 +17,11 @@ module MergeRequests true else - merge_request.unlock + merge_request.unlock_mr false end rescue - merge_request.unlock if merge_request.locked? + merge_request.unlock_mr if merge_request.locked? merge_request.mark_as_unmergeable false end diff --git a/app/services/merge_requests/reopen_service.rb b/app/services/merge_requests/reopen_service.rb index 2eb13d3e0e..bd68919a55 100644 --- a/app/services/merge_requests/reopen_service.rb +++ b/app/services/merge_requests/reopen_service.rb @@ -3,6 +3,7 @@ module MergeRequests def execute(merge_request) if merge_request.reopen event_service.reopen_mr(merge_request, current_user) + notification_service.reopen_mr(merge_request, current_user) create_note(merge_request) execute_hooks(merge_request) merge_request.reload_code diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index f1aa8b7393..6e416a0080 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -7,10 +7,10 @@ module MergeRequests def execute(merge_request) # We dont allow change of source/target projects # after merge request was created - params.delete(:source_project_id) - params.delete(:target_project_id) + params.except!(:source_project_id) + params.except!(:target_project_id) - state = params.delete('state_event') || params.delete(:state_event) + state = params[:state_event] case state when 'reopen' @@ -19,7 +19,7 @@ module MergeRequests MergeRequests::CloseService.new(project, current_user, {}).execute(merge_request) end - if params.present? && merge_request.update_attributes(params) + if params.present? && merge_request.update_attributes(params.except(:state_event)) merge_request.reset_events_cache if merge_request.previous_changes.include?('milestone_id') diff --git a/app/services/milestones/group_service.rb b/app/services/milestones/group_service.rb new file mode 100644 index 0000000000..11d702f1e7 --- /dev/null +++ b/app/services/milestones/group_service.rb @@ -0,0 +1,26 @@ +module Milestones + class GroupService < Milestones::BaseService + def initialize(project_milestones) + @project_milestones = project_milestones.group_by(&:title) + end + + def execute + build(@project_milestones) + end + + def milestone(title) + if title + group_milestone = @project_milestones[title].group_by(&:title) + build(group_milestone).first + else + nil + end + end + + private + + def build(milestone) + milestone.map{ |title, milestones| GroupMilestone.new(title, milestones) } + end + end +end diff --git a/app/services/milestones/update_service.rb b/app/services/milestones/update_service.rb index 307e96a2b3..ed64847f42 100644 --- a/app/services/milestones/update_service.rb +++ b/app/services/milestones/update_service.rb @@ -1,7 +1,7 @@ module Milestones class UpdateService < Milestones::BaseService def execute(milestone) - state = params.delete('state_event') || params.delete(:state_event) + state = params[:state_event] case state when 'activate' @@ -11,7 +11,7 @@ module Milestones end if params.present? - milestone.update_attributes(params) + milestone.update_attributes(params.except(:state_event)) end milestone diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 650b6008db..e934c486c7 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -80,6 +80,10 @@ class NotificationService close_resource_email(merge_request, merge_request.target_project, current_user, 'closed_merge_request_email') end + def reopen_issue(issue, current_user) + reopen_resource_email(issue, issue.project, current_user, 'issue_status_changed_email', 'reopened') + end + # When we merge a merge request we should send next emails: # # * merge_request author if their notification level is not Disabled @@ -89,12 +93,17 @@ class NotificationService def merge_mr(merge_request, current_user) recipients = reject_muted_users([merge_request.author, merge_request.assignee], merge_request.target_project) recipients = recipients.concat(project_watchers(merge_request.target_project)).uniq + recipients.delete(current_user) recipients.each do |recipient| mailer.merged_merge_request_email(recipient.id, merge_request.id, current_user.id) end end + def reopen_mr(merge_request, current_user) + reopen_resource_email(merge_request, merge_request.target_project, current_user, 'merge_request_status_email', 'reopened') + end + # Notify new user with email after creation def new_user(user) # Don't email omniauth created users @@ -301,7 +310,9 @@ class NotificationService end def reassign_resource_email(target, project, current_user, method) - recipients = User.where(id: [target.assignee_id, target.assignee_id_was]) + assignee_id_was = previous_record(target, "assignee_id") + + recipients = User.where(id: [target.assignee_id, assignee_id_was]) # Add watchers to email list recipients = recipients.concat(project_watchers(project)) @@ -313,11 +324,29 @@ class NotificationService recipients.delete(current_user) recipients.each do |recipient| - mailer.send(method, recipient.id, target.id, target.assignee_id_was, current_user.id) + mailer.send(method, recipient.id, target.id, assignee_id_was, current_user.id) + end + end + + def reopen_resource_email(target, project, current_user, method, status) + recipients = reject_muted_users([target.author, target.assignee], project) + recipients = recipients.concat(project_watchers(project)).uniq + recipients.delete(current_user) + + recipients.each do |recipient| + mailer.send(method, recipient.id, target.id, status, current_user.id) end end def mailer Notify.delay end + + def previous_record(object, attribute) + if object && attribute + if object.previous_changes.include?(attribute) + object.previous_changes[attribute].first + end + end + end end diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index dfadcfd296..3565e4e4f7 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -5,27 +5,13 @@ module Projects end def execute - # get namespace id - namespace_id = params.delete(:namespace_id) + @project = Project.new(params) - # check that user is allowed to set specified visibility_level + # Reset visibility levet if is not allowed to set it unless Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level]) - params.delete(:visibility_level) + @project.visibility_level = default_features.visibility_level end - # Load default feature settings - default_features = Gitlab.config.gitlab.default_projects_features - - default_opts = { - issues_enabled: default_features.issues, - wiki_enabled: default_features.wiki, - snippets_enabled: default_features.snippets, - merge_requests_enabled: default_features.merge_requests, - visibility_level: default_features.visibility_level - }.stringify_keys - - @project = Project.new(default_opts.merge(params)) - # Parametrize path for project # # Ex. @@ -33,13 +19,14 @@ module Projects # @project.path = @project.name.dup.parameterize unless @project.path.present? + # get namespace id + namespace_id = params[:namespace_id] if namespace_id # Find matching namespace and check if it allowed # for current user if namespace_id passed. - if allowed_namespace?(current_user, namespace_id) - @project.namespace_id = namespace_id - else + unless allowed_namespace?(current_user, namespace_id) + @project.namespace_id = nil deny_namespace return @project end diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index d115e92a10..e39fe882cb 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -12,7 +12,7 @@ module Projects class TransferError < StandardError; end def execute - namespace_id = params.delete(:namespace_id) + namespace_id = params[:namespace_id] namespace = Namespace.find_by(id: namespace_id) if allowed_transfer?(current_user, project, namespace) diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 551a3653ca..36877a6167 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -1,23 +1,18 @@ module Projects class UpdateService < BaseService - def execute(role = :default) - params[:project].delete(:namespace_id) + def execute # check that user is allowed to set specified visibility_level - unless can?(current_user, :change_visibility_level, project) && Gitlab::VisibilityLevel.allowed_for?(current_user, params[:project][:visibility_level]) - params[:project].delete(:visibility_level) + unless can?(current_user, :change_visibility_level, project) && Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level]) + params[:visibility_level] = project.visibility_level end - new_branch = params[:project].delete(:default_branch) + new_branch = params[:default_branch] if project.repository.exists? && new_branch && new_branch != project.default_branch project.change_head(new_branch) end - if project.update_attributes(params[:project], as: role) - if project.previous_changes.include?('namespace_id') - project.send_move_instructions - end - + if project.update_attributes(params.except(:default_branch)) if project.previous_changes.include?('path') project.rename_repo end diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index 9888da2f7f..d3ff291eaa 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -1,6 +1,5 @@ %h3.page-title Issues - %span.pull-right #{@issues.total_count} issues %p.light List all issues from all projects you have access to. diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index ee3bec2849..7a9ea9f6f9 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -1,6 +1,5 @@ %h3.page-title Merge Requests - %span.pull-right #{@merge_requests.total_count} merge requests %p.light diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb index 553d08369e..cb1291cf3b 100644 --- a/app/views/devise/mailer/confirmation_instructions.html.erb +++ b/app/views/devise/mailer/confirmation_instructions.html.erb @@ -6,4 +6,4 @@
You can confirm your account through the link below:
<% end %> -<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @resource.confirmation_token) %>
+<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>
diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb index e1144e943b..7913e88beb 100644 --- a/app/views/devise/mailer/reset_password_instructions.html.erb +++ b/app/views/devise/mailer/reset_password_instructions.html.erb @@ -2,7 +2,7 @@Someone has requested a link to change your password, and you can do this through the link below.
-<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @resource.reset_password_token) %>
+<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>
If you didn't request this, please ignore this email.
Your password won't change until you access the link above and create a new one.
diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb index 0429883f05..8c2a4f0c2d 100644 --- a/app/views/devise/mailer/unlock_instructions.html.erb +++ b/app/views/devise/mailer/unlock_instructions.html.erb @@ -4,4 +4,4 @@Click the link below to unlock your account:
-<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @resource.unlock_token) %>
+<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>
diff --git a/app/views/events/_event_issue.atom.haml b/app/views/events/_event_issue.atom.haml index 64dc02e3f5..56801107d0 100644 --- a/app/views/events/_event_issue.atom.haml +++ b/app/views/events/_event_issue.atom.haml @@ -1,2 +1,2 @@ %div{:xmlns => "http://www.w3.org/1999/xhtml"} - %p= simple_format issue.description + %p= markdown issue.description diff --git a/app/views/events/_event_merge_request.atom.haml b/app/views/events/_event_merge_request.atom.haml new file mode 100644 index 0000000000..dea256bb7f --- /dev/null +++ b/app/views/events/_event_merge_request.atom.haml @@ -0,0 +1,2 @@ +%div{xmlns: "http://www.w3.org/1999/xhtml"} + %p= markdown merge_request.description diff --git a/app/views/events/_event_note.atom.haml b/app/views/events/_event_note.atom.haml new file mode 100644 index 0000000000..96039ad18d --- /dev/null +++ b/app/views/events/_event_note.atom.haml @@ -0,0 +1,2 @@ +%div{:xmlns => "http://www.w3.org/1999/xhtml"} + %p= markdown note.note diff --git a/app/views/events/_event_push.atom.haml b/app/views/events/_event_push.atom.haml index e44b366040..17228c430c 100644 --- a/app/views/events/_event_push.atom.haml +++ b/app/views/events/_event_push.atom.haml @@ -6,7 +6,7 @@ %i at = commit[:timestamp].to_time.to_s(:short) - %blockquote= simple_format(escape_once(commit[:message])) + %blockquote= markdown(escape_once(commit[:message])) - if event.commits_count > 15 %p %i diff --git a/app/views/groups/_filter.html.haml b/app/views/groups/_filter.html.haml new file mode 100644 index 0000000000..393be3f1d1 --- /dev/null +++ b/app/views/groups/_filter.html.haml @@ -0,0 +1,12 @@ += 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/issues.html.haml b/app/views/groups/issues.html.haml index 0eec2d6be0..0152ae8683 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -1,6 +1,5 @@ %h3.page-title Issues - %span.pull-right #{@issues.total_count} issues %p.light Only issues from diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index 71adb2c551..71d346d046 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -1,6 +1,5 @@ %h3.page-title Merge Requests - %span.pull-right #{@merge_requests.total_count} merge requests %p.light Only merge requests from diff --git a/app/views/groups/milestones/_issue.html.haml b/app/views/groups/milestones/_issue.html.haml new file mode 100644 index 0000000000..c95c2e8967 --- /dev/null +++ b/app/views/groups/milestones/_issue.html.haml @@ -0,0 +1,10 @@ +%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid } + %span.milestone-row + - project = issue.project + %strong #{project.name} · + = link_to [project, issue] do + %span.cgray ##{issue.iid} + = link_to_gfm issue.title, [project, issue], title: issue.title + .pull-right.assignee-icon + - if issue.assignee + = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16" diff --git a/app/views/groups/milestones/_issues.html.haml b/app/views/groups/milestones/_issues.html.haml new file mode 100644 index 0000000000..9f350b772b --- /dev/null +++ b/app/views/groups/milestones/_issues.html.haml @@ -0,0 +1,6 @@ +.panel.panel-default + .panel-heading= title + %ul{ class: "well-list issues-sortable-list" } + - if issues + - issues.each do |issue| + = render 'issue', issue: issue diff --git a/app/views/groups/milestones/_merge_request.html.haml b/app/views/groups/milestones/_merge_request.html.haml new file mode 100644 index 0000000000..e0c903bfdb --- /dev/null +++ b/app/views/groups/milestones/_merge_request.html.haml @@ -0,0 +1,10 @@ +%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid } + %span.milestone-row + - project = merge_request.project + %strong #{project.name} · + = link_to [project, merge_request] do + %span.cgray ##{merge_request.iid} + = link_to_gfm merge_request.title, [project, merge_request], title: merge_request.title + .pull-right.assignee-icon + - if merge_request.assignee + = image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16" diff --git a/app/views/groups/milestones/_merge_requests.html.haml b/app/views/groups/milestones/_merge_requests.html.haml new file mode 100644 index 0000000000..50057e2c63 --- /dev/null +++ b/app/views/groups/milestones/_merge_requests.html.haml @@ -0,0 +1,6 @@ +.panel.panel-default + .panel-heading= title + %ul{ class: "well-list merge_requests-sortable-list" } + - if merge_requests + - merge_requests.each do |merge_request| + = render 'merge_request', merge_request: merge_request diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml new file mode 100644 index 0000000000..54e901173f --- /dev/null +++ b/app/views/groups/milestones/index.html.haml @@ -0,0 +1,50 @@ +%h3.page-title + Milestones + %span.pull-right #{@group_milestones.count} milestones + +%p.light + Only milestones from + %strong #{@group.name} + group are listed here. + +%hr + +.row + .fixed.sidebar-expand-button.hidden-lg.hidden-md + %i.icon-list.icon-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) + %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" diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml new file mode 100644 index 0000000000..29b274af5a --- /dev/null +++ b/app/views/groups/milestones/show.html.haml @@ -0,0 +1,88 @@ +%h3.page-title + Milestone #{@group_milestone.title} + .pull-right + - if can?(current_user, :manage_group, @group) + - if @group_milestone.active? + = link_to 'Close Milestone', group_milestone_path(@group, @group_milestone.safe_title, title: @group_milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-small btn-close" + - 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" + +- 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 + = preserve do + - if milestone.description.present? + = milestone.description + - else + %em Project milestone has no description + + .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}%;"} + +%ul.nav.nav-tabs + %li.active + = link_to '#tab-issues', 'data-toggle' => 'tab' do + Issues + %span.badge= @group_milestone.issue_count + %li + = link_to '#tab-merge-requests', 'data-toggle' => 'tab' do + Merge Requests + %span.badge= @group_milestone.merge_requests_count + %li + = link_to '#tab-participants', 'data-toggle' => 'tab' do + Participants + %span.badge= @group_milestone.participants.count + +.tab-content + .tab-pane.active#tab-issues + .row + .col-md-6 + = render 'issues', title: "Open", issues: @group_milestone.opened_issues + .col-md-6 + = render 'issues', title: "Closed", issues: @group_milestone.closed_issues + + .tab-pane#tab-merge-requests + .row + .col-md-6 + = render 'merge_requests', title: "Open", merge_requests: @group_milestone.opened_merge_requests + .col-md-6 + = render 'merge_requests', title: "Closed", merge_requests: @group_milestone.closed_merge_requests + + .tab-pane#tab-participants + %ul.bordered-list + - @group_milestone.participants.each do |user| + %li + = link_to user, title: user.name, class: "darken" do + = image_tag avatar_icon(user.email, 32), class: "avatar s32" + %strong= truncate(user.name, lenght: 40) + %br + %small.cgray= user.username diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index 7b67ecbf89..2ea4daff66 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -1,5 +1,5 @@ -.jumbotron - %h2 +%div + %h1 GitLab %span.light Enterprise Edition %span= Gitlab::VERSION @@ -17,7 +17,17 @@ %br Read more about GitLab at #{link_to "www.gitlab.com", "https://www.gitlab.com/", target: "_blank"}. +%hr + .row + .col-md-8 + .documentation-index + = preserve do + - readme_text = File.read(Rails.root.join("doc", "README.md")) + - text = readme_text.dup + - readme_text.scan(/\]\(([^(]+)\)/) { |match| text.gsub!(match.first, "help/#{match.first}") } + = markdown text + .col-md-4 .panel.panel-default .panel-heading @@ -33,13 +43,3 @@ %li Use = link_to "shortcuts", '#', onclick: "new Shortcuts()" - - .col-md-8 - .panel.panel-default.documentation-index - .panel-heading Documentation - .panel-body - = preserve do - - readme_text = File.read(Rails.root.join("doc", "README.md")) - - text = readme_text.dup - - readme_text.scan(/\]\(([^(]+)\)/) { |match| text.gsub!(match.first, "help/#{match.first}") } - = markdown text diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index c7a827555a..1ea91a1914 100644 --- a/app/views/layouts/admin.html.haml +++ b/app/views/layouts/admin.html.haml @@ -10,4 +10,4 @@ .container .content= yield - = yield :embedded_scripts \ No newline at end of file + = yield :embedded_scripts diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index d306e1eeb5..9095a843c9 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -2,6 +2,9 @@ = nav_link(path: 'groups#show', html_options: {class: 'home'}) do = link_to group_path(@group), title: "Home" do Activity + = nav_link(controller: [:group, :milestones]) do + = link_to group_milestones_path(@group) do + Milestones = nav_link(path: 'groups#issues') do = link_to issues_group_path(@group) do Issues diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml index 991d4f0c6d..ab421d63f1 100644 --- a/app/views/layouts/notify.html.haml +++ b/app/views/layouts/notify.html.haml @@ -4,6 +4,10 @@ %title GitLab :css + img { + max-width: 100%; + height: auto; + } p.details { font-style:italic; color:#777 diff --git a/app/views/layouts/projects.html.haml b/app/views/layouts/projects.html.haml index 11c815c52a..f02eca6bd7 100644 --- a/app/views/layouts/projects.html.haml +++ b/app/views/layouts/projects.html.haml @@ -14,4 +14,4 @@ .container .content= yield - = yield :embedded_scripts \ No newline at end of file + = yield :embedded_scripts diff --git a/app/views/notify/merge_request_status_email.html.haml b/app/views/notify/merge_request_status_email.html.haml new file mode 100644 index 0000000000..c9bf04f514 --- /dev/null +++ b/app/views/notify/merge_request_status_email.html.haml @@ -0,0 +1,2 @@ +%p + = "Merge Request ##{@merge_request.iid} was #{@mr_status} by #{@updated_by.name}" diff --git a/app/views/notify/merge_request_status_email.text.haml b/app/views/notify/merge_request_status_email.text.haml new file mode 100644 index 0000000000..8750bf86e2 --- /dev/null +++ b/app/views/notify/merge_request_status_email.text.haml @@ -0,0 +1,8 @@ += "Merge Request ##{@merge_request.iid} was #{@mr_status} by #{@updated_by.name}" + +Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)} + += merge_path_description(@merge_request, 'to') + +Author: #{@merge_request.author_name} +Assignee: #{@merge_request.assignee_name} diff --git a/app/views/notify/project_was_moved_email.html.haml b/app/views/notify/project_was_moved_email.html.haml index 3e761c4343..1667c59bc0 100644 --- a/app/views/notify/project_was_moved_email.html.haml +++ b/app/views/notify/project_was_moved_email.html.haml @@ -5,7 +5,11 @@ = link_to project_url(@project) do = @project.name_with_namespace %p - To update the remote url in your local repository run: + To update the remote url in your local repository run (for ssh): %p{ style: "background:#f5f5f5; padding:10px; border:1px solid #ddd" } git remote set-url origin #{@project.ssh_url_to_repo} +%p + or for http(s): +%p{ style: "background:#f5f5f5; padding:10px; border:1px solid #ddd" } + git remote set-url origin #{@project.http_url_to_repo} %br diff --git a/app/views/notify/project_was_moved_email.text.erb b/app/views/notify/project_was_moved_email.text.erb index 7889c7b9cc..664148fb3b 100644 --- a/app/views/notify/project_was_moved_email.text.erb +++ b/app/views/notify/project_was_moved_email.text.erb @@ -4,5 +4,7 @@ The project is now located under <%= project_url(@project) %> -To update the remote url in your local repository run: +To update the remote url in your local repository run (for ssh): git remote set-url origin <%= @project.ssh_url_to_repo %> +or for http(s): + git remote set-url origin <%= @project.http_url_to_repo %> diff --git a/app/views/notify/reassigned_issue_email.html.haml b/app/views/notify/reassigned_issue_email.html.haml index 07227a3e68..f1458df5c7 100644 --- a/app/views/notify/reassigned_issue_email.html.haml +++ b/app/views/notify/reassigned_issue_email.html.haml @@ -4,5 +4,8 @@ from %strong #{@previous_assignee.name} to - %strong #{@issue.assignee_name} + - if @issue.assignee_id + %strong #{@issue.assignee_name} + - else + %strong Unassigned diff --git a/app/views/notify/reassigned_issue_email.text.erb b/app/views/notify/reassigned_issue_email.text.erb index bc0d056792..4becac2749 100644 --- a/app/views/notify/reassigned_issue_email.text.erb +++ b/app/views/notify/reassigned_issue_email.text.erb @@ -2,4 +2,4 @@ Reassigned Issue <%= @issue.iid %> <%= url_for(project_issue_url(@issue.project, @issue)) %> -Assignee changed <%= "from #{@previous_assignee.name}" if @previous_assignee %> to <%= @issue.assignee_name %> +Assignee changed <%= "from #{@previous_assignee.name}" if @previous_assignee %> to <%= "#{@issue.assignee_id ? @issue.assignee_name : 'Unassigned'}" %> diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml index 85a01a567f..bf358fe70a 100644 --- a/app/views/notify/repository_push_email.html.haml +++ b/app/views/notify/repository_push_email.html.haml @@ -5,7 +5,9 @@ %ul - @commits.each do |commit| %li - #{commit.short_id} - #{commit.title} + %strong #{commit.short_id} + %span by #{commit.author_name} + %pre #{commit.safe_message} %h4 Changes: - @diffs.each do |diff| diff --git a/app/views/notify/repository_push_email.text.haml b/app/views/notify/repository_push_email.text.haml index a15b8efe1f..ac337c7628 100644 --- a/app/views/notify/repository_push_email.text.haml +++ b/app/views/notify/repository_push_email.text.haml @@ -3,7 +3,9 @@ \ Commits: - @commits.each do |commit| - #{commit.short_id} - #{truncate(commit.title, length: 40)} + #{commit.short_id} by #{commit.author_name} + #{commit.safe_message} + \- - - - - \ \ Changes: diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 74146b5f19..abe0d4cff4 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -7,7 +7,8 @@ - if commit.description? %a.text-expander.js-toggle-button ... - = link_to "Browse Code »", project_tree_path(project, commit), class: "pull-right" + = link_to_browse_code(project, commit) + .notes_count - if @note_counts - note_count = @note_counts.fetch(commit.id, 0) @@ -21,7 +22,8 @@ - if commit.description? .commit-row-description.js-toggle-content - = simple_format(commit.description) + %pre + = commit.description .commit-row-info = commit_author_link(commit, avatar: true, size: 16) diff --git a/app/views/projects/edit_tree/show.html.haml b/app/views/projects/edit_tree/show.html.haml index 16fc1ab1f9..05050e7df7 100644 --- a/app/views/projects/edit_tree/show.html.haml +++ b/app/views/projects/edit_tree/show.html.haml @@ -16,7 +16,7 @@ .btn-group.tree-btn-group = link_to "Cancel", @after_edit_path, class: "btn btn-tiny btn-cancel", data: { confirm: leave_edit_message } .file-content.code - %pre.js-edit-mode-pane#editor= @blob.data + %pre.js-edit-mode-pane#editor .js-edit-mode-pane#preview.hide .center %h2 @@ -43,6 +43,7 @@ ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace") var ace_mode = "#{@blob.language.try(:ace_mode)}"; var editor = ace.edit("editor"); + editor.setValue("#{escape_javascript(@blob.data)}"); if (ace_mode) { editor.getSession().setMode('ace/mode/' + ace_mode); } diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml index 716ea7cefe..dad547d4eb 100644 --- a/app/views/projects/issues/_head.html.haml +++ b/app/views/projects/issues/_head.html.haml @@ -2,8 +2,6 @@ = nav_link(controller: :issues) do = link_to project_issues_path(@project), class: "tab" do Browse Issues - - if current_controller?(:issues) - %span.badge.issue_counter #{@issues.total_count} = nav_link(controller: :milestones) do = link_to 'Milestones', project_milestones_path(@project), class: "tab" = nav_link(controller: :labels) do diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 3fc04c26cf..8d447da3ae 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -38,9 +38,9 @@ .issue-actions - if can? current_user, :modify_issue, issue - if issue.closed? - = link_to 'Reopen', project_issue_path(issue.project, issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-small btn-grouped reopen_issue", remote: true + = link_to 'Reopen', project_issue_path(issue.project, issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-small btn-grouped reopen_issue btn-reopen", remote: true - else - = link_to 'Close', project_issue_path(issue.project, issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-small btn-grouped close_issue", remote: true + = link_to 'Close', project_issue_path(issue.project, issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-small btn-grouped close_issue btn-close", remote: true = link_to edit_project_issue_path(issue.project, issue), class: "btn btn-small edit-issue-link btn-grouped" do %i.icon-edit Edit diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 2e66d05956..5de77b8bf3 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -4,6 +4,6 @@ %i.icon-list.icon-2x .col-md-3.responsive-side = render 'shared/project_filter', project_entities_path: project_issues_path(@project), - labels: true, redirect: 'issues' + labels: true, redirect: 'issues', entity: 'issue' .col-md-9.issues-holder = render "issues" diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index 4bb803eb6d..0954fa8fce 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -4,14 +4,13 @@ New Merge Request %h3.page-title Merge Requests - %span (#{@merge_requests.total_count}) %hr .row .fixed.sidebar-expand-button.hidden-lg.hidden-md %i.icon-list.icon-2x .col-md-3.responsive-side = render 'shared/project_filter', project_entities_path: project_merge_requests_path(@project), - labels: true, redirect: 'merge_requests' + labels: true, redirect: 'merge_requests', entity: 'merge_request' .col-md-9 .mr-filters.append-bottom-10 .dropdown.inline 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 07e05f5501..ead19ec05c 100644 --- a/app/views/projects/merge_requests/show/_mr_accept.html.haml +++ b/app/views/projects/merge_requests/show/_mr_accept.html.haml @@ -38,7 +38,7 @@ .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) + - 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 diff --git a/app/views/projects/merge_requests/show/_mr_ci.html.haml b/app/views/projects/merge_requests/show/_mr_ci.html.haml index 507a9e507f..b77eeac612 100644 --- a/app/views/projects/merge_requests/show/_mr_ci.html.haml +++ b/app/views/projects/merge_requests/show/_mr_ci.html.haml @@ -1,21 +1,21 @@ - if @commits.any? .ci_widget.ci-success{style: "display:none"} %i.icon-ok - %strong CI build passed + %span CI build passed for #{@merge_request.last_commit_short_sha}. = link_to "Build page", ci_build_details_path(@merge_request) .ci_widget.ci-failed{style: "display:none"} %i.icon-remove - %strong CI build failed + %span CI build failed for #{@merge_request.last_commit_short_sha}. = link_to "Build page", ci_build_details_path(@merge_request) - [:running, :pending].each do |status| .ci_widget{class: "ci-#{status}", style: "display:none"} %i.icon-time - %strong CI build #{status} + %span CI build #{status} for #{@merge_request.last_commit_short_sha}. = link_to "Build page", ci_build_details_path(@merge_request) @@ -26,4 +26,4 @@ .ci_widget.ci-error{style: "display:none"} %i.icon-remove - %strong Cannot connect to the CI server. Please check your settings and try again. + %span Cannot connect to the CI server. Please check your settings and try again. diff --git a/app/views/projects/milestones/_issue.html.haml b/app/views/projects/milestones/_issue.html.haml index 08ccd0cdc8..b5ec0fc988 100644 --- a/app/views/projects/milestones/_issue.html.haml +++ b/app/views/projects/milestones/_issue.html.haml @@ -2,7 +2,7 @@ %span.str-truncated = link_to [@project, issue] do %span.cgray ##{issue.iid} - = link_to_gfm issue.title, [@project, issue] + = link_to_gfm issue.title, [@project, issue], title: issue.title .pull-right.assignee-icon - if issue.assignee = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16" diff --git a/app/views/projects/milestones/_merge_request.html.haml b/app/views/projects/milestones/_merge_request.html.haml index d630c4518d..d54cb3f8e7 100644 --- a/app/views/projects/milestones/_merge_request.html.haml +++ b/app/views/projects/milestones/_merge_request.html.haml @@ -2,4 +2,4 @@ %span.str-truncated = link_to [@project, merge_request] do %span.cgray ##{merge_request.iid} - = link_to_gfm truncate(merge_request.title, length: 60), [@project, merge_request] + = link_to_gfm merge_request.title, [@project, merge_request], title: merge_request.title diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml index 5579659d60..4018d132a5 100644 --- a/app/views/projects/milestones/_milestone.html.haml +++ b/app/views/projects/milestones/_milestone.html.haml @@ -4,7 +4,7 @@ = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-small edit-milestone-link btn-grouped" do %i.icon-edit Edit - = link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-small btn-remove" + = link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-small btn-close" %h4 = link_to_gfm truncate(milestone.title, length: 100), project_milestone_path(milestone.project, milestone) - if milestone.expired? and not milestone.closed? diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 5cf7f33211..42c3f45f6c 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -7,9 +7,9 @@ %i.icon-edit Edit - if @milestone.active? - = link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-remove btn-grouped" + = 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-grouped" + = link_to 'Reopen Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped" - if @milestone.issues.any? && @milestone.can_be_closed? .alert.alert-success diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml index 88c1cfa28e..0bf59a2038 100644 --- a/app/views/projects/repositories/_download_archive.html.haml +++ b/app/views/projects/repositories/_download_archive.html.haml @@ -34,4 +34,4 @@ %span zip = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz'), class: 'btn', rel: 'nofollow' do %i.icon-download-alt - %span tar.gz \ No newline at end of file + %span tar.gz diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml index 7a24b76bce..8147cf272f 100644 --- a/app/views/search/results/_issue.html.haml +++ b/app/views/search/results/_issue.html.haml @@ -6,4 +6,4 @@ = truncate issue.title, length: 50 %span.light (#{issue.project.name_with_namespace}) - if issue.closed? - %span.label Closed + %span.label.label-danger Closed diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml index 22d7587f6c..de2a79970c 100644 --- a/app/views/search/results/_merge_request.html.haml +++ b/app/views/search/results/_merge_request.html.haml @@ -8,5 +8,7 @@ %span.light (#{merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} → #{merge_request.target_project.name_with_namespace}:#{merge_request.target_branch}) - else %span.light (#{merge_request.source_branch} → #{merge_request.target_branch}) - - if merge_request.closed? - %span.label Closed + - if merge_request.merged? + %span.label.label-primary Merged + - elsif merge_request.closed? + %span.label.label-danger Closed diff --git a/app/views/shared/_filter.html.haml b/app/views/shared/_filter.html.haml index 19ecc458e2..9e65ce11ad 100644 --- a/app/views/shared/_filter.html.haml +++ b/app/views/shared/_filter.html.haml @@ -6,12 +6,18 @@ %li{class: ("active" if params[:scope] == 'assigned-to-me')} = link_to filter_path(entity, scope: 'assigned-to-me') do Assigned to me + %span.pull-right + = assigned_entities_count(current_user, entity, @group) %li{class: ("active" if params[:scope] == 'authored')} = link_to filter_path(entity, scope: 'authored') do Created by me + %span.pull-right + = authored_entities_count(current_user, entity, @group) %li{class: ("active" if params[:scope] == 'all')} = link_to filter_path(entity, scope: 'all') do Everyone's + %span.pull-right + = authorized_entities_count(current_user, entity, @group) %fieldset.status-filter %legend State diff --git a/app/views/shared/_project_filter.html.haml b/app/views/shared/_project_filter.html.haml index 743b4fba54..38cb1208cd 100644 --- a/app/views/shared/_project_filter.html.haml +++ b/app/views/shared/_project_filter.html.haml @@ -6,12 +6,18 @@ %li{class: ("active" if params[:scope] == 'all')} = link_to project_filter_path(scope: 'all') do Everyone's + %span.pull-right + = authorized_entities_count(current_user, entity, @project) %li{class: ("active" if params[:scope] == 'assigned-to-me')} = link_to project_filter_path(scope: 'assigned-to-me') do Assigned to me + %span.pull-right + = assigned_entities_count(current_user, entity, @project) %li{class: ("active" if params[:scope] == 'created-by-me')} = link_to project_filter_path(scope: 'created-by-me') do Created by me + %span.pull-right + = authored_entities_count(current_user, entity, @project) %fieldset %legend State diff --git a/app/views/users/_groups.html.haml b/app/views/users/_groups.html.haml index 412df943fc..09b2985d49 100644 --- a/app/views/users/_groups.html.haml +++ b/app/views/users/_groups.html.haml @@ -1,3 +1,3 @@ - groups.each do |group| = link_to group, class: 'profile-groups-avatars', :title => group.name do - = image_tag group_icon(group.path) \ No newline at end of file + = image_tag group_icon(group.path) diff --git a/config/application.rb b/config/application.rb index 0a77f58f6d..58a5949c65 100644 --- a/config/application.rb +++ b/config/application.rb @@ -41,12 +41,6 @@ module Gitlab # like if you have constraints or database-specific column types # config.active_record.schema_format = :sql - # Enforce whitelist mode for mass assignment. - # This will create an empty whitelist of attributes available for mass-assignment for all models - # in your app. As such, your models will need to explicitly whitelist or blacklist accessible - # parameters by using an attr_accessible or attr_protected declaration. - config.active_record.whitelist_attributes = true - # Enable the asset pipeline config.assets.enabled = true config.assets.paths << Emoji.images_path diff --git a/config/database.yml.postgresql b/config/database.yml.postgresql index 66960551cf..7067e0fe40 100644 --- a/config/database.yml.postgresql +++ b/config/database.yml.postgresql @@ -10,7 +10,6 @@ production: # password: # host: localhost # port: 5432 - # socket: /tmp/postgresql.sock # # Development specific @@ -22,7 +21,6 @@ development: pool: 5 username: postgres password: - # socket: /tmp/postgresql.sock # # Staging specific @@ -34,7 +32,6 @@ staging: pool: 5 username: postgres password: - # socket: /tmp/postgresql.sock # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". @@ -46,4 +43,3 @@ test: &test pool: 5 username: postgres password: - # socket: /tmp/postgresql.sock diff --git a/config/environments/development.rb b/config/environments/development.rb index e4c7649fda..356e26bd68 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -19,9 +19,6 @@ Gitlab::Application.configure do # Only use best-standards-support built into browsers config.action_dispatch.best_standards_support = :builtin - # Raise exception on mass assignment protection for Active Record models - config.active_record.mass_assignment_sanitizer = :strict - # Do not compress assets config.assets.compress = false diff --git a/config/environments/test.rb b/config/environments/test.rb index 3860dc5c74..25b082b98d 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -26,9 +26,6 @@ Gitlab::Application.configure do # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test - # Raise exception on mass assignment protection for Active Record models - # config.active_record.mass_assignment_sanitizer = :strict - # Print deprecation notices to the stderr config.active_support.deprecation = :stderr diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 6846105197..2bfaa361c4 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -49,15 +49,6 @@ production: &base ## COLOR = 5 # default_theme: 2 # default: 2 - - ## Users management - # default: false - Account passwords are not sent via the email if signup is enabled. - # signup_enabled: true - # - # default: true - If set to false, standard login form won't be shown on the sign-in page - # signin_enabled: false - - # Restrict setting visibility levels for non-admin users. # The default is to allow all levels. #restricted_visibility_levels: [ "public" ] @@ -121,6 +112,16 @@ production: &base # 2. Auth settings # ========================== + ## Users can create accounts + # This also allows normal users to sign up for accounts themselves + # default: false - By default GitLab administrators must create all new accounts + # signup_enabled: true + + ## Standard login settings + # The standard login can be disabled to force login via LDAP + # default: true - If set to false the standard login form won't be shown on the sign-in page + # signin_enabled: false + ## LDAP settings # You can inspect a sample of the LDAP users with login access by running: # bundle exec rake gitlab:ldap:check RAILS_ENV=production diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index dfa28d4b04..79be2a6fb6 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -150,6 +150,6 @@ Settings['extra'] ||= Settingslogic.new({}) # if Rails.env.test? Settings.gitlab['default_projects_limit'] = 42 - Settings.gitlab['default_can_create_group'] = false + Settings.gitlab['default_can_create_group'] = true Settings.gitlab['default_can_create_team'] = false end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 50669ece7a..34f4f38698 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -155,10 +155,6 @@ Devise.setup do |config| # REST_AUTH_SITE_KEY to pepper) # config.encryptor = :sha512 - # ==> Configuration for :token_authenticatable - # Defines name of the authentication token params key - config.token_authentication_key = :private_token - # Authentication through token does not store user in session and needs # to be supplied on each request. Useful if you are using the token as API token. config.skip_session_storage << :token_auth diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml index 275273a0b1..1cbcde5b3d 100644 --- a/config/locales/devise.en.yml +++ b/config/locales/devise.en.yml @@ -25,6 +25,9 @@ en: sessions: signed_in: 'Signed in successfully.' signed_out: 'Signed out successfully.' + users_sessions: + user: + signed_in: 'Signed in successfully.' passwords: send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes.' updated: 'Your password was changed successfully. You are now signed in.' diff --git a/config/routes.rb b/config/routes.rb index a6299950f6..21bc9ad620 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -165,8 +165,10 @@ Gitlab::Application.routes.draw do end resources :users_groups, only: [:create, :update, :destroy] + scope module: :groups do resource :avatar, only: [:destroy] + resources :milestones end end diff --git a/db/migrate/20140407135544_fix_namespaces.rb b/db/migrate/20140407135544_fix_namespaces.rb index 8c4f2b0f6b..59665d538f 100644 --- a/db/migrate/20140407135544_fix_namespaces.rb +++ b/db/migrate/20140407135544_fix_namespaces.rb @@ -7,4 +7,4 @@ class FixNamespaces < ActiveRecord::Migration def down end -end \ No newline at end of file +end diff --git a/doc/api/repositories.md b/doc/api/repositories.md index 26ae3e8723..2539e3edbf 100644 --- a/doc/api/repositories.md +++ b/doc/api/repositories.md @@ -220,3 +220,32 @@ Response: "compare_same_ref": false } ``` + +## Contributors + +Get repository contributors list + +``` +GET /projects/:id/repository/contributors +``` + +Parameters: ++ `id` (required) - The ID of a project + +Response: + +``` +[{ + "name": "Dmitriy Zaporozhets", + "email": "dmitriy.zaporozhets@gmail.com", + "commits": 117, + "additions": 2097, + "deletions": 517 +}, { + "name": "Jacob Vosmaer", + "email": "contact@jacobvosmaer.nl", + "commits": 33, + "additions": 338, + "deletions": 244 +}] +``` diff --git a/doc/install/installation.md b/doc/install/installation.md index a9b32a87e9..d3f8c522a0 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -331,6 +331,8 @@ To make sure you didn't miss anything run a more thorough check with: If all items are green, then congratulations on successfully installing GitLab! +NOTE: Supply `SANITIZE=true` environment variable to `gitlab:check` to omit project names from the output of the check command. + ### Initial Login Visit YOUR_SERVER in your web browser for your first GitLab login. The setup has created an admin account for you. You can use it to log in: diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md index 64f571b435..2565acb8ef 100644 --- a/doc/markdown/markdown.md +++ b/doc/markdown/markdown.md @@ -4,39 +4,25 @@ **[GitLab Flavored Markdown](#gitlab-flavored-markdown-gfm)** -[Newlines](#newlines) - -[Multiple underscores in words](#multiple-underscores-in-words) - -[URL autolinking](#url-autolinking) - -[Code and Syntax Highlighting](#code-and-syntax-highlighting) - -[Emoji](#emoji) - -[Special GitLab references](#special-gitlab-references) +* [Newlines](#newlines) +* [Multiple underscores in words](#multiple-underscores-in-words) +* [URL autolinking](#url-autolinking) +* [Code and Syntax Highlighting](#code-and-syntax-highlighting) +* [Emoji](#emoji) +* [Special GitLab references](#special-gitlab-references) **[Standard Markdown](#standard-markdown)** -[Headers](#headers) - -[Emphasis](#emphasis) - -[Lists](#lists) - -[Links](#links) - -[Images](#images) - -[Blockquotes](#blockquotes) - -[Inline HTML](#inline-html) - -[Horizontal Rule](#horizontal-rule) - -[Line Breaks](#line-breaks) - -[Tables](#tables) +* [Headers](#headers) +* [Emphasis](#emphasis) +* [Lists](#lists) +* [Links](#links) +* [Images](#images) +* [Blockquotes](#blockquotes) +* [Inline HTML](#inline-html) +* [Horizontal Rule](#horizontal-rule) +* [Line Breaks](#line-breaks) +* [Tables](#tables) **[References](#references)** diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md index 493e59af22..9b117319ee 100644 --- a/doc/public_access/public_access.md +++ b/doc/public_access/public_access.md @@ -12,7 +12,7 @@ Public projects can be cloned **without any** authentication. It will also be listed on the [public access directory](/public). -**Any logged in user** will have [Guest](/help/permissions) permissions on the repository. +**Any logged in user** will have [Guest](../permissions/permissions) permissions on the repository. ## Internal projects @@ -20,7 +20,7 @@ Internal projects can be cloned by any logged in user. It will also be listed on the [public access directory](/public) for logged in users. -Any logged in user will have [Guest](/help/permissions) permissions on the repository. +Any logged in user will have [Guest](../permissions/permissions) permissions on the repository. ## How to change project visibility diff --git a/doc/raketasks/backup_hrz.png b/doc/raketasks/backup_hrz.png new file mode 100644 index 0000000000..03e50df1d7 Binary files /dev/null and b/doc/raketasks/backup_hrz.png differ diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 00ce6ed27c..aa58727111 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -1,5 +1,7 @@ # Backup restore + + ## Create a backup of the GitLab system Creates a backup archive of the database and all repositories. This archive will be saved in backup_path (see `config/gitlab.yml`). diff --git a/doc/raketasks/features.md b/doc/raketasks/features.md index eaa9af5f96..99b3d5525b 100644 --- a/doc/raketasks/features.md +++ b/doc/raketasks/features.md @@ -18,21 +18,3 @@ New path: `git@example.org:username/myrepo.git` or `git@example.org:groupname/my ``` bundle exec rake gitlab:enable_namespaces RAILS_ENV=production ``` - -## Rebuild project satellites - -This command will build missing satellites for projects. After this you will be able to **merge a merge request** via GitLab and use the **online editor**. - -``` -bundle exec rake gitlab:satellites:create RAILS_ENV=production -``` - -Example output: - -``` -Creating satellite for abcd.git -[git clone output] -Creating satellite for abcd2.git -[git clone output] -done -``` diff --git a/doc/raketasks/maintenance.md b/doc/raketasks/maintenance.md index 3339dfb03a..9f5d21527c 100644 --- a/doc/raketasks/maintenance.md +++ b/doc/raketasks/maintenance.md @@ -63,6 +63,8 @@ sudo gitlab-rake gitlab:check bundle exec rake gitlab:check RAILS_ENV=production ``` +NOTE: Use SANITIZE=true for gitlab:check if you want to omit project names from the output. + Example output: ``` diff --git a/doc/release/monthly.md b/doc/release/monthly.md index 22824016f2..922189a920 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -107,20 +107,12 @@ Create an annotated tag that points to the version change commit: git tag -a vx.x.0.rc1 -m 'Version x.x.0.rc1' ``` -### **7. Tweet** - -Tweet about the RC release: - -> GitLab x.x.x.rc1 is out. This is a release candidate intended for testing only. Please let us know if you find regressions. - -n -### **8. Update GitLab.com** +### **7. Update GitLab.com** Merge the RC1 code into GitLab.com. Once the build is green, deploy in the morning. - It is important to do this as soon as possible, so we can catch any errors before we release the full version. -### **9. Create a regressions issue** +### **8. Create a regressions issue** On [the GitLab CE issue tracker on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues/) create an issue titled "GitLab X.X regressions" add the following text: @@ -131,6 +123,12 @@ The release manager will comment here about the plans for patch releases. Assign the issue to the release manager and /cc all the core-team members active on the issue tracker. If there are any known bugs in the release add them immediately. +### **9. Tweet** + +Tweet about the RC release: + +> GitLab x.x.x.rc1 is out. This release candidate is only suitable for testing. Please create issues for regressions and add a link from LINK_TO_ISSUE. + # **21st - Preparation ** ### **1. Prepare the blog post** diff --git a/doc/update/6.0-to-7.0.md b/doc/update/6.0-to-7.0.md index b73adc8cfb..881eb2844b 100644 --- a/doc/update/6.0-to-7.0.md +++ b/doc/update/6.0-to-7.0.md @@ -56,7 +56,7 @@ sudo apt-get install logrotate ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch -sudo -u git -H git checkout v1.9.3 # Addresses multiple critical security vulnerabilities +sudo -u git -H git checkout v1.9.6 # Addresses multiple critical security vulnerabilities ``` ## 5. Install libs, migrations, etc. diff --git a/doc/update/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md index 05c9567967..9168989064 100644 --- a/doc/update/mysql_to_postgresql.md +++ b/doc/update/mysql_to_postgresql.md @@ -91,7 +91,7 @@ cd tmp/backups/postgresql sudo -u git -H mysqldump --compatible=postgresql --default-character-set=utf8 -r gitlabhq_production.mysql -u root gitlabhq_production # Clone the database converter -sudo -u git -H git clone https://github.com/lanyrd/mysql-postgresql-converter.git +sudo -u git -H git clone https://github.com/gitlabhq/mysql-postgresql-converter.git # Convert gitlabhq_production.mysql sudo -u git -H mkdir db diff --git a/features/group.feature b/features/group.feature index 71c28c07a3..b5ff03db84 100644 --- a/features/group.feature +++ b/features/group.feature @@ -120,3 +120,24 @@ Feature: Groups When I search for 'Mary' member Then I should see user "Mary Jane" in team list Then I should not see user "John Doe" in team list + + # Group milestones + + Scenario: I should see group "Owned" milestone index page with no milestones + When I visit group "Owned" page + And I click on group milestones + Then I should see group milestones index page has no milestones + + Scenario: I should see group "Owned" milestone index page with milestones + Given Group has projects with milestones + When I visit group "Owned" page + And I click on group milestones + Then I should see group milestones index page with milestones + + Scenario: I should see group "Owned" milestone show page + Given Group has projects with milestones + When I visit group "Owned" page + And I click on group milestones + And I click on one group milestone + Then I should see group milestone with descriptions and expiry date + And I should see group milestone with all issues and MRs assigned to that milestone diff --git a/features/project/commits/user_lookup.feature b/features/project/commits/user_lookup.feature index f3864c0ab3..aa347e24fe 100644 --- a/features/project/commits/user_lookup.feature +++ b/features/project/commits/user_lookup.feature @@ -11,4 +11,4 @@ Feature: Project Browse Commits User Lookup Scenario: I browse another commit from list Given I click on another commit link - Then I see other commit info \ No newline at end of file + Then I see other commit info diff --git a/features/project/edit_issuetracker.feature b/features/project/edit_issuetracker.feature index b5477d3c7a..cc0de07ca6 100644 --- a/features/project/edit_issuetracker.feature +++ b/features/project/edit_issuetracker.feature @@ -15,4 +15,4 @@ Feature: Project Issue Tracker When I visit edit project "Shop" page And change the issue tracker to "Redmine" And I save project - Then I the project should have "Redmine" as issue tracker \ No newline at end of file + Then I the project should have "Redmine" as issue tracker diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature index a204c3e10c..4af2cc8358 100644 --- a/features/project/source/browse_files.feature +++ b/features/project/source/browse_files.feature @@ -38,4 +38,16 @@ Feature: Project Browse files And I click link "Diff" Then I see diff + Scenario: I can browse directory with Browse Dir + Given I click on app directory + And I click on history link + Then I see Browse dir link + Scenario: I can browse file with Browse File + Given I click on readme file + And I click on history link + Then I see Browse file link + + Scenario: I can browse code with Browse Code + Given I click on history link + Then I see Browse code link diff --git a/features/steps/group/group.rb b/features/steps/group/group.rb index d0c8f2d22b..787b0c3dac 100644 --- a/features/steps/group/group.rb +++ b/features/steps/group/group.rb @@ -175,6 +175,42 @@ class Groups < Spinach::FeatureSteps end end + step 'I click on group milestones' do + click_link 'Milestones' + end + + step 'I should see group milestones index page has no milestones' do + page.should have_content('No milestones to show') + end + + step 'Group has projects with milestones' do + group_milestone + end + + step 'I should see group milestones index page with milestones' do + page.should have_content('Version 7.2') + page.should have_content('GL-113') + page.should have_link('2 Issues', href: group_milestone_path("owned", "version-7-2", title: "Version 7.2")) + page.should have_link('3 Merge Requests', href: group_milestone_path("owned", "gl-113", title: "GL-113")) + end + + step 'I click on one group milestone' do + click_link 'GL-113' + 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, 2014') + page.should have_content('Project milestone has no description') + end + + step 'I should see group milestone with all issues and MRs assigned to that milestone' do + page.should have_content('Milestone GL-113') + page.should have_content('Progress: 0 closed – 4 open') + page.should have_link(@issue1.title, href: project_issue_path(@project1, @issue1)) + page.should have_link(@mr3.title, href: project_merge_request_path(@project3, @mr3)) + end + protected def assigned_to_me key @@ -184,4 +220,70 @@ class Groups < Spinach::FeatureSteps def project Group.find_by(name: "Owned").projects.first end + + def group_milestone + group = Group.find_by(name: "Owned") + + @project1 = create :project, + group: group + project2 = create :project, + path: 'gitlab-ci', + group: group + @project3 = create :project, + path: 'cookbook-gitlab', + group: group + milestone1_project1 = create :milestone, + title: "Version 7.2", + project: @project1 + milestone1_project2 = create :milestone, + title: "Version 7.2", + project: project2 + milestone1_project3 = create :milestone, + title: "Version 7.2", + project: @project3 + milestone2_project1 = create :milestone, + title: "GL-113", + project: @project1 + milestone2_project2 = create :milestone, + title: "GL-113", + project: project2 + milestone2_project3 = create :milestone, + title: "GL-113", + project: @project3, + due_date: '2014-08-20', + description: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry' + @issue1 = create :issue, + project: @project1, + assignee: current_user, + author: current_user, + milestone: milestone2_project1 + issue2 = create :issue, + project: project2, + assignee: current_user, + author: current_user, + milestone: milestone1_project2 + issue3 = create :issue, + project: @project3, + assignee: current_user, + author: current_user, + milestone: milestone1_project1 + mr1 = create :merge_request, + source_project: @project1, + target_project: @project1, + assignee: current_user, + author: current_user, + milestone: milestone2_project1 + mr2 = create :merge_request, + source_project: project2, + target_project: project2, + assignee: current_user, + author: current_user, + milestone: milestone2_project2 + @mr3 = create :merge_request, + source_project: @project3, + target_project: @project3, + assignee: current_user, + author: current_user, + milestone: milestone2_project3 + end end diff --git a/features/steps/project/archived.rb b/features/steps/project/archived.rb index dfbe762c43..8b490d2ffc 100644 --- a/features/steps/project/archived.rb +++ b/features/steps/project/archived.rb @@ -34,4 +34,4 @@ class ProjectArchived < Spinach::FeatureSteps click_link "Unarchive" end -end \ No newline at end of file +end diff --git a/features/steps/project/browse_files.rb b/features/steps/project/browse_files.rb index 7cdd1101ac..7134050da6 100644 --- a/features/steps/project/browse_files.rb +++ b/features/steps/project/browse_files.rb @@ -62,4 +62,32 @@ class ProjectBrowseFiles < Spinach::FeatureSteps page.should have_content "File name" page.should have_content "Commit message" end + + step 'I click on app directory' do + click_link 'app' + end + + step 'I click on history link' do + click_link 'history' + end + + step 'I see Browse dir link' do + page.should have_link 'Browse Dir »' + page.should_not have_link 'Browse Code »' + end + + step 'I click on readme file' do + click_link 'README.md' + end + + step 'I see Browse file link' do + page.should have_link 'Browse File »' + page.should_not have_link 'Browse Code »' + end + + step 'I see Browse code link' do + page.should have_link 'Browse Code »' + page.should_not have_link 'Browse File »' + page.should_not have_link 'Browse Dir »' + end end diff --git a/features/steps/project/multiselect_blob.rb b/features/steps/project/multiselect_blob.rb index 3d330e837c..28df7bc931 100644 --- a/features/steps/project/multiselect_blob.rb +++ b/features/steps/project/multiselect_blob.rb @@ -55,4 +55,4 @@ class ProjectMultiselectBlob < Spinach::FeatureSteps step 'I click on "Gemfile.lock" file in repo' do click_link "Gemfile.lock" end -end \ No newline at end of file +end diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index 235c4e2c5d..7db573ad44 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -10,7 +10,7 @@ module SharedProject # Create a specific project called "Shop" And 'I own project "Shop"' do @project = Project.find_by(name: "Shop") - @project ||= create(:project, name: "Shop", namespace: @user.namespace, merge_requests_template: "This merge request should contain the following.") + @project ||= create(:project, name: "Shop", namespace: @user.namespace, snippets_enabled: true, merge_requests_template: "This merge request should contain the following.") @project.team << [@user, :master] end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 90a96f9e5c..8bf1b8236f 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -226,5 +226,9 @@ module API expose :same, as: :compare_same_ref end + + class Contributor < Grape::Entity + expose :name, :email, :commits, :additions, :deletions + end end end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index b6a5806d64..d7d209e16f 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -98,10 +98,14 @@ module API def attributes_for_keys(keys) attrs = {} + keys.each do |key| - attrs[key] = params[key] if params[key].present? or (params.has_key?(key) and params[key] == false) + if params[key].present? or (params.has_key?(key) and params[key] == false) + attrs[key] = params[key] + end end - attrs + + ActionController::Parameters.new(attrs).permit! end # error helpers diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 03806d9343..d091fa4f03 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -150,6 +150,18 @@ module API compare = Gitlab::Git::Compare.new(user_project.repository.raw_repository, params[:from], params[:to], MergeRequestDiff::COMMITS_SAFE_SIZE) present compare, with: Entities::Compare end + + # Get repository contributors + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # GET /projects/:id/repository/contributors + get ':id/repository/contributors' do + authorize! :download_code, user_project + + present user_project.repository.contributors, with: Entities::Contributor + end end end end diff --git a/lib/api/users.rb b/lib/api/users.rb index 92dbe97f0a..69553f1639 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -59,7 +59,7 @@ module API authenticated_as_admin! required_attributes! [:email, :password, :name, :username] attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin] - user = User.build_user(attrs, as: :admin) + user = User.build_user(attrs) admin = attrs.delete(:admin) user.admin = admin unless admin.nil? if user.save @@ -96,7 +96,7 @@ module API admin = attrs.delete(:admin) user.admin = admin unless admin.nil? - if user.update_attributes(attrs, as: :admin) + if user.update_attributes(attrs) present user, with: Entities::UserFull else not_found! diff --git a/lib/gitlab/config_helper.rb b/lib/gitlab/config_helper.rb new file mode 100644 index 0000000000..41880069e4 --- /dev/null +++ b/lib/gitlab/config_helper.rb @@ -0,0 +1,9 @@ +module Gitlab::ConfigHelper + def gitlab_config_features + Gitlab.config.gitlab.default_projects_features + end + + def gitlab_config + Gitlab.config.gitlab + end +end diff --git a/lib/gitlab/contributors.rb b/lib/gitlab/contributors.rb new file mode 100644 index 0000000000..c41e92b620 --- /dev/null +++ b/lib/gitlab/contributors.rb @@ -0,0 +1,9 @@ +module Gitlab + class Contributor + attr_accessor :email, :name, :commits, :additions, :deletions + + def initialize + @commits, @additions, @deletions = 0, 0, 0 + end + end +end diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb index 0ac2828f8d..c4ecb99e7a 100644 --- a/lib/gitlab/ldap/adapter.rb +++ b/lib/gitlab/ldap/adapter.rb @@ -14,7 +14,15 @@ module Gitlab end def self.adapter_options - encryption = config['method'].to_s == 'ssl' ? :simple_tls : nil + encryption = + case config['method'].to_s + when 'ssl' + :simple_tls + when 'tls' + :start_tls + else + nil + end options = { host: config['host'], diff --git a/lib/gitlab/oauth/user.rb b/lib/gitlab/oauth/user.rb index 38e33c0eee..94d59180e1 100644 --- a/lib/gitlab/oauth/user.rb +++ b/lib/gitlab/oauth/user.rb @@ -27,7 +27,7 @@ module Gitlab password_confirmation: password, } - user = model.build_user(opts, as: :admin) + user = model.build_user(opts) user.skip_confirmation! # Services like twitter and github does not return email via oauth diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index e932b64f4f..4b8038843b 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -6,18 +6,35 @@ module Gitlab default_regex end + def username_regex_message + default_regex_message + end + def project_name_regex /\A[a-zA-Z0-9_][a-zA-Z0-9_\-\. ]*\z/ end + def project_regex_message + "can contain only letters, digits, '_', '-' and '.' and space. " \ + "It must start with letter, digit or '_'." + end + def name_regex /\A[a-zA-Z0-9_\-\. ]*\z/ end + def name_regex_message + "can contain only letters, digits, '_', '-' and '.' and space." + end + def path_regex default_regex end + def path_regex_message + default_regex_message + end + def archive_formats_regex #|zip|tar| tar.gz | tar.bz2 | /(zip|tar|tar\.gz|tgz|gz|tar\.bz2|tbz|tbz2|tb2|bz2)/ @@ -48,8 +65,14 @@ module Gitlab protected + def default_regex_message + "can contain only letters, digits, '_', '-' and '.'. " \ + "It must start with letter, digit or '_', optionally preceeded by '.'. " \ + "It must not end in '.git'." + end + def default_regex - /\A[.?]?[a-zA-Z0-9_][a-zA-Z0-9_\-\.]*(? 'test', 'force' => 'yes'}, *cmd) or raise("#{cmd} failed!") end end -end \ No newline at end of file +end diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index e1c0269b29..cc32805f5e 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -30,4 +30,4 @@ describe ApplicationController do controller.send(:check_password_expiration) end end -end \ No newline at end of file +end diff --git a/spec/controllers/blob_controller_spec.rb b/spec/controllers/blob_controller_spec.rb index cea6922e1c..929f6d3b46 100644 --- a/spec/controllers/blob_controller_spec.rb +++ b/spec/controllers/blob_controller_spec.rb @@ -34,4 +34,18 @@ describe Projects::BlobController do it { should respond_with(:not_found) } end end + + describe 'GET show with tree path' do + render_views + + before do + get :show, project_id: project.to_param, id: id + controller.instance_variable_set(:@blob, nil) + end + + context 'redirect to tree' do + let(:id) { 'master/doc' } + it { should redirect_to("/#{project.path_with_namespace}/tree/master/doc") } + end + end end diff --git a/spec/controllers/tree_controller_spec.rb b/spec/controllers/tree_controller_spec.rb index 479118a346..b169c2a678 100644 --- a/spec/controllers/tree_controller_spec.rb +++ b/spec/controllers/tree_controller_spec.rb @@ -40,4 +40,17 @@ describe Projects::TreeController do it { should respond_with(:not_found) } end end + + describe 'GET show with blob path' do + render_views + + before do + get :show, project_id: project.to_param, id: id + end + + context 'redirect to blob' do + let(:id) { 'master/README.md' } + it { should redirect_to("/#{project.path_with_namespace}/blob/master/README.md") } + end + end end diff --git a/spec/factories.rb b/spec/factories.rb index 41cc99cbcb..ad4c56986c 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -32,6 +32,7 @@ FactoryGirl.define do path { name.downcase.gsub(/\s/, '_') } namespace creator + snippets_enabled true trait :public do visibility_level Gitlab::VisibilityLevel::PUBLIC @@ -245,7 +246,7 @@ FactoryGirl.define do end end end - + factory :email do user email do diff --git a/spec/fixtures/doc_sample.txt b/spec/fixtures/doc_sample.txt index 45dbc1aadd..600477e942 100644 --- a/spec/fixtures/doc_sample.txt +++ b/spec/fixtures/doc_sample.txt @@ -1,3 +1,3 @@ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. -Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? \ No newline at end of file +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? diff --git a/spec/javascripts/stat_graph_spec.js b/spec/javascripts/stat_graph_spec.js index b8881769ac..b589af3461 100644 --- a/spec/javascripts/stat_graph_spec.js +++ b/spec/javascripts/stat_graph_spec.js @@ -14,4 +14,4 @@ describe("StatGraph", function () { }) }) -}); \ No newline at end of file +}); diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb new file mode 100644 index 0000000000..a3aae7771b --- /dev/null +++ b/spec/lib/gitlab/regex_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Gitlab::Regex do + describe 'path regex' do + it { 'gitlab-ce'.should match(Gitlab::Regex.path_regex) } + it { 'gitlab_git'.should match(Gitlab::Regex.path_regex) } + it { '_underscore.js'.should match(Gitlab::Regex.path_regex) } + it { '100px.com'.should match(Gitlab::Regex.path_regex) } + it { '?gitlab'.should_not match(Gitlab::Regex.path_regex) } + it { 'git lab'.should_not match(Gitlab::Regex.path_regex) } + it { 'gitlab.git'.should_not match(Gitlab::Regex.path_regex) } + end + + describe 'project name regex' do + it { 'gitlab-ce'.should match(Gitlab::Regex.project_name_regex) } + it { 'GitLab CE'.should match(Gitlab::Regex.project_name_regex) } + it { '100 lines'.should match(Gitlab::Regex.project_name_regex) } + it { 'gitlab.git'.should match(Gitlab::Regex.project_name_regex) } + it { '?gitlab'.should_not match(Gitlab::Regex.project_name_regex) } + end +end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 224b613b47..d7230ec434 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -22,6 +22,23 @@ describe Notify do end end + shared_examples 'an email starting a new thread' do |message_id_prefix| + it 'has a discussion identifier' do + should have_header 'Message-ID', /<#{message_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/ + end + end + + shared_examples 'an answer to an existing thread' do |thread_id_prefix| + it 'has a subject that begins with Re: ' do + should have_subject /^Re: / + end + + it 'has headers that reference an existing thread' do + should have_header 'References', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/ + should have_header 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/ + end + end + describe 'for new users, the email' do let(:example_site_path) { root_path } let(:new_user) { create(:user, email: 'newguy@example.com', created_by_id: 1) } @@ -153,6 +170,7 @@ describe Notify do subject { Notify.new_issue_email(issue.assignee_id, issue.id) } it_behaves_like 'an assignee email' + it_behaves_like 'an email starting a new thread', 'issue' it 'has the correct subject' do should have_subject /#{project.name} \| #{issue.title} \(##{issue.iid}\)/ @@ -161,10 +179,6 @@ describe Notify do it 'contains a link to the new issue' do should have_body_text /#{project_issue_path project, issue}/ end - - it 'has the correct message-id set' do - should have_header 'Message-ID', "