diff options
author | Vinnie Okada <vokada@mrvinn.com> | 2015-03-24 20:03:22 -0600 |
---|---|---|
committer | Vinnie Okada <vokada@mrvinn.com> | 2015-03-24 20:03:22 -0600 |
commit | 057c8c344b6518cb50b81607e0f88734fc164a9e (patch) | |
tree | 996afee0c0f33bd6308f83b330a179dc29dfb48a | |
parent | 637ca0b388382112850fd3052a961bb07db34d14 (diff) | |
parent | b9372c999707558b695fa401b4f660a3d38fce86 (diff) | |
download | gitlab-ce-057c8c344b6518cb50b81607e0f88734fc164a9e.tar.gz |
Merge branch 'master' into markdown-tags
85 files changed, 829 insertions, 530 deletions
diff --git a/CHANGELOG b/CHANGELOG index af0f330884a..217f327a09a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,12 +2,14 @@ Please view this file on the master branch, on stable branches it's out of date. v 7.10.0 (unreleased) - Allow HTML tags in Markdown input + - Include missing events and fix save functionality in admin service template settings form (Stan Hu) - Fix "Import projects from" button to show the correct instructions (Stan Hu) - Fix dots in Wiki slugs causing errors (Stan Hu) - Fix OAuth2 issue importing a new project from GitHub and GitLab (Stan Hu) - Update poltergeist to version 1.6.0 to support PhantomJS 2.0 (Zeger-Jan van de Weg) - Fix cross references when usernames, milestones, or project names contain underscores (Stan Hu) - Disable reference creation for comments surrounded by code/preformatted blocks (Stan Hu) + - Reduce Rack Attack false positives causing 403 errors during HTTP authentication (Stan Hu) - enable line wrapping per default and remove the checkbox to toggle it (Hannes Rosenögger) - extend the commit calendar to show the actual commits made on a date (Hannes Rosenögger) - Fix a link in the patch update guide @@ -17,6 +19,7 @@ v 7.10.0 (unreleased) - Add changelog, license and contribution guide links to project sidebar. - Improve diff UI - Fix alignment of navbar toggle button (Cody Mize) + - Fix checkbox rendering for nested task lists - Identical look of selectboxes in UI - Move "Import existing repository by URL" option to button. - Improve error message when save profile has error. @@ -28,8 +31,17 @@ v 7.10.0 (unreleased) - Restrict permissions on backup files - Improve oauth accounts UI in profile page - Add ability to unlink connected accounts + - Replace commits calendar with faster contribution calendar that includes issues and merge requests + - Add inifinite scroll to user page activity + - Don't show commit comment button when user is not signed in. + - Don't include system notes in issue/MR comment count. + - Don't mark merge request as updated when merge status relative to target branch changes. + - Link note avatar to user. v 7.9.0 + - Send EmailsOnPush email when branch or tag is created or deleted. + +v 7.9.0 (unreleased) - Add HipChat integration documentation (Stan Hu) - Update documentation for object_kind field in Webhook push and tag push Webhooks (Stan Hu) - Fix broken email images (Hannes Rosenögger) @@ -146,7 +158,6 @@ v 7.8.0 - Add API endpoint to fetch all changes on a MergeRequest (Jeroen van Baarsen) - View note image attachments in new tab when clicked instead of downloading them - Improve sorting logic in UI and API. Explicitly define what sorting method is used by default - - Allow more variations for commit messages closing issues (Julien Bianchi and Hannes Rosenögger) - Fix overflow at sidebar when have several items - Add notes for label changes in issue and merge requests - Show tags in commit view (Hannes Rosenögger) @@ -168,7 +179,7 @@ v 7.8.0 - Add a commit calendar to the user profile (Hannes Rosenögger) - Submit comment on command-enter - Notify all members of a group when that group is mentioned in a comment, for example: `@gitlab-org` or `@sales`. - - Extend issue clossing pattern to include "Resolve", "Resolves", "Resolved", "Resolving" and "Close" + - Extend issue clossing pattern to include "Resolve", "Resolves", "Resolved", "Resolving" and "Close" (Julien Bianchi and Hannes Rosenögger) - Fix long broadcast message cut-off on left sidebar (Visay Keo) - Add Project Avatars (Steven Thonus and Hannes Rosenögger) - Password reset token validity increased from 2 hours to 2 days since it is also send on account creation. diff --git a/Gemfile.lock b/Gemfile.lock index 4f1cab43dd5..7da4d3c3583 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -188,7 +188,7 @@ GEM dotenv (>= 0.7) thor (>= 0.13.6) formatador (0.2.4) - gemnasium-gitlab-service (0.2.5) + gemnasium-gitlab-service (0.2.4) rugged (~> 0.21) gemojione (2.0.0) json @@ -516,7 +516,7 @@ GEM rubyntlm (0.5.0) rubypants (0.2.0) rugged (0.21.4) - rugments (1.0.0.beta5) + rugments (1.0.0.beta6) safe_yaml (0.9.7) sanitize (2.1.0) nokogiri (>= 1.4.4) diff --git a/app/assets/javascripts/calendar.js.coffee b/app/assets/javascripts/calendar.js.coffee index 2891a48e249..d08ef9361a6 100644 --- a/app/assets/javascripts/calendar.js.coffee +++ b/app/assets/javascripts/calendar.js.coffee @@ -7,7 +7,7 @@ class @calendar constructor: (timestamps, starting_year, starting_month, calendar_activities_path) -> cal = new CalHeatMap() cal.init - itemName: ["commit"] + itemName: ["contribution"] data: timestamps start: new Date(starting_year, starting_month) domainLabelFormat: "%b" @@ -27,7 +27,6 @@ class @calendar legendCellPadding: 3 onClick: (date, count) -> formated_date = date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate() - $(".calendar_commit_activity").fadeOut 400 $.ajax url: calendar_activities_path data: @@ -36,6 +35,4 @@ class @calendar dataType: "html" success: (data) -> $(".user-calendar-activities").html data - $(".calendar_commit_activity").find(".js-toggle-content").hide() - $(".calendar_commit_activity").fadeIn 400 diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index edf482f33d8..deabaf8a784 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -97,6 +97,7 @@ class Dispatcher new ProjectFork() when 'users:show' new User() + new Activities() switch path.first() when 'admin' diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee index 09c202e42a5..6127d2bb480 100644 --- a/app/assets/javascripts/merge_request.js.coffee +++ b/app/assets/javascripts/merge_request.js.coffee @@ -113,8 +113,14 @@ class @MergeRequest allowed_states = ["failed", "canceled", "running", "pending", "success"] if state in allowed_states $('.ci_widget.ci-' + state).show() + switch state + when "failed", "canceled" + @setMergeButtonClass('btn-danger') + when "running", "pending" + @setMergeButtonClass('btn-warning') else $('.ci_widget.ci-error').show() + @setMergeButtonClass('btn-danger') showCiCoverage: (coverage) -> cov_html = $('<span>') @@ -144,6 +150,9 @@ class @MergeRequest this.$('.merge-in-progress').hide() this.$('.automerge_widget.already_cannot_be_merged').show() + setMergeButtonClass: (css_class) -> + $('.accept_merge_request').removeClass("btn-create").addClass(css_class) + mergeInProgress: -> $.ajax type: 'GET' diff --git a/app/assets/javascripts/project_users_select.js.coffee b/app/assets/javascripts/project_users_select.js.coffee index e22c7c11f1c..80ab1a61ab9 100644 --- a/app/assets/javascripts/project_users_select.js.coffee +++ b/app/assets/javascripts/project_users_select.js.coffee @@ -25,7 +25,7 @@ class @ProjectUsersSelect initSelection: (element, callback) -> id = $(element).val() - if id isnt "" + if id != "" && id != "-1" Api.user(id, callback) @@ -44,10 +44,7 @@ class @ProjectUsersSelect else avatar = gon.default_avatar_url - if user.id == '' - avatarMarkup = '' - else - avatarMarkup = "<div class='user-image'><img class='avatar s24' src='#{avatar}'></div>" + avatarMarkup = "<div class='user-image'><img class='avatar s24' src='#{avatar}'></div>" "<div class='user-result'> #{avatarMarkup} diff --git a/app/assets/stylesheets/generic/calendar.scss b/app/assets/stylesheets/generic/calendar.scss index e2ab7fc51a5..a36fefe22c5 100644 --- a/app/assets/stylesheets/generic/calendar.scss +++ b/app/assets/stylesheets/generic/calendar.scss @@ -1,21 +1,8 @@ .user-calendar-activities { - - .calendar_commit_activity { - padding: 5px 0 0; - } - .calendar_onclick_hr { padding: 0; margin: 10px 0; } - - .calendar_commit_date { - color: #999; - } - - .calendar_activity_summary { - font-size: 14px; - } .str-truncated { max-width: 70%; @@ -31,14 +18,6 @@ background-color: #ddd; } } - - .commit-row-message { - color: #333; - &:hover { - color: #444; - text-decoration: underline; - } - } } /** * This overwrites the default values of the cal-heatmap gem diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 7b7bb88bc20..af6ea58382f 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -15,6 +15,11 @@ word-break: break-all; margin-right: 200px; display: block; + + .file-mode { + margin-left: 10px; + color: #777; + } } .diff-btn-group { @@ -34,11 +39,6 @@ font-family: $monospace_font; font-size: smaller; } - - .file-mode { - font-family: $monospace_font; - margin-left: 10px; - } } .diff-content { overflow: auto; diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index d8fe339b7b3..8abd4207beb 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -137,30 +137,15 @@ background-color: #F1FAF1; } - &.ci-pending { - color: #548; - border-color: #548; - background-color: #F4F1FA; - } - + &.ci-pending, &.ci-running { color: $gl-warning; border-color: $gl-warning; background-color: #FAF5F1; } - &.ci-failed { - color: $gl-danger; - border-color: $gl-danger; - background-color: #FAF1F1; - } - - &.ci-canceled { - color: $gl-warning; - border-color: $gl-danger; - background-color: #FAF5F1; - } - + &.ci-failed, + &.ci-canceled, &.ci-error { color: $gl-danger; border-color: $gl-danger; diff --git a/app/assets/stylesheets/themes/ui_blue.scss b/app/assets/stylesheets/themes/ui_blue.scss index cb7980b5a07..e223058be8b 100644 --- a/app/assets/stylesheets/themes/ui_blue.scss +++ b/app/assets/stylesheets/themes/ui_blue.scss @@ -1,5 +1,5 @@ /** - * Modern GitLab UI theme + * Blue GitLab UI theme */ .ui_blue { @include dark-theme(#BECDE9, #2980b9, #1970a9, #096099); diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb index 44a3f1379d8..76a938c5fe4 100644 --- a/app/controllers/admin/services_controller.rb +++ b/app/controllers/admin/services_controller.rb @@ -46,7 +46,9 @@ class Admin::ServicesController < Admin::ApplicationController :user_key, :device, :priority, :sound, :bamboo_url, :username, :password, :build_key, :server, :teamcity_url, :build_type, :description, :issues_url, :new_issue_url, :restrict_to_branch, - :send_from_committer_email, :disable_diffs + :send_from_committer_email, :disable_diffs, + :push_events, :tag_push_events, :note_events, :issues_events, + :merge_requests_events ]) end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 68130eb128c..679d6897ce9 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -4,10 +4,7 @@ class UsersController < ApplicationController layout :determine_layout def show - @contributed_projects = Project. - where(id: authorized_projects_ids & @user.contributed_projects_ids). - in_group_namespace. - includes(:namespace). + @contributed_projects = contributed_projects.joined(@user). reject(&:forked?) @projects = @user.personal_projects. @@ -16,24 +13,26 @@ class UsersController < ApplicationController # Collect only groups common for both users @groups = @user.groups & GroupsFinder.new.execute(current_user) - # Get user activity feed for projects common for both users - @events = @user.recent_events. - where(project_id: authorized_projects_ids). - with_associations.limit(30) - @title = @user.name @title_url = user_path(@user) respond_to do |format| format.html - format.atom { render layout: false } + + format.atom do + load_events + render layout: false + end + + format.json do + load_events + pager_json("events/_events", @events.count) + end end end def calendar - projects = Project.where(id: authorized_projects_ids & @user.contributed_projects_ids) - - calendar = Gitlab::CommitsCalendar.new(projects, @user) + calendar = contributions_calendar @timestamps = calendar.timestamps @starting_year = calendar.starting_year @starting_month = calendar.starting_month @@ -42,20 +41,13 @@ class UsersController < ApplicationController end def calendar_activities - projects = Project.where(id: authorized_projects_ids & @user.contributed_projects_ids) + @calendar_date = Date.parse(params[:date]) rescue nil + @events = [] - date = Date.parse(params[:date]) rescue nil - if date - @calendar_activities = Gitlab::CommitsCalendar.get_commits_for_date(projects, @user, date) - else - @calendar_activities = {} + if @calendar_date + @events = contributions_calendar.events_by_date(@calendar_date) end - # get the total number of unique commits - @commit_count = @calendar_activities.values.flatten.map(&:id).uniq.count - - @calendar_date = date - render 'calendar_activities', layout: false end @@ -82,4 +74,24 @@ class UsersController < ApplicationController @authorized_projects_ids ||= ProjectsFinder.new.execute(current_user).pluck(:id) end + + def contributed_projects + @contributed_projects = Project. + where(id: authorized_projects_ids & @user.contributed_projects_ids). + includes(:namespace) + end + + def contributions_calendar + @contributions_calendar ||= Gitlab::ContributionsCalendar. + new(contributed_projects.reject(&:forked?), @user) + end + + def load_events + # Get user activity feed for projects common for both users + @events = @user.recent_events. + where(project_id: authorized_projects_ids). + with_associations + + @events = @events.limit(20).offset(params[:offset] || 0) + end end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 15c5dcb6a25..a4bd4d30215 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -58,22 +58,11 @@ module IssuesHelper end def bulk_update_milestone_options - options_for_select(['None (backlog)']) + + options_for_select([['None (backlog)', -1]]) + options_from_collection_for_select(project_active_milestones, 'id', 'title', params[:milestone_id]) end - def bulk_update_assignee_options(project = @project) - options_for_select(['None (unassigned)']) + - options_from_collection_for_select(project.team.members, 'id', - 'name', params[:assignee_id]) - end - - def assignee_options(object, project = @project) - options_from_collection_for_select(project.team.members.sort_by(&:name), - 'id', 'name', object.assignee_id) - end - def milestone_options(object) options_from_collection_for_select(object.project.milestones.active, 'id', 'title', object.milestone_id) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index a14277180c7..7bf51b5b8e8 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -146,6 +146,10 @@ module ProjectsHelper nav_tabs << feature if project.send :"#{feature}_enabled" end + if project.issues_enabled || project.merge_requests_enabled + nav_tabs << [:milestones, :labels] + end + nav_tabs.flatten end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 7d3fcfa7037..c31a556ff7b 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -23,9 +23,9 @@ module SearchHelper # Autocomplete results for various settings pages def default_autocomplete [ - { label: "My Profile settings", url: profile_path }, - { label: "My SSH Keys", url: profile_keys_path }, - { label: "My Dashboard", url: root_path }, + { label: "Profile settings", url: profile_path }, + { label: "SSH Keys", url: profile_keys_path }, + { label: "Dashboard", url: root_path }, { label: "Admin Section", url: admin_root_path }, ] end diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index b55129de292..3cd812825e2 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -16,31 +16,69 @@ module Emails subject: subject("Project was moved")) end - def repository_push_email(project_id, recipient, author_id, branch, compare, reverse_compare = false, send_from_committer_email = false, disable_diffs = false) + def repository_push_email(project_id, recipient, author_id: nil, + ref: nil, + action: nil, + compare: nil, + reverse_compare: false, + send_from_committer_email: false, + disable_diffs: false) + unless author_id && ref && action + raise ArgumentError, "missing keywords: author_id, ref, action" + end + @project = Project.find(project_id) @author = User.find(author_id) @reverse_compare = reverse_compare @compare = compare - @commits = Commit.decorate(compare.commits) - @diffs = compare.diffs - @branch = Gitlab::Git.ref_name(branch) + @ref_name = Gitlab::Git.ref_name(ref) + @ref_type = Gitlab::Git.tag_ref?(ref) ? "tag" : "branch" + @action = action @disable_diffs = disable_diffs - @subject = "[#{@project.path_with_namespace}][#{@branch}] " + if @compare + @commits = Commit.decorate(compare.commits) + @diffs = compare.diffs + end + + @action_name = + case action + when :create + "pushed new" + when :delete + "deleted" + else + "pushed to" + end + + @subject = "[#{@project.path_with_namespace}]" + @subject << "[#{@ref_name}]" if action == :push + @subject << " " + + if action == :push + if @commits.length > 1 + @target_url = namespace_project_compare_url(@project.namespace, + @project, + from: Commit.new(@compare.base), + to: Commit.new(@compare.head)) + @subject << "Deleted " if @reverse_compare + @subject << "#{@commits.length} commits: #{@commits.first.title}" + else + @target_url = namespace_project_commit_url(@project.namespace, + @project, @commits.first) - if @commits.length > 1 - @target_url = namespace_project_compare_url(@project.namespace, - @project, - from: Commit.new(@compare.base), - to: Commit.new(@compare.head)) - @subject << "Deleted " if @reverse_compare - @subject << "#{@commits.length} commits: #{@commits.first.title}" + @subject << "Deleted 1 commit: " if @reverse_compare + @subject << @commits.first.title + end else - @target_url = namespace_project_commit_url(@project.namespace, - @project, @commits.first) + unless action == :delete + @target_url = namespace_project_tree_url(@project.namespace, + @project, @ref_name) + end - @subject << "Deleted 1 commit: " if @reverse_compare - @subject << @commits.first.title + subject_action = @action_name.dup + subject_action[0] = subject_action[0].capitalize + @subject << "#{subject_action} #{@ref_type} #{@ref_name}" end @disable_footer = true diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb index 410e8dc820b..bbb3b301a9f 100644 --- a/app/models/concerns/taskable.rb +++ b/app/models/concerns/taskable.rb @@ -5,7 +5,7 @@ # Used by MergeRequest and Issue module Taskable TASK_PATTERN_MD = /^(?<bullet> *[*-] *)\[(?<checked>[ xX])\]/.freeze - TASK_PATTERN_HTML = /^<li>\[(?<checked>[ xX])\]/.freeze + TASK_PATTERN_HTML = /^<li>(?<p_tag>\s*<p>)?\[(?<checked>[ xX])\]/.freeze # Change the state of a task list item for this Taskable. Edit the object's # description by finding the nth task item and changing its checkbox diff --git a/app/models/event.rb b/app/models/event.rb index 2103a48a71b..57f6d5cd4e0 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -55,6 +55,12 @@ class Event < ActiveRecord::Base order('id DESC').limit(100). update_all(updated_at: Time.now) end + + def contributions + where("action = ? OR (target_type in (?) AND action in (?))", + Event::PUSHED, ["MergeRequest", "Issue"], + [Event::CREATED, Event::CLOSED, Event::MERGED]) + end end def proper? diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 798306f6dcc..5634f9a686e 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -105,6 +105,15 @@ class MergeRequest < ActiveRecord::Base state :unchecked state :can_be_merged state :cannot_be_merged + + around_transition do |merge_request, transition, block| + merge_request.record_timestamps = false + begin + block.call + ensure + merge_request.record_timestamps = true + end + end end validates :source_project, presence: true, unless: :allow_broken diff --git a/app/models/note.rb b/app/models/note.rb index 27b583a869a..e86160e7cd9 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -48,6 +48,7 @@ class Note < ActiveRecord::Base scope :inline, ->{ where("line_code IS NOT NULL") } scope :not_inline, ->{ where(line_code: [nil, '']) } scope :system, ->{ where(system: true) } + scope :user, ->{ where(system: false) } scope :common, ->{ where(noteable_type: ["", nil]) } scope :fresh, ->{ order(created_at: :asc, id: :asc) } scope :inc_author_project, ->{ includes(:project, :author) } diff --git a/app/models/project_contributions.rb b/app/models/project_contributions.rb deleted file mode 100644 index bfe9928b158..00000000000 --- a/app/models/project_contributions.rb +++ /dev/null @@ -1,32 +0,0 @@ -class ProjectContributions - attr_reader :project, :user - - def initialize(project, user) - @project, @user = project, user - end - - def commits_log - repository = project.repository - - if !repository.exists? || repository.empty? - return {} - end - - Rails.cache.fetch(cache_key) do - repository.commits_per_day_for_user(user) - end - end - - def user_commits_on_date(date) - repository = @project.repository - - if !repository.exists? || repository.empty? - return [] - end - commits = repository.commits_by_user_on_date_log(@user, date) - end - - def cache_key - "#{Date.today.to_s}-commits-log-#{project.id}-#{user.email}" - end -end diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb index acb5e7f1af5..6f6e5950aab 100644 --- a/app/models/project_services/emails_on_push_service.rb +++ b/app/models/project_services/emails_on_push_service.rb @@ -36,13 +36,19 @@ class EmailsOnPushService < Service end def supported_events - %w(push) + %w(push tag_push) end def execute(push_data) return unless supported_events.include?(push_data[:object_kind]) - EmailsOnPushWorker.perform_async(project_id, recipients, push_data, send_from_committer_email?, disable_diffs?) + EmailsOnPushWorker.perform_async( + project_id, + recipients, + push_data, + send_from_committer_email: send_from_committer_email?, + disable_diffs: disable_diffs? + ) end def send_from_committer_email? diff --git a/app/models/repository.rb b/app/models/repository.rb index 082ad7a0c6a..77765cae1a0 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -149,41 +149,6 @@ class Repository end end - def timestamps_by_user_log(user) - author_emails = '(' + user.all_emails.map{ |e| Regexp.escape(e) }.join('|') + ')' - args = %W(git log -E --author=#{author_emails} --since=#{(Date.today - 1.year).to_s} --branches --pretty=format:%cd --date=short) - dates = Gitlab::Popen.popen(args, path_to_repo).first.split("\n") - - if dates.present? - dates - else - [] - end - end - - def commits_by_user_on_date_log(user, date) - # format the date string for git - start_date = date.strftime("%Y-%m-%d 00:00:00") - end_date = date.strftime("%Y-%m-%d 23:59:59") - - author_emails = '(' + user.all_emails.map{ |e| Regexp.escape(e) }.join('|') + ')' - args = %W(git log -E --author=#{author_emails} --after=#{start_date.to_s} --until=#{end_date.to_s} --branches --pretty=format:%h) - commits = Gitlab::Popen.popen(args, path_to_repo).first.split("\n") - - commits.map! do |commit_id| - commit(commit_id) - end - end - - def commits_per_day_for_user(user) - timestamps_by_user_log(user). - group_by { |commit_date| commit_date }. - inject({}) do |hash, (timestamp_date, commits)| - hash[timestamp_date] = commits.count - hash - end - end - def lookup_cache @lookup_cache ||= {} end diff --git a/app/models/user.rb b/app/models/user.rb index ba325132df8..979150b4d68 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -110,6 +110,7 @@ class User < ActiveRecord::Base has_many :notes, dependent: :destroy, foreign_key: :author_id has_many :merge_requests, dependent: :destroy, foreign_key: :author_id has_many :events, dependent: :destroy, foreign_key: :author_id, class_name: "Event" + has_many :subscriptions, dependent: :destroy has_many :recent_events, -> { order "id DESC" }, foreign_key: :author_id, class_name: "Event" has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue" has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest" @@ -603,13 +604,10 @@ class User < ActiveRecord::Base end def contributed_projects_ids - Event.where(author_id: self). + Event.contributions.where(author_id: self). where("created_at > ?", Time.now - 1.year). - where("action = :pushed OR (target_type = 'MergeRequest' AND action = :created)", - pushed: Event::PUSHED, created: Event::CREATED). reorder(project_id: :desc). select(:project_id). - uniq - .map(&:project_id) + uniq.map(&:project_id) end end diff --git a/app/services/issues/bulk_update_service.rb b/app/services/issues/bulk_update_service.rb index c7cd20b6b60..eb07413ee94 100644 --- a/app/services/issues/bulk_update_service.rb +++ b/app/services/issues/bulk_update_service.rb @@ -4,9 +4,9 @@ module Issues issues_ids = params.delete(:issues_ids).split(",") issue_params = params - issue_params.delete(:state_event) unless issue_params[:state_event].present? - issue_params.delete(:milestone_id) unless issue_params[:milestone_id].present? - issue_params.delete(:assignee_id) unless issue_params[:assignee_id].present? + issue_params.delete(:state_event) unless issue_params[:state_event].present? + issue_params.delete(:milestone_id) unless issue_params[:milestone_id].present? + issue_params.delete(:assignee_id) unless issue_params[:assignee_id].present? issues = Issue.where(id: issues_ids) issues.each do |issue| diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index c61d67a7893..3371fe7d5ef 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -14,6 +14,9 @@ module Issues issue.update_nth_task(params[:task_num].to_i, false) end + params[:assignee_id] = "" if params[:assignee_id] == "-1" + params[:milestone_id] = "" if params[:milestone_id] == "-1" + old_labels = issue.labels.to_a if params.present? && issue.update_attributes(params.except(:state_event, diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index 7eef2c2d6a5..e9b526d1fb7 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -53,7 +53,7 @@ module MergeRequests if merge_request.source_branch == @branch_name || force_push? merge_request.reload_code - update_merge_request(merge_request) + merge_request.mark_as_unchecked else mr_commit_ids = merge_request.commits.map(&:id) push_commit_ids = @commits.map(&:id) @@ -61,20 +61,14 @@ module MergeRequests if matches.any? merge_request.reload_code - update_merge_request(merge_request) + merge_request.mark_as_unchecked else - update_merge_request(merge_request) + merge_request.mark_as_unchecked end end end end - def update_merge_request(merge_request) - MergeRequests::UpdateService.new( - merge_request.target_project, - @current_user, merge_status: 'unchecked').execute(merge_request) - end - # Add comment about pushing new commits to merge requests def comment_mr_with_commits merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 870b50bb60d..0ac6dfea6fd 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -23,6 +23,9 @@ module MergeRequests merge_request.update_nth_task(params[:task_num].to_i, false) end + params[:assignee_id] = "" if params[:assignee_id] == "-1" + params[:milestone_id] = "" if params[:milestone_id] == "-1" + old_labels = merge_request.labels.to_a if params.present? && merge_request.update_attributes( diff --git a/app/views/admin/broadcast_messages/index.html.haml b/app/views/admin/broadcast_messages/index.html.haml index c0afaf16d8f..7e29311bf42 100644 --- a/app/views/admin/broadcast_messages/index.html.haml +++ b/app/views/admin/broadcast_messages/index.html.haml @@ -21,13 +21,11 @@ .form-group.js-toggle-colors-container.hide = f.label :color, "Background Color", class: 'control-label' .col-sm-10 - = f.text_field :color, placeholder: "#AA33EE", class: "form-control" - .light 6 character hex values starting with a # sign. + = f.color_field :color, value: "#AA33EE", class: "form-control" .form-group.js-toggle-colors-container.hide = f.label :font, "Font Color", class: 'control-label' .col-sm-10 - = f.text_field :font, placeholder: "#224466", class: "form-control" - .light 6 character hex values starting with a # sign. + = f.color_field :font, value: "#224466", class: "form-control" .form-group = f.label :starts_at, class: 'control-label' .col-sm-10.datetime-controls diff --git a/app/views/admin/services/_form.html.haml b/app/views/admin/services/_form.html.haml index a953833b37c..18b7e8ba270 100644 --- a/app/views/admin/services/_form.html.haml +++ b/app/views/admin/services/_form.html.haml @@ -14,6 +14,11 @@ = preserve do = markdown @service.help + .form-group + = f.label :active, "Active", class: "control-label" + .col-sm-10 + = f.check_box :active + - if @service.supported_events.length > 1 .form-group = f.label :url, "Trigger", class: 'control-label' @@ -34,6 +39,14 @@ %strong Tag push events %p.light This url will be triggered when a new tag is pushed to the repository + - if @service.supported_events.include?("note") + %div + = f.check_box :note_events, class: 'pull-left' + .prepend-left-20 + = f.label :note_events, class: 'list-label' do + %strong Comments + %p.light + This url will be triggered when someone adds a comment - if @service.supported_events.include?("issue") %div = f.check_box :issues_events, class: 'pull-left' diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb index cb1291cf3bf..c6fa8f0ee36 100644 --- a/app/views/devise/mailer/confirmation_instructions.html.erb +++ b/app/views/devise/mailer/confirmation_instructions.html.erb @@ -6,4 +6,4 @@ <p>You can confirm your account through the link below:</p> <% end %> -<p><%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %></p> +<p><%= link_to 'Confirm your account', confirmation_url(@resource, confirmation_token: @token) %></p> diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb index 7913e88beb6..23b31da92d8 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 @@ <p>Someone has requested a link to change your password, and you can do this through the link below.</p> -<p><%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %></p> +<p><%= link_to 'Change your password', edit_password_url(@resource, reset_password_token: @token) %></p> <p>If you didn't request this, please ignore this email.</p> <p>Your password won't change until you access the link above and create a new one.</p> diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb index 8c2a4f0c2d9..79d6c761d8f 100644 --- a/app/views/devise/mailer/unlock_instructions.html.erb +++ b/app/views/devise/mailer/unlock_instructions.html.erb @@ -4,4 +4,4 @@ <p>Click the link below to unlock your account:</p> -<p><%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %></p> +<p><%= link_to 'Unlock your account', unlock_url(@resource, unlock_token: @token) %></p> diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml index 0640739b5d7..56048e99c17 100644 --- a/app/views/devise/passwords/edit.html.haml +++ b/app/views/devise/passwords/edit.html.haml @@ -11,7 +11,7 @@ %div = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm new password", required: true .clearfix - = f.submit "Change my password", class: "btn btn-primary" + = f.submit "Change your password", class: "btn btn-primary" .clearfix.prepend-top-20 %p diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb index b11817af95d..f379e71ae5b 100644 --- a/app/views/devise/registrations/edit.html.erb +++ b/app/views/devise/registrations/edit.html.erb @@ -21,8 +21,8 @@ <div><%= f.submit "Update", class: "input_button" %></div> <% end %> -<h3>Cancel my account</h3> +<h3>Cancel your account</h3> -<p>Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>.</p> +<p>Unhappy? <%= link_to "Cancel your account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>.</p> <%= link_to "Back", :back %> diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index ed03f885dcd..246a6c1bdfd 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -53,7 +53,7 @@ %code .panel .well-list .panel.panel-default - .panel-heading My list + .panel-heading Your list %ul.well-list %li One item diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml index fc8a487ece7..b1c2e1a7b19 100644 --- a/app/views/layouts/_head_panel.html.haml +++ b/app/views/layouts/_head_panel.html.haml @@ -25,7 +25,7 @@ = link_to explore_root_path, title: "Explore", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do %i.fa.fa-globe %li - = link_to user_snippets_path(current_user), title: "My snippets", class: 'has_bottom_tooltip', 'data-original-title' => 'My snippets' do + = link_to user_snippets_path(current_user), title: "Your snippets", class: 'has_bottom_tooltip', 'data-original-title' => 'Your snippets' do %i.fa.fa-clipboard - if current_user.is_admin? %li diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 91cae2b572c..52681865d64 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -44,11 +44,12 @@ %span Graphs - = nav_link(controller: :milestones) do - = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do - %i.fa.fa-clock-o - %span - Milestones + - if project_nav_tab? :milestones + = nav_link(controller: :milestones) do + = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do + %i.fa.fa-clock-o + %span + Milestones - if project_nav_tab? :issues = nav_link(controller: :issues) do @@ -67,11 +68,12 @@ Merge Requests %span.count.merge_counter= @project.merge_requests.opened.count - = nav_link(controller: :labels) do - = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do - %i.fa.fa-tags - %span - Labels + - if project_nav_tab? :labels + = nav_link(controller: :labels) do + = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do + %i.fa.fa-tags + %span + Labels - if project_nav_tab? :wiki = nav_link(controller: :wikis) do diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml index 039b92df2be..bbf7004c906 100644 --- a/app/views/notify/repository_push_email.html.haml +++ b/app/views/notify/repository_push_email.html.haml @@ -1,66 +1,67 @@ -%h3 #{@author.name} pushed to #{@branch} at #{link_to @project.name_with_namespace, namespace_project_url(@project.namespace, @project)} +%h3 #{@author.name} #{@action_name} #{@ref_type} #{@ref_name} at #{link_to @project.name_with_namespace, namespace_project_url(@project.namespace, @project)} -- if @reverse_compare - %p - %strong WARNING: - The push did not contain any new commits, but force pushed to delete the commits and changes below. +- if @compare + - if @reverse_compare + %p + %strong WARNING: + The push did not contain any new commits, but force pushed to delete the commits and changes below. -%h4 - = @reverse_compare ? "Deleted commits:" : "Commits:" + %h4 + = @reverse_compare ? "Deleted commits:" : "Commits:" -%ul - - @commits.each do |commit| - %li - %strong #{link_to commit.short_id, namespace_project_commit_url(@project.namespace, @project, commit)} - %div - %span by #{commit.author_name} - %i at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")} - %pre.commit-message - = commit.safe_message + %ul + - @commits.each do |commit| + %li + %strong #{link_to commit.short_id, namespace_project_commit_url(@project.namespace, @project, commit)} + %div + %span by #{commit.author_name} + %i at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")} + %pre.commit-message + = commit.safe_message -%h4 #{pluralize @diffs.count, "changed file"}: + %h4 #{pluralize @diffs.count, "changed file"}: -%ul - - @diffs.each_with_index do |diff, i| - %li.file-stats - %a{href: "#{@target_url if @disable_diffs}#diff-#{i}" } - - if diff.deleted_file - %span.deleted-file - − + %ul + - @diffs.each_with_index do |diff, i| + %li.file-stats + %a{href: "#{@target_url if @disable_diffs}#diff-#{i}" } + - if diff.deleted_file + %span.deleted-file + − + = diff.old_path + - elsif diff.renamed_file = diff.old_path - - elsif diff.renamed_file - = diff.old_path - → - = diff.new_path - - elsif diff.new_file - %span.new-file - + + → = diff.new_path - - else - = diff.new_path - -- unless @disable_diffs - %h4 Changes: - - @diffs.each_with_index do |diff, i| - %li{id: "diff-#{i}"} - %a{href: @target_url + "#diff-#{i}"} - - if diff.deleted_file - %strong - = diff.old_path - deleted - - elsif diff.renamed_file - %strong - = diff.old_path - → - %strong + - elsif diff.new_file + %span.new-file + + + = diff.new_path + - else = diff.new_path - - else - %strong - = diff.new_path - %hr - %pre - = color_email_diff(diff.diff) - %br -- if @compare.timeout - %h5 Huge diff. To prevent performance issues changes are hidden + - unless @disable_diffs + %h4 Changes: + - @diffs.each_with_index do |diff, i| + %li{id: "diff-#{i}"} + %a{href: @target_url + "#diff-#{i}"} + - if diff.deleted_file + %strong + = diff.old_path + deleted + - elsif diff.renamed_file + %strong + = diff.old_path + → + %strong + = diff.new_path + - else + %strong + = diff.new_path + %hr + %pre + = color_email_diff(diff.diff) + %br + + - if @compare.timeout + %h5 Huge diff. To prevent performance issues changes are hidden diff --git a/app/views/notify/repository_push_email.text.haml b/app/views/notify/repository_push_email.text.haml index 8d67a42234e..97a176ed2a3 100644 --- a/app/views/notify/repository_push_email.text.haml +++ b/app/views/notify/repository_push_email.text.haml @@ -1,47 +1,49 @@ -#{@author.name} pushed to #{@branch} at #{@project.name_with_namespace} -\ -\ -- if @reverse_compare - WARNING: The push did not contain any new commits, but force pushed to delete the commits and changes below. +#{@author.name} #{@action_name} #{@ref_type} #{@ref_name} at #{@project.name_with_namespace} +- if @compare \ \ -= @reverse_compare ? "Deleted commits:" : "Commits:" -- @commits.each do |commit| - #{commit.short_id} by #{commit.author_name} at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")} - #{commit.safe_message} - \- - - - - -\ -\ -#{pluralize @diffs.count, "changed file"}: -\ -- @diffs.each do |diff| - - if diff.deleted_file - \- − #{diff.old_path} - - elsif diff.renamed_file - \- #{diff.old_path} → #{diff.new_path} - - elsif diff.new_file - \- + #{diff.new_path} - - else - \- #{diff.new_path} -- unless @disable_diffs + - if @reverse_compare + WARNING: The push did not contain any new commits, but force pushed to delete the commits and changes below. + \ + \ + = @reverse_compare ? "Deleted commits:" : "Commits:" + - @commits.each do |commit| + #{commit.short_id} by #{commit.author_name} at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")} + #{commit.safe_message} + \- - - - - + \ \ + #{pluralize @diffs.count, "changed file"}: \ - Changes: - @diffs.each do |diff| - \ - \===================================== - if diff.deleted_file - #{diff.old_path} deleted + \- − #{diff.old_path} - elsif diff.renamed_file - #{diff.old_path} → #{diff.new_path} + \- #{diff.old_path} → #{diff.new_path} + - elsif diff.new_file + \- + #{diff.new_path} - else - = diff.new_path - \===================================== - != diff.diff -- if @compare.timeout - \ - \ - Huge diff. To prevent performance issues it was hidden -\ -\ -View it on GitLab: #{@target_url} + \- #{diff.new_path} + - unless @disable_diffs + \ + \ + Changes: + - @diffs.each do |diff| + \ + \===================================== + - if diff.deleted_file + #{diff.old_path} deleted + - elsif diff.renamed_file + #{diff.old_path} → #{diff.new_path} + - else + = diff.new_path + \===================================== + != diff.diff + - if @compare.timeout + \ + \ + Huge diff. To prevent performance issues it was hidden + - if @target_url + \ + \ + View it on GitLab: #{@target_url} diff --git a/app/views/profiles/history.html.haml b/app/views/profiles/history.html.haml index 9cafe03b8b3..b1ab433f48f 100644 --- a/app/views/profiles/history.html.haml +++ b/app/views/profiles/history.html.haml @@ -1,5 +1,5 @@ %h3.page-title - My Account History + Your Account History %p.light All events created by your account are listed below. %hr diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml index 965d5e032f9..0904c50c88b 100644 --- a/app/views/profiles/keys/index.html.haml +++ b/app/views/profiles/keys/index.html.haml @@ -3,8 +3,6 @@ .pull-right = link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new" %p.light - My SSH keys: #{@keys.count} - %br Before you can add an SSH key you need to = link_to "generate it.", help_page_path("ssh", "README") %hr diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index c0e13e67be3..a295a0d6cdc 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -19,7 +19,7 @@ .fork-buttons - if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 - = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to my fork' do + = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork' do = link_to_toggle_fork - else = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project" do diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 4c853f577e9..c6026f96804 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -13,7 +13,7 @@ - note_count = @note_counts.fetch(commit.id, 0) - else - notes = project.notes.for_commit_id(commit.id) - - note_count = notes.count + - note_count = notes.user.count - if note_count > 0 %span.light diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 860ab096341..672a6635321 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -13,12 +13,13 @@ - submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path) = submodule_link(submodule_item, @commit.id) - else - - if diff_file.renamed_file - %span= "#{diff_file.old_path} renamed to #{diff_file.new_path}" - - else - %span= diff_file.new_path - - if diff_file.mode_changed? - %span.file-mode= "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}" + %span + - if diff_file.renamed_file + = "#{diff_file.old_path} renamed to #{diff_file.new_path}" + - else + = diff_file.new_path + - if diff_file.mode_changed? + %span.file-mode= "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}" .diff-btn-group - if blob.text? diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml index b1c987563f1..e691db9c08e 100644 --- a/app/views/projects/diffs/_text_file.html.haml +++ b/app/views/projects/diffs/_text_file.html.haml @@ -16,7 +16,7 @@ - else %td.old_line = link_to raw(type == "new" ? " " : line_old), "##{line_code}", id: line_code - - if @comments_allowed + - if @comments_allowed && can?(current_user, :write_note, @project) = link_to_new_diff_note(line_code) %td.new_line{data: {linenumber: line.new_pos}} = link_to raw(type == "old" ? " " : line.new_pos) , "##{line_code}", id: line_code diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 7b06fe72882..998e74d12cf 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -10,11 +10,12 @@ - if issue.closed? %span CLOSED - - if issue.notes.any? + - note_count = issue.notes.user.count + - if note_count > 0 %span %i.fa.fa-comments - = issue.notes.count + = note_count .issue-info = link_to "##{issue.iid}", issue_path(issue), class: "light" diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml index 2305fce112e..ad993db6c0b 100644 --- a/app/views/projects/labels/_form.html.haml +++ b/app/views/projects/labels/_form.html.haml @@ -16,9 +16,9 @@ .col-sm-10 .input-group .input-group-addon.label-color-preview - = f.color_field :color, placeholder: "#AA33EE", class: "form-control" + = f.color_field :color, value: "#AA33EE", class: "form-control" .help-block - 6 character hex values starting with a # sign. + Choose any color. %br Or you can choose one of suggested colors below diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index ecbff722b42..4f30d1e69f7 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -16,11 +16,12 @@ %span.label-branch< %i.fa.fa-code-fork %span= merge_request.target_branch - - if merge_request.notes.any? + - note_count = merge_request.mr_and_commit_notes.user.count + - if note_count > 0 %span %i.fa.fa-comments - = merge_request.mr_and_commit_notes.count + = note_count .merge-request-info = link_to "##{merge_request.iid}", merge_request_path(merge_request), class: "light" - if merge_request.assignee diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index ca4ceecb225..a74aede4e6b 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -40,7 +40,7 @@ = link_to merge_request_path(@merge_request) do %i.fa.fa-comments Discussion - %span.badge= @merge_request.mr_and_commit_notes.count + %span.badge= @merge_request.mr_and_commit_notes.user.count %li.commits-tab{data: {action: 'commits'}} = link_to merge_request_path(@merge_request), title: 'Commits' do %i.fa.fa-history diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 110d8967342..25cc0030965 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -60,11 +60,12 @@ Participants %span.badge= @users.count - .pull-right - = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do - %i.fa.fa-plus - New Issue - = link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_id: @milestone.id), class: "btn edit-milestone-link btn-grouped" + - if @project.issues_enabled + .pull-right + = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do + %i.fa.fa-plus + New Issue + = link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_id: @milestone.id), class: "btn edit-milestone-link btn-grouped" .tab-content .tab-pane.active#tab-issues diff --git a/app/views/projects/notes/_discussion.html.haml b/app/views/projects/notes/_discussion.html.haml index f4c6fad2fed..3561ca49f81 100644 --- a/app/views/projects/notes/_discussion.html.haml +++ b/app/views/projects/notes/_discussion.html.haml @@ -2,7 +2,8 @@ .timeline-entry .timeline-entry-inner .timeline-icon - = image_tag avatar_icon(note.author_email), class: "avatar s40" + = link_to user_path(note.author) do + = image_tag avatar_icon(note.author_email), class: "avatar s40" .timeline-content - if note.for_merge_request? - if note.outdated? diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index f3d00a6f06d..71bdf5c8f2a 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -4,7 +4,8 @@ - if note.system %span.fa.fa-circle - else - = image_tag avatar_icon(note.author_email), class: "avatar s40" + = link_to user_path(note.author) do + = image_tag avatar_icon(note.author_email), class: "avatar s40" .timeline-content .note-header .note-actions @@ -21,7 +22,8 @@ %i.fa.fa-trash-o.cred Remove - if note.system - = image_tag avatar_icon(note.author_email), class: "avatar s16" + = link_to user_path(note.author) do + = image_tag avatar_icon(note.author_email), class: "avatar s16" = link_to_member(@project, note.author, avatar: false) %span.author-username = '@' + note.author.username diff --git a/app/views/projects/notes/discussions/_diff.html.haml b/app/views/projects/notes/discussions/_diff.html.haml index f717c77a898..711aa39101b 100644 --- a/app/views/projects/notes/discussions/_diff.html.haml +++ b/app/views/projects/notes/discussions/_diff.html.haml @@ -2,13 +2,13 @@ - if diff .diff-file .diff-header - - if diff.deleted_file - %span= diff.old_path - - else - %span= diff.new_path - - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode - %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}" - %br/ + %span + - if diff.deleted_file + = diff.old_path + - else + = diff.new_path + - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode + %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}" .diff-content %table - note.truncated_diff_lines.each do |line| diff --git a/app/views/snippets/current_user_index.html.haml b/app/views/snippets/current_user_index.html.haml index b2b7ea4df0e..0df5ade500d 100644 --- a/app/views/snippets/current_user_index.html.haml +++ b/app/views/snippets/current_user_index.html.haml @@ -1,5 +1,5 @@ %h3.page-title - My Snippets + Your Snippets .pull-right = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do Add new snippet diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml index 0d71c41e2e7..5cd8ae26cf9 100644 --- a/app/views/snippets/index.html.haml +++ b/app/views/snippets/index.html.haml @@ -2,12 +2,12 @@ Public snippets .pull-right - + - if current_user = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do Add new snippet = link_to user_snippets_path(current_user), class: "btn btn-grouped" do - My snippets + Your snippets %p.light Public snippets created by you and other users are listed here diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml index edfa2092df9..55a990c94ed 100644 --- a/app/views/snippets/show.html.haml +++ b/app/views/snippets/show.html.haml @@ -23,7 +23,7 @@ .back-link - if @snippet.author == current_user = link_to user_snippets_path(current_user) do - ← my snippets + ← your snippets - else = link_to snippets_path do ← discover snippets diff --git a/app/views/users/_projects.html.haml b/app/views/users/_projects.html.haml index 6c7779be30e..b7383d5594e 100644 --- a/app/views/users/_projects.html.haml +++ b/app/views/users/_projects.html.haml @@ -1,5 +1,5 @@ - if @contributed_projects.present? - .panel.panel-default + .panel.panel-default.contributed-projects .panel-heading Projects contributed to = render 'shared/projects_list', projects: @contributed_projects.sort_by(&:star_count).reverse, diff --git a/app/views/users/calendar.html.haml b/app/views/users/calendar.html.haml index d113ceeb753..488f49267c7 100644 --- a/app/views/users/calendar.html.haml +++ b/app/views/users/calendar.html.haml @@ -1,4 +1,7 @@ -%h4 Commits calendar +%h4 + Contributions calendar + .pull-right + %small Issues, merge requests and push events #cal-heatmap.calendar :javascript new calendar( diff --git a/app/views/users/calendar_activities.html.haml b/app/views/users/calendar_activities.html.haml index 7c0cecfadb5..027a93a75fc 100644 --- a/app/views/users/calendar_activities.html.haml +++ b/app/views/users/calendar_activities.html.haml @@ -1,33 +1,23 @@ -.calendar_commit_activity - %hr - %h4 - Commit Activity - %strong - - if @commit_count == 0 - no - - else - = @commit_count - %span.calendar_commit_date - unique - = 'commit'.pluralize(@commit_count) - on - = @calendar_date.strftime("%b %d, %Y") rescue '' - -unless @commit_count == 0 - %hr - - @calendar_activities.each do |project, commits| - - next if commits.empty? - %div.js-toggle-container +%h4.prepend-top-20 + %span.light Contributions for + %strong #{@calendar_date.to_s(:short)} + +%ul.bordered-list + - @events.sort_by(&:created_at).each do |event| + %li + %span.light + %i.fa.fa-clock-o + = event.created_at.to_s(:time) + - if event.push? + #{event.action_name} #{event.ref_type} #{event.ref_name} + - else + = event_action_name(event) + - if event.target + %strong= link_to "##{event.target_iid}", [event.project.namespace.becomes(Namespace), event.project, event.target] + + at %strong - = pluralize(commits.count, 'commit') - in project - = link_to project.name_with_namespace, project_path(project) - %a.text-expander.js-toggle-button … - %hr - %div.js-toggle-content - - commits.each do |commit| - %span.monospace - = commit.committed_date.strftime("%H:%M") - = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" - = link_to commit.message, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message str-truncated" - %br - %hr + - if event.project + = link_to_project event.project + - else + = event.project_name diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index fd96020d129..0653fb871ae 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -40,7 +40,8 @@ %strong %i.fa.fa-rss - = render @events + .content_list + = spinner %aside.col-md-4 = render 'profile', user: @user = render 'projects' diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb index e59ca81defe..89fa2117dd2 100644 --- a/app/workers/emails_on_push_worker.rb +++ b/app/workers/emails_on_push_worker.rb @@ -1,40 +1,49 @@ class EmailsOnPushWorker include Sidekiq::Worker - def perform(project_id, recipients, push_data, send_from_committer_email = false, disable_diffs = false) + def perform(project_id, recipients, push_data, send_from_committer_email: false, disable_diffs: false) project = Project.find(project_id) before_sha = push_data["before"] after_sha = push_data["after"] - branch = push_data["ref"] + ref = push_data["ref"] author_id = push_data["user_id"] - if Gitlab::Git.blank_ref?(before_sha) || Gitlab::Git.blank_ref?(after_sha) - # skip if new branch was pushed or branch was removed - return true - end + action = + if Gitlab::Git.blank_ref?(before_sha) + :create + elsif Gitlab::Git.blank_ref?(after_sha) + :delete + else + :push + end - compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha) + compare = nil + reverse_compare = false + if action == :push + compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha) - return false if compare.same + return false if compare.same - if compare.commits.empty? - compare = Gitlab::Git::Compare.new(project.repository.raw_repository, after_sha, before_sha) + if compare.commits.empty? + compare = Gitlab::Git::Compare.new(project.repository.raw_repository, after_sha, before_sha) - reverse_compare = true + reverse_compare = true - return false if compare.commits.empty? + return false if compare.commits.empty? + end end recipients.split(" ").each do |recipient| Notify.repository_push_email( project_id, recipient, - author_id, - branch, - compare, - reverse_compare, - send_from_committer_email, - disable_diffs + author_id: author_id, + ref: ref, + action: action, + compare: compare, + reverse_compare: reverse_compare, + send_from_committer_email: send_from_committer_email, + disable_diffs: disable_diffs ).deliver end ensure diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index a85db10e019..c4a0fefb7ab 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -285,6 +285,9 @@ production: &base rack_attack: git_basic_auth: + # Rack Attack IP banning enabled + # enabled: true + # # Whitelist requests from 127.0.0.1 for web proxies (NGINX/Apache) with incorrect headers # ip_whitelist: ["127.0.0.1"] # diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 70af7a829c4..15c1ae9466f 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -183,6 +183,7 @@ Settings['extra'] ||= Settingslogic.new({}) # Settings['rack_attack'] ||= Settingslogic.new({}) Settings.rack_attack['git_basic_auth'] ||= Settingslogic.new({}) +Settings.rack_attack.git_basic_auth['enabled'] = true if Settings.rack_attack.git_basic_auth['enabled'].nil? Settings.rack_attack.git_basic_auth['ip_whitelist'] ||= %w{127.0.0.1} Settings.rack_attack.git_basic_auth['maxretry'] ||= 10 Settings.rack_attack.git_basic_auth['findtime'] ||= 1.minute diff --git a/db/migrate/20150324155957_set_incorrect_assignee_id_to_null.rb b/db/migrate/20150324155957_set_incorrect_assignee_id_to_null.rb new file mode 100644 index 00000000000..42dc8173e46 --- /dev/null +++ b/db/migrate/20150324155957_set_incorrect_assignee_id_to_null.rb @@ -0,0 +1,6 @@ +class SetIncorrectAssigneeIdToNull < ActiveRecord::Migration + def up + execute "UPDATE issues SET assignee_id = NULL WHERE assignee_id = -1" + execute "UPDATE merge_requests SET assignee_id = NULL WHERE assignee_id = -1" + end +end diff --git a/db/schema.rb b/db/schema.rb index e1a5b70532a..4a445ae5832 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150320234437) do +ActiveRecord::Schema.define(version: 20150324155957) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/docker/Dockerfile b/docker/Dockerfile index 4eb280f9554..f34cbc38a54 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -11,7 +11,7 @@ RUN apt-get update -q \ # If the Omnibus package version below is outdated please contribute a merge request to update it. # If you run GitLab Enterprise Edition point it to a location where you have downloaded it. RUN TMP_FILE=$(mktemp); \ - wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.8.3-omnibus-1_amd64.deb \ + wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.9.0-omnibus.2-1_amd64.deb \ && dpkg -i $TMP_FILE \ && rm -f $TMP_FILE diff --git a/features/admin/settings.feature b/features/admin/settings.feature index 8fdf0575c2c..52e47307b23 100644 --- a/features/admin/settings.feature +++ b/features/admin/settings.feature @@ -7,3 +7,10 @@ Feature: Admin Settings Scenario: Change application settings When I modify settings and save form Then I should see application settings saved + + Scenario: Change Slack Service Template settings + When I click on "Service Templates" + And I click on "Slack" service + Then I check all events and submit form + And I should see service template settings saved + And I should see all checkboxes checked diff --git a/features/steps/admin/settings.rb b/features/steps/admin/settings.rb index c2d0d2a3fa3..87d4e969ff5 100644 --- a/features/steps/admin/settings.rb +++ b/features/steps/admin/settings.rb @@ -15,4 +15,33 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps current_application_settings.home_page_url.should == 'https://about.gitlab.com/' page.should have_content 'Application settings saved successfully' end + + step 'I click on "Service Templates"' do + click_link 'Service Templates' + end + + step 'I click on "Slack" service' do + click_link 'Slack' + end + + step 'I check all events and submit form' do + page.check('Active') + page.check('Push events') + page.check('Tag push events') + page.check('Comments') + page.check('Issues events') + page.check('Merge Request events') + fill_in 'Webhook', with: "http://localhost" + click_on 'Save' + end + + step 'I should see service template settings saved' do + page.should have_content 'Application settings saved successfully' + end + + step 'I should see all checkboxes checked' do + all('input[type=checkbox]').each do |checkbox| + checkbox.should be_checked + end + end end diff --git a/features/steps/user.rb b/features/steps/user.rb index d6f05ecb2c7..10cae692a88 100644 --- a/features/steps/user.rb +++ b/features/steps/user.rb @@ -7,4 +7,37 @@ class Spinach::Features::User < Spinach::FeatureSteps step 'I should see user "John Doe" page' do expect(title).to match(/^\s*John Doe/) end + + step '"John Doe" has contributions' do + user = User.find_by(name: 'John Doe') + project = contributed_project + + # Issue controbution + issue_params = { title: 'Bug in old browser' } + Issues::CreateService.new(project, user, issue_params).execute + + # Push code contribution + push_params = { + project: project, + action: Event::PUSHED, + author_id: user.id, + data: { commit_count: 3 } + } + + Event.create(push_params) + end + + step 'I should see contributed projects' do + within '.contributed-projects' do + page.should have_content(@contributed_project.name) + end + end + + step 'I should see contributions calendar' do + page.should have_css('.cal-heatmap-container') + end + + def contributed_project + @contributed_project ||= create(:project, :public) + end end diff --git a/features/user.feature b/features/user.feature index a2167935fd2..69618e929c4 100644 --- a/features/user.feature +++ b/features/user.feature @@ -67,3 +67,12 @@ Feature: User And I should see project "Enterprise" And I should not see project "Internal" And I should not see project "Community" + + @javascript + Scenario: "John Doe" contribution profile + Given I sign in as a user + And "John Doe" has contributions + When I visit user "John Doe" page + Then I should see user "John Doe" page + And I should see contributed projects + And I should see contributions calendar diff --git a/lib/api/branches.rb b/lib/api/branches.rb index b52d786e020..edfdf842f85 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -1,4 +1,5 @@ require 'mime/types' +require 'uri' module API # Projects API @@ -103,7 +104,7 @@ module API delete ":id/repository/branches/:branch" do authorize_push_project result = DeleteBranchService.new(user_project, current_user). - execute(params[:branch]) + execute(URI.unescape(params[:branch])) if result[:status] == :success { diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index ee877e099b1..ffe4565ef1e 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -1,3 +1,4 @@ +require_relative 'rack_attack_helpers' require_relative 'shell_env' module Grack @@ -85,25 +86,41 @@ module Grack user = oauth_access_token_check(login, password) end - return user if user.present? - - # At this point, we know the credentials were wrong. We let Rack::Attack - # know there was a failed authentication attempt from this IP. This - # information is stored in the Rails cache (Redis) and will be used by - # the Rack::Attack middleware to decide whether to block requests from - # this IP. + # If the user authenticated successfully, we reset the auth failure count + # from Rack::Attack for that IP. A client may attempt to authenticate + # with a username and blank password first, and only after it receives + # a 401 error does it present a password. Resetting the count prevents + # false positives from occurring. + # + # Otherwise, we let Rack::Attack know there was a failed authentication + # attempt from this IP. This information is stored in the Rails cache + # (Redis) and will be used by the Rack::Attack middleware to decide + # whether to block requests from this IP. config = Gitlab.config.rack_attack.git_basic_auth - Rack::Attack::Allow2Ban.filter(@request.ip, config) do - # Unless the IP is whitelisted, return true so that Allow2Ban - # increments the counter (stored in Rails.cache) for the IP - if config.ip_whitelist.include?(@request.ip) - false + + if config.enabled + if user + # A successful login will reset the auth failure count from this IP + Rack::Attack::Allow2Ban.reset(@request.ip, config) else - true + banned = Rack::Attack::Allow2Ban.filter(@request.ip, config) do + # Unless the IP is whitelisted, return true so that Allow2Ban + # increments the counter (stored in Rails.cache) for the IP + if config.ip_whitelist.include?(@request.ip) + false + else + true + end + end + + if banned + Rails.logger.info "IP #{@request.ip} failed to login " \ + "as #{login} but has been temporarily banned from Git auth" + end end end - nil # No user was found + user end def authorized_request? diff --git a/lib/gitlab/backend/rack_attack_helpers.rb b/lib/gitlab/backend/rack_attack_helpers.rb new file mode 100644 index 00000000000..8538f3f6eca --- /dev/null +++ b/lib/gitlab/backend/rack_attack_helpers.rb @@ -0,0 +1,31 @@ +# rack-attack v4.2.0 doesn't yet support clearing of keys. +# Taken from https://github.com/kickstarter/rack-attack/issues/113 +class Rack::Attack::Allow2Ban + def self.reset(discriminator, options) + findtime = options[:findtime] or raise ArgumentError, "Must pass findtime option" + + cache.reset_count("#{key_prefix}:count:#{discriminator}", findtime) + cache.delete("#{key_prefix}:ban:#{discriminator}") + end +end + +class Rack::Attack::Cache + def reset_count(unprefixed_key, period) + epoch_time = Time.now.to_i + # Add 1 to expires_in to avoid timing error: http://git.io/i1PHXA + expires_in = period - (epoch_time % period) + 1 + key = "#{(epoch_time / period).to_i}:#{unprefixed_key}" + delete(key) + end + + def delete(unprefixed_key) + store.delete("#{prefix}:#{unprefixed_key}") + end +end + +class Rack::Attack::StoreProxy::RedisStoreProxy + def delete(key, options={}) + self.del(key) + rescue Redis::BaseError + end +end diff --git a/lib/gitlab/commits_calendar.rb b/lib/gitlab/commits_calendar.rb deleted file mode 100644 index 8963d346b6f..00000000000 --- a/lib/gitlab/commits_calendar.rb +++ /dev/null @@ -1,41 +0,0 @@ -module Gitlab - class CommitsCalendar - attr_reader :timestamps - - def initialize(projects, user) - @timestamps = {} - date_timestamps = [] - - projects.reject(&:forked?).each do |project| - date_timestamps << ProjectContributions.new(project, user).commits_log - end - - # Sumarrize commits from all projects per days - date_timestamps = date_timestamps.inject do |collection, date| - collection.merge(date) { |k, old_v, new_v| old_v + new_v } - end - - date_timestamps ||= [] - date_timestamps.each do |date, commits| - timestamp = Date.parse(date).to_time.to_i.to_s rescue nil - @timestamps[timestamp] = commits if timestamp - end - end - - def self.get_commits_for_date(projects, user, date) - user_commits = {} - projects.reject(&:forked?).each do |project| - user_commits[project] = ProjectContributions.new(project, user).user_commits_on_date(date) - end - user_commits - end - - def starting_year - (Time.now - 1.year).strftime("%Y") - end - - def starting_month - Date.today.strftime("%m").to_i - end - end -end diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb new file mode 100644 index 00000000000..3fd0823df06 --- /dev/null +++ b/lib/gitlab/contributions_calendar.rb @@ -0,0 +1,56 @@ +module Gitlab + class ContributionsCalendar + attr_reader :timestamps, :projects, :user + + def initialize(projects, user) + @projects = projects + @user = user + end + + def timestamps + return @timestamps if @timestamps.present? + + @timestamps = {} + date_from = 1.year.ago + date_to = Date.today + + events = Event.reorder(nil).contributions.where(author_id: user.id). + where("created_at > ?", date_from).where(project_id: projects). + group('date(created_at)'). + select('date(created_at), count(id) as total_amount'). + map(&:attributes) + + dates = (1.year.ago.to_date..(Date.today + 1.day)).to_a + + dates.each do |date| + date_id = date.to_time.to_i.to_s + @timestamps[date_id] = 0 + day_events = events.find { |day_events| day_events["date"] == date } + + if day_events + @timestamps[date_id] = day_events["total_amount"] + end + end + + @timestamps + end + + def events_by_date(date) + events = Event.contributions.where(author_id: user.id). + where("created_at > ? AND created_at < ?", date.beginning_of_day, date.end_of_day). + where(project_id: projects) + + events.select do |event| + event.push? || event.issue? || event.merge_request? + end + end + + def starting_year + (Time.now - 1.year).strftime("%Y") + end + + def starting_month + Date.today.strftime("%m").to_i + end + end +end diff --git a/lib/gitlab/ldap/person.rb b/lib/gitlab/ldap/person.rb index 3c426179375..b81f3e8e8f5 100644 --- a/lib/gitlab/ldap/person.rb +++ b/lib/gitlab/ldap/person.rb @@ -14,7 +14,6 @@ module Gitlab end def self.find_by_dn(dn, adapter) - dn = Net::LDAP::Filter.escape(dn) adapter.user('dn', dn) end diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index 11da4be4022..41bb8d08924 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -368,11 +368,12 @@ module Gitlab # ActiveSupport::SafeBuffer, hence the `String.new` String.new(text).gsub(Taskable::TASK_PATTERN_HTML) do checked = $LAST_MATCH_INFO[:checked].downcase == 'x' + p_tag = $LAST_MATCH_INFO[:p_tag] if checked - "#{li_tag}#{checked_box}" + "#{li_tag}#{p_tag}#{checked_box}" else - "#{li_tag}#{unchecked_box}" + "#{li_tag}#{p_tag}#{unchecked_box}" end end end diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb index 8b85f3da83f..0dab7bcfa4d 100644 --- a/lib/gitlab/project_search_results.rb +++ b/lib/gitlab/project_search_results.rb @@ -67,7 +67,7 @@ module Gitlab end def notes - Note.where(project_id: limit_project_ids).search(query).order('updated_at DESC') + Note.where(project_id: limit_project_ids).user.search(query).order('updated_at DESC') end def limit_project_ids diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 7962bcdde71..d47a37914df 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -25,34 +25,21 @@ describe UsersController do end describe 'GET #calendar_activities' do - include RepoHelpers - let(:project) { create(:project) } - let(:calendar_user) { create(:user, email: sample_commit.author_email) } - let(:commit1) { '0ed8c6c6752e8c6ea63e7b92a517bf5ac1209c80' } - let(:commit2) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' } + let!(:project) { create(:project) } + let!(:user) { create(:user) } before do allow_any_instance_of(User).to receive(:contributed_projects_ids).and_return([project.id]) project.team << [user, :developer] end - it 'assigns @commit_count' do - get :calendar_activities, username: calendar_user.username, date: '2014-07-31' - expect(assigns(:commit_count)).to eq(2) - end - it 'assigns @calendar_date' do - get :calendar_activities, username: calendar_user.username, date: '2014-07-31' + get :calendar_activities, username: user.username, date: '2014-07-31' expect(assigns(:calendar_date)).to eq(Date.parse('2014-07-31')) end - it 'assigns @calendar_activities' do - get :calendar_activities, username: calendar_user.username, date: '2014-07-31' - expect(assigns(:calendar_activities).values.flatten.map(&:id)).to eq([commit1, commit2]) - end - it 'renders calendar_activities' do - get :calendar_activities, username: calendar_user.username + get :calendar_activities, username: user.username expect(response).to render_template('calendar_activities') end end diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index 3a884d39bf1..c631acc591d 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -847,6 +847,17 @@ EOT ) end + it 'should render checkboxes for nested tasks' do + rendered_text = markdown(@source_text_asterisk, parse_tasks: true) + + expect(rendered_text).to match( + /<input.*checkbox.*valid unchecked nested task/ + ) + expect(rendered_text).to match( + /<input.*checkbox.*valid checked nested task/ + ) + end + it 'should not be confused by whitespace before bullets' do rendered_text_asterisk = markdown(@source_text_asterisk, parse_tasks: true) diff --git a/spec/lib/gitlab/backend/grack_auth_spec.rb b/spec/lib/gitlab/backend/grack_auth_spec.rb index 768312f0028..d0aad54f677 100644 --- a/spec/lib/gitlab/backend/grack_auth_spec.rb +++ b/spec/lib/gitlab/backend/grack_auth_spec.rb @@ -6,7 +6,7 @@ describe Grack::Auth do let(:app) { lambda { |env| [200, {}, "Success!"] } } let!(:auth) { Grack::Auth.new(app) } - let(:env) { + let(:env) { { "rack.input" => "", "REQUEST_METHOD" => "GET", @@ -85,6 +85,17 @@ describe Grack::Auth do it "responds with status 401" do expect(status).to eq(401) end + + context "when the user is IP banned" do + before do + expect(Rack::Attack::Allow2Ban).to receive(:filter).and_return(true) + allow_any_instance_of(Rack::Request).to receive(:ip).and_return('1.2.3.4') + end + + it "responds with status 401" do + expect(status).to eq(401) + end + end end context "when authentication succeeds" do @@ -109,10 +120,49 @@ describe Grack::Auth do end context "when the user isn't blocked" do + before do + expect(Rack::Attack::Allow2Ban).to receive(:reset) + end + it "responds with status 200" do expect(status).to eq(200) end end + + context "when blank password attempts follow a valid login" do + let(:options) { Gitlab.config.rack_attack.git_basic_auth } + let(:maxretry) { options[:maxretry] - 1 } + let(:ip) { '1.2.3.4' } + + before do + allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip) + Rack::Attack::Allow2Ban.reset(ip, options) + end + + after do + Rack::Attack::Allow2Ban.reset(ip, options) + end + + def attempt_login(include_password) + password = include_password ? user.password : "" + env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, password) + Grack::Auth.new(app) + auth.call(env).first + end + + it "repeated attempts followed by successful attempt" do + for n in 0..maxretry do + expect(attempt_login(false)).to eq(401) + end + + expect(attempt_login(true)).to eq(200) + expect(Rack::Attack::Allow2Ban.send(:banned?, ip)).to eq(nil) + + for n in 0..maxretry do + expect(attempt_login(false)).to eq(401) + end + end + end end context "when the user doesn't have access to the project" do diff --git a/spec/lib/gitlab/backend/rack_attack_helpers_spec.rb b/spec/lib/gitlab/backend/rack_attack_helpers_spec.rb new file mode 100644 index 00000000000..2ac496fd669 --- /dev/null +++ b/spec/lib/gitlab/backend/rack_attack_helpers_spec.rb @@ -0,0 +1,35 @@ +require "spec_helper" + +describe 'RackAttackHelpers' do + describe 'reset' do + let(:discriminator) { 'test-key'} + let(:maxretry) { 5 } + let(:period) { 1.minute } + let(:options) { { findtime: period, bantime: 60, maxretry: maxretry } } + + def do_filter + for i in 1..maxretry - 1 do + status = Rack::Attack::Allow2Ban.filter(discriminator, options) { true } + expect(status).to eq(false) + end + end + + def do_reset + Rack::Attack::Allow2Ban.reset(discriminator, options) + end + + before do + do_reset + end + + after do + do_reset + end + + it 'user is not banned after n - 1 retries' do + do_filter + do_reset + do_filter + end + end +end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index e3a3b542358..ba42f9e5c70 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -640,6 +640,100 @@ describe Notify do end end + describe 'email on push for a created branch' do + let(:example_site_path) { root_path } + let(:user) { create(:user) } + let(:tree_path) { namespace_project_tree_path(project.namespace, project, "master") } + + subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :create) } + + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(user.name) + expect(sender.address).to eq(gitlab_sender) + end + + it 'is sent to recipient' do + is_expected.to deliver_to 'devs@company.name' + end + + it 'has the correct subject' do + is_expected.to have_subject /Pushed new branch master/ + end + + it 'contains a link to the branch' do + is_expected.to have_body_text /#{tree_path}/ + end + end + + describe 'email on push for a created tag' do + let(:example_site_path) { root_path } + let(:user) { create(:user) } + let(:tree_path) { namespace_project_tree_path(project.namespace, project, "v1.0") } + + subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/tags/v1.0', action: :create) } + + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(user.name) + expect(sender.address).to eq(gitlab_sender) + end + + it 'is sent to recipient' do + is_expected.to deliver_to 'devs@company.name' + end + + it 'has the correct subject' do + is_expected.to have_subject /Pushed new tag v1\.0/ + end + + it 'contains a link to the tag' do + is_expected.to have_body_text /#{tree_path}/ + end + end + + describe 'email on push for a deleted branch' do + let(:example_site_path) { root_path } + let(:user) { create(:user) } + + subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :delete) } + + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(user.name) + expect(sender.address).to eq(gitlab_sender) + end + + it 'is sent to recipient' do + is_expected.to deliver_to 'devs@company.name' + end + + it 'has the correct subject' do + is_expected.to have_subject /Deleted branch master/ + end + end + + describe 'email on push for a deleted tag' do + let(:example_site_path) { root_path } + let(:user) { create(:user) } + + subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/tags/v1.0', action: :delete) } + + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(user.name) + expect(sender.address).to eq(gitlab_sender) + end + + it 'is sent to recipient' do + is_expected.to deliver_to 'devs@company.name' + end + + it 'has the correct subject' do + is_expected.to have_subject /Deleted tag v1\.0/ + end + end + describe 'email on push with multiple commits' do let(:example_site_path) { root_path } let(:user) { create(:user) } @@ -648,7 +742,7 @@ describe Notify do let(:diff_path) { namespace_project_compare_path(project.namespace, project, from: Commit.new(compare.base), to: Commit.new(compare.head)) } let(:send_from_committer_email) { false } - subject { Notify.repository_push_email(project.id, 'devs@company.name', user.id, 'master', compare, false, send_from_committer_email) } + subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, reverse_compare: false, send_from_committer_email: send_from_committer_email) } it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -736,7 +830,7 @@ describe Notify do let(:commits) { Commit.decorate(compare.commits) } let(:diff_path) { namespace_project_commit_path(project.namespace, project, commits.first) } - subject { Notify.repository_push_email(project.id, 'devs@company.name', user.id, 'master', compare) } + subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare) } it 'is sent as the author' do sender = subject.header[:from].addrs[0] diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 0e3e0b167d7..f41e5a97ca3 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -13,47 +13,16 @@ describe Repository do it { is_expected.not_to include('fix') } end - describe :last_commit_for_path do - subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id } - - it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') } - end - - context :timestamps_by_user_log do - before do - Date.stub(:today).and_return(Date.new(2015, 03, 01)) - end - - describe 'single e-mail for user' do - let(:user) { create(:user, email: sample_commit.author_email) } - - subject { repository.timestamps_by_user_log(user) } + describe :tag_names_contains do + subject { repository.tag_names_contains(sample_commit.id) } - it { is_expected.to eq(['2014-08-06', '2014-07-31', '2014-07-31']) } - end - - describe 'multiple emails for user' do - let(:email_alias) { create(:email, email: another_sample_commit.author_email) } - let(:user) { create(:user, email: sample_commit.author_email, emails: [email_alias]) } - - subject { repository.timestamps_by_user_log(user) } - - it { is_expected.to eq(['2015-01-10', '2014-08-06', '2014-07-31', '2014-07-31']) } - end + it { is_expected.to include('v1.1.0') } + it { is_expected.not_to include('v1.0.0') } end - context :commits_by_user_on_date_log do - - describe 'single e-mail for user' do - let(:user) { create(:user, email: sample_commit.author_email) } - let(:commit1) { '0ed8c6c6752e8c6ea63e7b92a517bf5ac1209c80' } - let(:commit2) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' } - - subject { repository.commits_by_user_on_date_log(user,Date.new(2014, 07, 31)) } + describe :last_commit_for_path do + subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id } - it 'contains the exepected commits' do - expect(subject.flatten.map(&:id)).to eq([commit1, commit2]) - end - end + it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') } end end |