Commit 057c8c34 authored by Vinnie Okada's avatar Vinnie Okada

Merge branch 'master' into markdown-tags

parents 637ca0b3 b9372c99
...@@ -2,12 +2,14 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -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) v 7.10.0 (unreleased)
- Allow HTML tags in Markdown input - 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 "Import projects from" button to show the correct instructions (Stan Hu)
- Fix dots in Wiki slugs causing errors (Stan Hu) - Fix dots in Wiki slugs causing errors (Stan Hu)
- Fix OAuth2 issue importing a new project from GitHub and GitLab (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) - 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) - 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) - 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) - 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) - extend the commit calendar to show the actual commits made on a date (Hannes Rosenögger)
- Fix a link in the patch update guide - Fix a link in the patch update guide
...@@ -17,6 +19,7 @@ v 7.10.0 (unreleased) ...@@ -17,6 +19,7 @@ v 7.10.0 (unreleased)
- Add changelog, license and contribution guide links to project sidebar. - Add changelog, license and contribution guide links to project sidebar.
- Improve diff UI - Improve diff UI
- Fix alignment of navbar toggle button (Cody Mize) - Fix alignment of navbar toggle button (Cody Mize)
- Fix checkbox rendering for nested task lists
- Identical look of selectboxes in UI - Identical look of selectboxes in UI
- Move "Import existing repository by URL" option to button. - Move "Import existing repository by URL" option to button.
- Improve error message when save profile has error. - Improve error message when save profile has error.
...@@ -28,8 +31,17 @@ v 7.10.0 (unreleased) ...@@ -28,8 +31,17 @@ v 7.10.0 (unreleased)
- Restrict permissions on backup files - Restrict permissions on backup files
- Improve oauth accounts UI in profile page - Improve oauth accounts UI in profile page
- Add ability to unlink connected accounts - 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 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) - Add HipChat integration documentation (Stan Hu)
- Update documentation for object_kind field in Webhook push and tag push Webhooks (Stan Hu) - Update documentation for object_kind field in Webhook push and tag push Webhooks (Stan Hu)
- Fix broken email images (Hannes Rosenögger) - Fix broken email images (Hannes Rosenögger)
...@@ -146,7 +158,6 @@ v 7.8.0 ...@@ -146,7 +158,6 @@ v 7.8.0
- Add API endpoint to fetch all changes on a MergeRequest (Jeroen van Baarsen) - 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 - 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 - 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 - Fix overflow at sidebar when have several items
- Add notes for label changes in issue and merge requests - Add notes for label changes in issue and merge requests
- Show tags in commit view (Hannes Rosenögger) - Show tags in commit view (Hannes Rosenögger)
...@@ -168,7 +179,7 @@ v 7.8.0 ...@@ -168,7 +179,7 @@ v 7.8.0
- Add a commit calendar to the user profile (Hannes Rosenögger) - Add a commit calendar to the user profile (Hannes Rosenögger)
- Submit comment on command-enter - 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`. - 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) - Fix long broadcast message cut-off on left sidebar (Visay Keo)
- Add Project Avatars (Steven Thonus and Hannes Rosenögger) - 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. - Password reset token validity increased from 2 hours to 2 days since it is also send on account creation.
......
...@@ -188,7 +188,7 @@ GEM ...@@ -188,7 +188,7 @@ GEM
dotenv (>= 0.7) dotenv (>= 0.7)
thor (>= 0.13.6) thor (>= 0.13.6)
formatador (0.2.4) formatador (0.2.4)
gemnasium-gitlab-service (0.2.5) gemnasium-gitlab-service (0.2.4)
rugged (~> 0.21) rugged (~> 0.21)
gemojione (2.0.0) gemojione (2.0.0)
json json
...@@ -516,7 +516,7 @@ GEM ...@@ -516,7 +516,7 @@ GEM
rubyntlm (0.5.0) rubyntlm (0.5.0)
rubypants (0.2.0) rubypants (0.2.0)
rugged (0.21.4) rugged (0.21.4)
rugments (1.0.0.beta5) rugments (1.0.0.beta6)
safe_yaml (0.9.7) safe_yaml (0.9.7)
sanitize (2.1.0) sanitize (2.1.0)
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
......
...@@ -7,7 +7,7 @@ class @calendar ...@@ -7,7 +7,7 @@ class @calendar
constructor: (timestamps, starting_year, starting_month, calendar_activities_path) -> constructor: (timestamps, starting_year, starting_month, calendar_activities_path) ->
cal = new CalHeatMap() cal = new CalHeatMap()
cal.init cal.init
itemName: ["commit"] itemName: ["contribution"]
data: timestamps data: timestamps
start: new Date(starting_year, starting_month) start: new Date(starting_year, starting_month)
domainLabelFormat: "%b" domainLabelFormat: "%b"
...@@ -27,7 +27,6 @@ class @calendar ...@@ -27,7 +27,6 @@ class @calendar
legendCellPadding: 3 legendCellPadding: 3
onClick: (date, count) -> onClick: (date, count) ->
formated_date = date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate() formated_date = date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate()
$(".calendar_commit_activity").fadeOut 400
$.ajax $.ajax
url: calendar_activities_path url: calendar_activities_path
data: data:
...@@ -36,6 +35,4 @@ class @calendar ...@@ -36,6 +35,4 @@ class @calendar
dataType: "html" dataType: "html"
success: (data) -> success: (data) ->
$(".user-calendar-activities").html data $(".user-calendar-activities").html data
$(".calendar_commit_activity").find(".js-toggle-content").hide()
$(".calendar_commit_activity").fadeIn 400
...@@ -97,6 +97,7 @@ class Dispatcher ...@@ -97,6 +97,7 @@ class Dispatcher
new ProjectFork() new ProjectFork()
when 'users:show' when 'users:show'
new User() new User()
new Activities()
switch path.first() switch path.first()
when 'admin' when 'admin'
......
...@@ -113,8 +113,14 @@ class @MergeRequest ...@@ -113,8 +113,14 @@ class @MergeRequest
allowed_states = ["failed", "canceled", "running", "pending", "success"] allowed_states = ["failed", "canceled", "running", "pending", "success"]
if state in allowed_states if state in allowed_states
$('.ci_widget.ci-' + state).show() $('.ci_widget.ci-' + state).show()
switch state
when "failed", "canceled"
@setMergeButtonClass('btn-danger')
when "running", "pending"
@setMergeButtonClass('btn-warning')
else else
$('.ci_widget.ci-error').show() $('.ci_widget.ci-error').show()
@setMergeButtonClass('btn-danger')
showCiCoverage: (coverage) -> showCiCoverage: (coverage) ->
cov_html = $('<span>') cov_html = $('<span>')
...@@ -144,6 +150,9 @@ class @MergeRequest ...@@ -144,6 +150,9 @@ class @MergeRequest
this.$('.merge-in-progress').hide() this.$('.merge-in-progress').hide()
this.$('.automerge_widget.already_cannot_be_merged').show() this.$('.automerge_widget.already_cannot_be_merged').show()
setMergeButtonClass: (css_class) ->
$('.accept_merge_request').removeClass("btn-create").addClass(css_class)
mergeInProgress: -> mergeInProgress: ->
$.ajax $.ajax
type: 'GET' type: 'GET'
......
...@@ -25,7 +25,7 @@ class @ProjectUsersSelect ...@@ -25,7 +25,7 @@ class @ProjectUsersSelect
initSelection: (element, callback) -> initSelection: (element, callback) ->
id = $(element).val() id = $(element).val()
if id isnt "" if id != "" && id != "-1"
Api.user(id, callback) Api.user(id, callback)
...@@ -44,9 +44,6 @@ class @ProjectUsersSelect ...@@ -44,9 +44,6 @@ class @ProjectUsersSelect
else else
avatar = gon.default_avatar_url 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'> "<div class='user-result'>
......
.user-calendar-activities { .user-calendar-activities {
.calendar_commit_activity {
padding: 5px 0 0;
}
.calendar_onclick_hr { .calendar_onclick_hr {
padding: 0; padding: 0;
margin: 10px 0; margin: 10px 0;
} }
.calendar_commit_date {
color: #999;
}
.calendar_activity_summary {
font-size: 14px;
}
.str-truncated { .str-truncated {
max-width: 70%; max-width: 70%;
} }
...@@ -31,14 +18,6 @@ ...@@ -31,14 +18,6 @@
background-color: #ddd; background-color: #ddd;
} }
} }
.commit-row-message {
color: #333;
&:hover {
color: #444;
text-decoration: underline;
}
}
} }
/** /**
* This overwrites the default values of the cal-heatmap gem * This overwrites the default values of the cal-heatmap gem
......
...@@ -15,6 +15,11 @@ ...@@ -15,6 +15,11 @@
word-break: break-all; word-break: break-all;
margin-right: 200px; margin-right: 200px;
display: block; display: block;
.file-mode {
margin-left: 10px;
color: #777;
}
} }
.diff-btn-group { .diff-btn-group {
...@@ -34,11 +39,6 @@ ...@@ -34,11 +39,6 @@
font-family: $monospace_font; font-family: $monospace_font;
font-size: smaller; font-size: smaller;
} }
.file-mode {
font-family: $monospace_font;
margin-left: 10px;
}
} }
.diff-content { .diff-content {
overflow: auto; overflow: auto;
......
...@@ -137,30 +137,15 @@ ...@@ -137,30 +137,15 @@
background-color: #F1FAF1; background-color: #F1FAF1;
} }
&.ci-pending { &.ci-pending,
color: #548;
border-color: #548;
background-color: #F4F1FA;
}
&.ci-running { &.ci-running {
color: $gl-warning; color: $gl-warning;
border-color: $gl-warning; border-color: $gl-warning;
background-color: #FAF5F1; background-color: #FAF5F1;
} }
&.ci-failed { &.ci-failed,
color: $gl-danger; &.ci-canceled,
border-color: $gl-danger;
background-color: #FAF1F1;
}
&.ci-canceled {
color: $gl-warning;
border-color: $gl-danger;
background-color: #FAF5F1;
}
&.ci-error { &.ci-error {
color: $gl-danger; color: $gl-danger;
border-color: $gl-danger; border-color: $gl-danger;
......
/** /**
* Modern GitLab UI theme * Blue GitLab UI theme
*/ */
.ui_blue { .ui_blue {
@include dark-theme(#BECDE9, #2980b9, #1970a9, #096099); @include dark-theme(#BECDE9, #2980b9, #1970a9, #096099);
......
...@@ -46,7 +46,9 @@ class Admin::ServicesController < Admin::ApplicationController ...@@ -46,7 +46,9 @@ class Admin::ServicesController < Admin::ApplicationController
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password, :user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
:build_key, :server, :teamcity_url, :build_type, :build_key, :server, :teamcity_url, :build_type,
:description, :issues_url, :new_issue_url, :restrict_to_branch, :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
end end
...@@ -4,10 +4,7 @@ class UsersController < ApplicationController ...@@ -4,10 +4,7 @@ class UsersController < ApplicationController
layout :determine_layout layout :determine_layout
def show def show
@contributed_projects = Project. @contributed_projects = contributed_projects.joined(@user).
where(id: authorized_projects_ids & @user.contributed_projects_ids).
in_group_namespace.
includes(:namespace).
reject(&:forked?) reject(&:forked?)
@projects = @user.personal_projects. @projects = @user.personal_projects.
...@@ -16,24 +13,26 @@ class UsersController < ApplicationController ...@@ -16,24 +13,26 @@ class UsersController < ApplicationController
# Collect only groups common for both users # Collect only groups common for both users
@groups = @user.groups & GroupsFinder.new.execute(current_user) @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 = @user.name
@title_url = user_path(@user) @title_url = user_path(@user)
respond_to do |format| respond_to do |format|
format.html 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
end end
def calendar def calendar
projects = Project.where(id: authorized_projects_ids & @user.contributed_projects_ids) calendar = contributions_calendar
calendar = Gitlab::CommitsCalendar.new(projects, @user)
@timestamps = calendar.timestamps @timestamps = calendar.timestamps
@starting_year = calendar.starting_year @starting_year = calendar.starting_year
@starting_month = calendar.starting_month @starting_month = calendar.starting_month
...@@ -42,20 +41,13 @@ class UsersController < ApplicationController ...@@ -42,20 +41,13 @@ class UsersController < ApplicationController
end end
def calendar_activities 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 @calendar_date
if date @events = contributions_calendar.events_by_date(@calendar_date)
@calendar_activities = Gitlab::CommitsCalendar.get_commits_for_date(projects, @user, date)
else
@calendar_activities = {}
end 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 render 'calendar_activities', layout: false
end end
...@@ -82,4 +74,24 @@ class UsersController < ApplicationController ...@@ -82,4 +74,24 @@ class UsersController < ApplicationController
@authorized_projects_ids ||= @authorized_projects_ids ||=
ProjectsFinder.new.execute(current_user).pluck(:id) ProjectsFinder.new.execute(current_user).pluck(:id)
end 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 end
...@@ -58,22 +58,11 @@ module IssuesHelper ...@@ -58,22 +58,11 @@ module IssuesHelper
end end
def bulk_update_milestone_options 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', options_from_collection_for_select(project_active_milestones, 'id',
'title', params[:milestone_id]) 'title', params[:milestone_id])
end 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) def milestone_options(object)
options_from_collection_for_select(object.project.milestones.active, options_from_collection_for_select(object.project.milestones.active,
'id', 'title', object.milestone_id) 'id', 'title', object.milestone_id)
......
...@@ -146,6 +146,10 @@ module ProjectsHelper ...@@ -146,6 +146,10 @@ module ProjectsHelper
nav_tabs << feature if project.send :"#{feature}_enabled" nav_tabs << feature if project.send :"#{feature}_enabled"
end end
if project.issues_enabled || project.merge_requests_enabled
nav_tabs << [:milestones, :labels]
end
nav_tabs.flatten nav_tabs.flatten
end end
......
...@@ -23,9 +23,9 @@ module SearchHelper ...@@ -23,9 +23,9 @@ module SearchHelper
# Autocomplete results for various settings pages # Autocomplete results for various settings pages
def default_autocomplete def default_autocomplete
[ [
{ label: "My Profile settings", url: profile_path }, { label: "Profile settings", url: profile_path },
{ label: "My SSH Keys", url: profile_keys_path }, { label: "SSH Keys", url: profile_keys_path },
{ label: "My Dashboard", url: root_path }, { label: "Dashboard", url: root_path },
{ label: "Admin Section", url: admin_root_path }, { label: "Admin Section", url: admin_root_path },
] ]
end end
......
...@@ -16,18 +16,46 @@ module Emails ...@@ -16,18 +16,46 @@ module Emails
subject: subject("Project was moved")) subject: subject("Project was moved"))
end 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) @project = Project.find(project_id)
@author = User.find(author_id) @author = User.find(author_id)
@reverse_compare = reverse_compare @reverse_compare = reverse_compare
@compare = compare @compare = compare
@ref_name = Gitlab::Git.ref_name(ref)
@ref_type = Gitlab::Git.tag_ref?(ref) ? "tag" : "branch"
@action = action
@disable_diffs = disable_diffs
if @compare
@commits = Commit.decorate(compare.commits) @commits = Commit.decorate(compare.commits)
@diffs = compare.diffs @diffs = compare.diffs
@branch = Gitlab::Git.ref_name(branch) end
@disable_diffs = disable_diffs
@subject = "[#{@project.path_with_namespace}][#{@branch}] " @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 if @commits.length > 1
@target_url = namespace_project_compare_url(@project.namespace, @target_url = namespace_project_compare_url(@project.namespace,
@project, @project,
...@@ -42,6 +70,16 @@ module Emails ...@@ -42,6 +70,16 @@ module Emails
@subject << "Deleted 1 commit: " if @reverse_compare @subject << "Deleted 1 commit: " if @reverse_compare
@subject << @commits.first.title @subject << @commits.first.title
end end
else
unless action == :delete
@target_url = namespace_project_tree_url(@project.namespace,
@project, @ref_name)
end
subject_action = @action_name.dup
subject_action[0] = subject_action[0].capitalize
@subject << "#{subject_action} #{@ref_type} #{@ref_name}"
end
@disable_footer = true @disable_footer = true
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
# Used by MergeRequest and Issue # Used by MergeRequest and Issue
module Taskable module Taskable
TASK_PATTERN_MD = /^(?<bullet> *[*-] *)\[(?<checked>[ xX])\]/.freeze 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 # 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 # description by finding the nth task item and changing its checkbox
......
...@@ -55,6 +55,12 @@ class Event < ActiveRecord::Base ...@@ -55,6 +55,12 @@ class Event < ActiveRecord::Base
order('id DESC').limit(100). order('id DESC').limit(100).
update_all(updated_at: Time.now) update_all(updated_at: Time.now)
end end
def contributions
where("action = ? OR (target_type in (?) AND action in (?))",
Event::PUSHED, ["MergeRequest", "Issue"],
[Event::CREATED, Event::CLOSED, Event::MERGED])
end
end end
def proper? def proper?
......
...@@ -105,6 +105,15 @@ class MergeRequest < ActiveRecord::Base ...@@ -105,6 +105,15 @@ class MergeRequest < ActiveRecord::Base
state :unchecked state :unchecked
state :can_be_merged state :can_be_merged
state :cannot_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 end
validates :source_project, presence: true, unless: :allow_broken validates :source_project, presence: true, unless: :allow_broken
......
...@@ -48,6 +48,7 @@ class Note < ActiveRecord::Base ...@@ -48,6 +48,7 @@ class Note < ActiveRecord::Base
scope :inline, ->{ where("line_code IS NOT NULL") } scope :inline, ->{ where("line_code IS NOT NULL") }
scope :not_inline, ->{ where(line_code: [nil, '']) } scope :not_inline, ->{ where(line_code: [nil, '']) }
scope :system, ->{ where(system: true) } scope :system, ->{ where(system: true) }
scope :user, ->{ where(system: false) }
scope :common, ->{ where(noteable_type: ["", nil]) } scope :common, ->{ where(noteable_type: ["", nil]) }
scope :fresh, ->{ order(created_at: :asc, id: :asc) } scope :fresh, ->{ order(created_at: :asc, id: :asc) }
scope :inc_author_project, ->{ includes(:project, :author) } scope :inc_author_project, ->{ includes(:project, :author) }
......
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
...@@ -36,13 +36,19 @@ class EmailsOnPushService < Service ...@@ -36,13 +36,19 @@ class EmailsOnPushService < Service
end end
def supported_events def supported_events
%w(push) %w(push tag_push)
end end
def execute(push_data) def execute(push_data)
return unless supported_events.include?(push_data[:object_kind]) 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 end
def send_from_committer_email? def send_from_committer_email?
......
...@@ -149,41 +149,6 @@ class Repository ...@@ -149,41 +149,6 @@ class Repository
end end
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 def lookup_cache
@lookup_cache ||= {} @lookup_cache ||= {}
end end
......
...@@ -110,6 +110,7 @@ class User < ActiveRecord::Base ...@@ -110,6 +110,7 @@ class User < ActiveRecord::Base
has_many :notes, dependent: :destroy, foreign_key: :author_id has_many :notes, dependent: :destroy, foreign_key: :author_id
has_many :merge_requests, 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 :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 :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_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue"
has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest" has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
...@@ -603,13 +604,10 @@ class User < ActiveRecord::Base ...@@ -603,13 +604,10 @@ class User < ActiveRecord::Base
end end
def contributed_projects_ids def contributed_projects_ids
Event.where(author_id: self). Event.contributions.where(author_id: self).
where("created_at > ?", Time.now - 1.year). 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). reorder(project_id: :desc).
select(:project_id). select(:project_id).
uniq uniq.map(&:project_id)
.map(&:project_id)
end end
end end
...@@ -14,6 +14,9 @@ module Issues ...@@ -14,6 +14,9 @@ module Issues
issue.update_nth_task(params[:task_num].to_i, false) issue.update_nth_task(params[:task_num].to_i, false)
end end
params[:assignee_id] = "" if params[:assignee_id] == "-1"
params[:milestone_id] = "" if params[:milestone_id] == "-1"
old_labels = issue.labels.to_a old_labels = issue.labels.to_a
if params.present? && issue.update_attributes(params.except(:state_event, if params.present? && issue.update_attributes(params.except(:state_event,
......
...@@ -53,7 +53,7 @@ module MergeRequests ...@@ -53,7 +53,7 @@ module MergeRequests
if merge_request.source_branch == @branch_name || force_push? if merge_request.source_branch == @branch_name || force_push?
merge_request.reload_code merge_request.reload_code
update_merge_request(merge_request) merge_request.mark_as_unchecked
else else
mr_commit_ids = merge_request.commits.map(&:id) mr_commit_ids = merge_request.commits.map(&:id)
push_commit_ids = @commits.map(&:id) push_commit_ids = @commits.map(&:id)
...@@ -61,20 +61,14 @@ module MergeRequests ...@@ -61,20 +61,14 @@ module MergeRequests
if matches.any? if matches.any?
merge_request.reload_code merge_request.reload_code
update_merge_request(merge_request) merge_request.mark_as_unchecked
else else
update_merge_request(merge_request) merge_request.mark_as_unchecked
end end
end end
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 # Add comment about pushing new commits to merge requests
def comment_mr_with_commits def comment_mr_with_commits
merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a
......
...@@ -23,6 +23,9 @@ module MergeRequests ...@@ -23,6 +23,9 @@ module MergeRequests
merge_request.update_nth_task(params[:task_num].to_i, false) merge_request.update_nth_task(params[:task_num].to_i, false)
end end
params[:assignee_id] = "" if params[:assignee_id] == "-1"
params[:milestone_id] = "" if params[:milestone_id] == "-1"
old_labels = merge_request.labels.to_a old_labels = merge_request.labels.to_a
if params.present? && merge_request.update_attributes( if params.present? && merge_request.update_attributes(
......
...@@ -21,13 +21,11 @@ ...@@ -21,13 +21,11 @@
.form-group.js-toggle-colors-container.hide .form-group.js-toggle-colors-container.hide
= f.label :color, "Background Color", class: 'control-label' = f.label :color, "Background Color", class: 'control-label'
.col-sm-10 .col-sm-10
= f.text_field :color, placeholder: "#AA33EE", class: "form-control" = f.color_field :color, value: "#AA33EE", class: "form-control"
.light 6 character hex values starting with a # sign.
.form-group.js-toggle-colors-container.hide .form-group.js-toggle-colors-container.hide
= f.label :font, "Font Color", class: 'control-label' = f.label :font, "Font Color", class: 'control-label'
.col-sm-10 .col-sm-10
= f.text_field :font, placeholder: "#224466", class: "form-control" = f.color_field :font, value: "#224466", class: "form-control"
.light 6 character hex values starting with a # sign.
.form-group .form-group
= f.label :starts_at, class: 'control-label' = f.label :starts_at, class: 'control-label'
.col-sm-10.datetime-controls .col-sm-10.datetime-controls
......
...@@ -14,6 +14,11 @@ ...@@ -14,6 +14,11 @@
= preserve do = preserve do
= markdown @service.help = 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 - if @service.supported_events.length > 1
.form-group .form-group
= f.label :url, "Trigger", class: 'control-label' = f.label :url, "Trigger", class: 'control-label'
...@@ -34,6 +39,14 @@ ...@@ -34,6 +39,14 @@
%strong Tag push events %strong Tag push events
%p.light %p.light
This url will be triggered when a new tag is pushed to the repository 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") - if @service.supported_events.include?("issue")
%div %div
= f.check_box :issues_events, class: 'pull-left' = f.check_box :issues_events, class: 'pull-left'
......
...@@ -6,4 +6,4 @@ ...@@ -6,4 +6,4 @@
<p>You can confirm your account through the link below:</p> <p>You can confirm your account through the link below:</p>
<% end %> <% 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>
...@@ -2,7 +2,7 @@ ...@@ -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>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>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> <p>Your password won't change until you access the link above and create a new one.</p>
...@@ -4,4 +4,4 @@ ...@@ -4,4 +4,4 @@
<p>Click the link below to unlock your account:</p> <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>
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
%div %div
= f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm new password", required: true = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm new password", required: true
.clearfix .clearfix
= f.submit "Change my password", class: "btn btn-primary" = f.submit "Change your password", class: "btn btn-primary"
.clearfix.prepend-top-20 .clearfix.prepend-top-20
%p %p
......
...@@ -21,8 +21,8 @@ ...@@ -21,8 +21,8 @@
<div><%= f.submit "Update", class: "input_button" %></div> <div><%= f.submit "Update", class: "input_button" %></div>
<% end %> <% 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 %> <%= link_to "Back", :back %>
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
%code .panel .well-list %code .panel .well-list
.panel.panel-default .panel.panel-default
.panel-heading My list .panel-heading Your list
%ul.well-list %ul.well-list
%li %li
One item One item
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
= link_to explore_root_path, title: "Explore", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do = link_to explore_root_path, title: "Explore", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do
%i.fa.fa-globe %i.fa.fa-globe
%li %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 %i.fa.fa-clipboard
- if current_user.is_admin? - if current_user.is_admin?
%li %li
......
...@@ -44,6 +44,7 @@ ...@@ -44,6 +44,7 @@
%span %span
Graphs Graphs
- if project_nav_tab? :milestones
= nav_link(controller: :milestones) do = nav_link(controller: :milestones) do
= link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
%i.fa.fa-clock-o %i.fa.fa-clock-o
...@@ -67,6 +68,7 @@ ...@@ -67,6 +68,7 @@
Merge Requests Merge Requests
%span.count.merge_counter= @project.merge_requests.opened.count %span.count.merge_counter= @project.merge_requests.opened.count
- if project_nav_tab? :labels
= nav_link(controller: :labels) do = nav_link(controller: :labels) do
= link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
%i.fa.fa-tags %i.fa.fa-tags
......
%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 - if @compare
- if @reverse_compare
%p %p
%strong WARNING: %strong WARNING:
The push did not contain any new commits, but force pushed to delete the commits and changes below. The push did not contain any new commits, but force pushed to delete the commits and changes below.
%h4 %h4
= @reverse_compare ? "Deleted commits:" : "Commits:" = @reverse_compare ? "Deleted commits:" : "Commits:"
%ul %ul
- @commits.each do |commit| - @commits.each do |commit|
%li %li
%strong #{link_to commit.short_id, namespace_project_commit_url(@project.namespace, @project, commit)} %strong #{link_to commit.short_id, namespace_project_commit_url(@project.namespace, @project, commit)}
...@@ -18,9 +19,9 @@ ...@@ -18,9 +19,9 @@
%pre.commit-message %pre.commit-message
= commit.safe_message = commit.safe_message
%h4 #{pluralize @diffs.count, "changed file"}: %h4 #{pluralize @diffs.count, "changed file"}:
%ul %ul
- @diffs.each_with_index do |diff, i| - @diffs.each_with_index do |diff, i|
%li.file-stats %li.file-stats
%a{href: "#{@target_url if @disable_diffs}#diff-#{i}" } %a{href: "#{@target_url if @disable_diffs}#diff-#{i}" }
...@@ -39,7 +40,7 @@ ...@@ -39,7 +40,7 @@
- else - else
= diff.new_path = diff.new_path
- unless @disable_diffs - unless @disable_diffs
%h4 Changes: %h4 Changes:
- @diffs.each_with_index do |diff, i| - @diffs.each_with_index do |diff, i|
%li{id: "diff-#{i}"} %li{id: "diff-#{i}"}
...@@ -62,5 +63,5 @@ ...@@ -62,5 +63,5 @@
= color_email_diff(diff.diff) = color_email_diff(diff.diff)
%br %br
- if @compare.timeout - if @compare.timeout
%h5 Huge diff. To prevent performance issues changes are hidden %h5 Huge diff. To prevent performance issues changes are hidden
#{@author.name} pushed to #{@branch} at #{@project.name_with_namespace} #{@author.name} #{@action_name} #{@ref_type} #{@ref_name} at #{@project.name_with_namespace}
\ - if @compare
\ \
- if @reverse_compare \
- if @reverse_compare
WARNING: The push did not contain any new commits, but force pushed to delete the commits and changes below. WARNING: The push did not contain any new commits, but force pushed to delete the commits and changes below.
\ \
\ \
= @reverse_compare ? "Deleted commits:" : "Commits:" = @reverse_compare ? "Deleted commits:" : "Commits:"
- @commits.each do |commit| - @commits.each do |commit|
#{commit.short_id} by #{commit.author_name} at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")} #{commit.short_id} by #{commit.author_name} at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")}
#{commit.safe_message} #{commit.safe_message}
\- - - - - \- - - - -
\ \
\ \
#{pluralize @diffs.count, "changed file"}: #{pluralize @diffs.count, "changed file"}:
\ \
- @diffs.each do |diff| - @diffs.each do |diff|
- if diff.deleted_file - if diff.deleted_file
\- − #{diff.old_path} \- − #{diff.old_path}
- elsif diff.renamed_file - elsif diff.renamed_file
...@@ -23,7 +24,7 @@ ...@@ -23,7 +24,7 @@
\- + #{diff.new_path} \- + #{diff.new_path}
- else - else
\- #{diff.new_path} \- #{diff.new_path}
- unless @disable_diffs - unless @disable_diffs
\ \
\ \
Changes: Changes:
...@@ -38,10 +39,11 @@ ...@@ -38,10 +39,11 @@
= diff.new_path = diff.new_path
\===================================== \=====================================
!= diff.diff != diff.diff
- if @compare.timeout - if @compare.timeout
\ \
\ \
Huge diff. To prevent performance issues it was hidden Huge diff. To prevent performance issues it was hidden
\ - if @target_url
\ \
View it on GitLab: #{@target_url} \
View it on GitLab: #{@target_url}
%h3.page-title %h3.page-title
My Account History Your Account History
%p.light %p.light
All events created by your account are listed below. All events created by your account are listed below.
%hr %hr
......
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
.pull-right .pull-right
= link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new" = link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new"
%p.light %p.light
My SSH keys: #{@keys.count}
%br
Before you can add an SSH key you need to Before you can add an SSH key you need to
= link_to "generate it.", help_page_path("ssh", "README") = link_to "generate it.", help_page_path("ssh", "README")
%hr %hr
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
.fork-buttons .fork-buttons
- if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace - 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 - 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 = link_to_toggle_fork
- else - else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project" do = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project" do
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
- note_count = @note_counts.fetch(commit.id, 0) - note_count = @note_counts.fetch(commit.id, 0)
- else - else
- notes = project.notes.for_commit_id(commit.id) - notes = project.notes.for_commit_id(commit.id)
- note_count = notes.count - note_count = notes.user.count
- if note_count > 0 - if note_count > 0
%span.light %span.light
......
...@@ -13,10 +13,11 @@ ...@@ -13,10 +13,11 @@
- submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path) - submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path)
= submodule_link(submodule_item, @commit.id) = submodule_link(submodule_item, @commit.id)
- else - else
%span
- if diff_file.renamed_file - if diff_file.renamed_file
%span= "#{diff_file.old_path} renamed to #{diff_file.new_path}" = "#{diff_file.old_path} renamed to #{diff_file.new_path}"
- else - else
%span= diff_file.new_path = diff_file.new_path
- if diff_file.mode_changed? - if diff_file.mode_changed?
%span.file-mode= "#{diff_file.diff.a_mode}#{diff_file.diff.b_mode}" %span.file-mode= "#{diff_file.diff.a_mode}#{diff_file.diff.b_mode}"
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
- else - else
%td.old_line %td.old_line
= link_to raw(type == "new" ? "&nbsp;" : line_old), "##{line_code}", id: line_code = link_to raw(type == "new" ? "&nbsp;" : 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) = link_to_new_diff_note(line_code)
%td.new_line{data: {linenumber: line.new_pos}} %td.new_line{data: {linenumber: line.new_pos}}
= link_to raw(type == "old" ? "&nbsp;" : line.new_pos) , "##{line_code}", id: line_code = link_to raw(type == "old" ? "&nbsp;" : line.new_pos) , "##{line_code}", id: line_code
......
...@@ -10,11 +10,12 @@ ...@@ -10,11 +10,12 @@
- if issue.closed? - if issue.closed?
%span %span
CLOSED CLOSED
- if issue.notes.any? - note_count = issue.notes.user.count
- if note_count > 0
&nbsp; &nbsp;
%span %span
%i.fa.fa-comments %i.fa.fa-comments
= issue.notes.count = note_count
.issue-info .issue-info
= link_to "##{issue.iid}", issue_path(issue), class: "light" = link_to "##{issue.iid}", issue_path(issue), class: "light"
......
...@@ -16,9 +16,9 @@ ...@@ -16,9 +16,9 @@
.col-sm-10 .col-sm-10
.input-group .input-group
.input-group-addon.label-color-preview &nbsp; .input-group-addon.label-color-preview &nbsp;
= f.color_field :color, placeholder: "#AA33EE", class: "form-control" = f.color_field :color, value: "#AA33EE", class: "form-control"
.help-block .help-block
6 character hex values starting with a # sign. Choose any color.
%br %br
Or you can choose one of suggested colors below Or you can choose one of suggested colors below
......
...@@ -16,11 +16,12 @@ ...@@ -16,11 +16,12 @@
%span.label-branch< %span.label-branch<
%i.fa.fa-code-fork %i.fa.fa-code-fork
%span= merge_request.target_branch %span= merge_request.target_branch
- if merge_request.notes.any? - note_count = merge_request.mr_and_commit_notes.user.count
- if note_count > 0
&nbsp; &nbsp;
%span %span
%i.fa.fa-comments %i.fa.fa-comments
= merge_request.mr_and_commit_notes.count = note_count
.merge-request-info .merge-request-info
= link_to "##{merge_request.iid}", merge_request_path(merge_request), class: "light" = link_to "##{merge_request.iid}", merge_request_path(merge_request), class: "light"
- if merge_request.assignee - if merge_request.assignee
......
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
= link_to merge_request_path(@merge_request) do = link_to merge_request_path(@merge_request) do
%i.fa.fa-comments %i.fa.fa-comments
Discussion 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'}} %li.commits-tab{data: {action: 'commits'}}
= link_to merge_request_path(@merge_request), title: 'Commits' do = link_to merge_request_path(@merge_request), title: 'Commits' do
%i.fa.fa-history %i.fa.fa-history
......
...@@ -60,6 +60,7 @@ ...@@ -60,6 +60,7 @@
Participants Participants
%span.badge= @users.count %span.badge= @users.count
- if @project.issues_enabled
.pull-right .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 = 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 %i.fa.fa-plus
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
.timeline-entry .timeline-entry
.timeline-entry-inner .timeline-entry-inner
.timeline-icon .timeline-icon
= link_to user_path(note.author) do
= image_tag avatar_icon(note.author_email), class: "avatar s40" = image_tag avatar_icon(note.author_email), class: "avatar s40"
.timeline-content .timeline-content
- if note.for_merge_request? - if note.for_merge_request?
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
- if note.system - if note.system
%span.fa.fa-circle %span.fa.fa-circle
- else - else
= link_to user_path(note.author) do
= image_tag avatar_icon(note.author_email), class: "avatar s40" = image_tag avatar_icon(note.author_email), class: "avatar s40"
.timeline-content .timeline-content
.note-header .note-header
...@@ -21,6 +22,7 @@ ...@@ -21,6 +22,7 @@
%i.fa.fa-trash-o.cred %i.fa.fa-trash-o.cred
Remove Remove
- if note.system - if note.system
= link_to user_path(note.author) do
= image_tag avatar_icon(note.author_email), class: "avatar s16" = image_tag avatar_icon(note.author_email), class: "avatar s16"
= link_to_member(@project, note.author, avatar: false) = link_to_member(@project, note.author, avatar: false)
%span.author-username %span.author-username
......
...@@ -2,13 +2,13 @@ ...@@ -2,13 +2,13 @@
- if diff - if diff
.diff-file .diff-file
.diff-header .diff-header
%span
- if diff.deleted_file - if diff.deleted_file
%span= diff.old_path = diff.old_path
- else - else
%span= diff.new_path = diff.new_path
- if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
%span.file-mode= "#{diff.a_mode}#{diff.b_mode}" %span.file-mode= "#{diff.a_mode}#{diff.b_mode}"
%br/
.diff-content .diff-content
%table %table
- note.truncated_diff_lines.each do |line| - note.truncated_diff_lines.each do |line|
......
%h3.page-title %h3.page-title
My Snippets Your Snippets
.pull-right .pull-right
= link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do
Add new snippet Add new snippet
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
= link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do
Add new snippet Add new snippet
= link_to user_snippets_path(current_user), class: "btn btn-grouped" do = link_to user_snippets_path(current_user), class: "btn btn-grouped" do
My snippets Your snippets
%p.light %p.light
Public snippets created by you and other users are listed here Public snippets created by you and other users are listed here
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
.back-link .back-link
- if @snippet.author == current_user - if @snippet.author == current_user
= link_to user_snippets_path(current_user) do = link_to user_snippets_path(current_user) do
&larr; my snippets &larr; your snippets
- else - else
= link_to snippets_path do = link_to snippets_path do
&larr; discover snippets &larr; discover snippets
......
- if @contributed_projects.present? - if @contributed_projects.present?
.panel.panel-default .panel.panel-default.contributed-projects
.panel-heading Projects contributed to .panel-heading Projects contributed to
= render 'shared/projects_list', = render 'shared/projects_list',
projects: @contributed_projects.sort_by(&:star_count).reverse, projects: @contributed_projects.sort_by(&:star_count).reverse,
......
%h4 Commits calendar %h4
Contributions calendar
.pull-right
%small Issues, merge requests and push events
#cal-heatmap.calendar #cal-heatmap.calendar
:javascript :javascript
new calendar( new calendar(
......
.calendar_commit_activity %h4.prepend-top-20
%hr %span.light Contributions for
%h4 %strong #{@calendar_date.to_s(:short)}
Commit Activity
%strong %ul.bordered-list
- if @commit_count == 0 - @events.sort_by(&:created_at).each do |event|
no %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 - else
= @commit_count = event_action_name(event)
%span.calendar_commit_date - if event.target
unique %strong= link_to "##{event.target_iid}", [event.project.namespace.becomes(Namespace), event.project, event.target]
= 'commit'.pluralize(@commit_count)
on at
= @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
%strong %strong
= pluralize(commits.count, 'commit') - if event.project
in project = link_to_project event.project
= link_to project.name_with_namespace, project_path(project) - else
%a.text-expander.js-toggle-button &hellip; = event.project_name
%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
...@@ -40,7 +40,8 @@ ...@@ -40,7 +40,8 @@
%strong %strong
%i.fa.fa-rss %i.fa.fa-rss
= render @events .content_list
= spinner
%aside.col-md-4 %aside.col-md-4
= render 'profile', user: @user = render 'profile', user: @user
= render 'projects' = render 'projects'
......
class EmailsOnPushWorker class EmailsOnPushWorker
include Sidekiq::Worker 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) project = Project.find(project_id)
before_sha = push_data["before"] before_sha = push_data["before"]
after_sha = push_data["after"] after_sha = push_data["after"]
branch = push_data["ref"] ref = push_data["ref"]
author_id = push_data["user_id"] author_id = push_data["user_id"]
if Gitlab::Git.blank_ref?(before_sha) || Gitlab::Git.blank_ref?(after_sha) action =
# skip if new branch was pushed or branch was removed if Gitlab::Git.blank_ref?(before_sha)
return true :create
elsif Gitlab::Git.blank_ref?(after_sha)
:delete
else
:push
end end
compare = nil
reverse_compare = false
if action == :push
compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha) compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha)
return false if compare.same return false if compare.same
...@@ -24,17 +31,19 @@ class EmailsOnPushWorker ...@@ -24,17 +31,19 @@ class EmailsOnPushWorker
return false if compare.commits.empty? return false if compare.commits.empty?
end end
end
recipients.split(" ").each do |recipient| recipients.split(" ").each do |recipient|
Notify.repository_push_email( Notify.repository_push_email(
project_id, project_id,
recipient, recipient,
author_id, author_id: author_id,
branch, ref: ref,
compare, action: action,
reverse_compare, compare: compare,
send_from_committer_email, reverse_compare: reverse_compare,
disable_diffs send_from_committer_email: send_from_committer_email,
disable_diffs: disable_diffs
).deliver ).deliver
end end
ensure ensure
......
...@@ -285,6 +285,9 @@ production: &base ...@@ -285,6 +285,9 @@ production: &base
rack_attack: rack_attack:
git_basic_auth: 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 # Whitelist requests from 127.0.0.1 for web proxies (NGINX/Apache) with incorrect headers
# ip_whitelist: ["127.0.0.1"] # ip_whitelist: ["127.0.0.1"]
# #
......
...@@ -183,6 +183,7 @@ Settings['extra'] ||= Settingslogic.new({}) ...@@ -183,6 +183,7 @@ Settings['extra'] ||= Settingslogic.new({})
# #
Settings['rack_attack'] ||= Settingslogic.new({}) Settings['rack_attack'] ||= Settingslogic.new({})
Settings.rack_attack['git_basic_auth'] ||= 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['ip_whitelist'] ||= %w{127.0.0.1}
Settings.rack_attack.git_basic_auth['maxretry'] ||= 10 Settings.rack_attack.git_basic_auth['maxretry'] ||= 10
Settings.rack_attack.git_basic_auth['findtime'] ||= 1.minute Settings.rack_attack.git_basic_auth['findtime'] ||= 1.minute
......
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
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
......
...@@ -11,7 +11,7 @@ RUN apt-get update -q \ ...@@ -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 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. # If you run GitLab Enterprise Edition point it to a location where you have downloaded it.
RUN TMP_FILE=$(mktemp); \ 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 \ && dpkg -i $TMP_FILE \
&& rm -f $TMP_FILE && rm -f $TMP_FILE
......
...@@ -7,3 +7,10 @@ Feature: Admin Settings ...@@ -7,3 +7,10 @@ Feature: Admin Settings
Scenario: Change application settings Scenario: Change application settings
When I modify settings and save form When I modify settings and save form
Then I should see application settings saved 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
...@@ -15,4 +15,33 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps ...@@ -15,4 +15,33 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps
current_application_settings.home_page_url.should == 'https://about.gitlab.com/' current_application_settings.home_page_url.should == 'https://about.gitlab.com/'
page.should have_content 'Application settings saved successfully' page.should have_content 'Application settings saved successfully'
end 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 end
...@@ -7,4 +7,37 @@ class Spinach::Features::User < Spinach::FeatureSteps ...@@ -7,4 +7,37 @@ class Spinach::Features::User < Spinach::FeatureSteps
step 'I should see user "John Doe" page' do step 'I should see user "John Doe" page' do
expect(title).to match(/^\s*John Doe/) expect(title).to match(/^\s*John Doe/)
end 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 end
...@@ -67,3 +67,12 @@ Feature: User ...@@ -67,3 +67,12 @@ Feature: User
And I should see project "Enterprise" And I should see project "Enterprise"
And I should not see project "Internal" And I should not see project "Internal"
And I should not see project "Community" 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
require 'mime/types' require 'mime/types'
require 'uri'
module API module API
# Projects API # Projects API
...@@ -103,7 +104,7 @@ module API ...@@ -103,7 +104,7 @@ module API
delete ":id/repository/branches/:branch" do delete ":id/repository/branches/:branch" do
authorize_push_project authorize_push_project
result = DeleteBranchService.new(user_project, current_user). result = DeleteBranchService.new(user_project, current_user).
execute(params[:branch]) execute(URI.unescape(params[:branch]))
if result[:status] == :success if result[:status] == :success
{ {
......
require_relative 'rack_attack_helpers'
require_relative 'shell_env' require_relative 'shell_env'
module Grack module Grack
...@@ -85,15 +86,24 @@ module Grack ...@@ -85,15 +86,24 @@ module Grack
user = oauth_access_token_check(login, password) user = oauth_access_token_check(login, password)
end end
return user if user.present? # If the user authenticated successfully, we reset the auth failure count
# from Rack::Attack for that IP. A client may attempt to authenticate
# At this point, we know the credentials were wrong. We let Rack::Attack # with a username and blank password first, and only after it receives
# know there was a failed authentication attempt from this IP. This # a 401 error does it present a password. Resetting the count prevents
# information is stored in the Rails cache (Redis) and will be used by # false positives from occurring.
# the Rack::Attack middleware to decide whether to block requests from #
# this IP. # 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 config = Gitlab.config.rack_attack.git_basic_auth
Rack::Attack::Allow2Ban.filter(@request.ip, config) do
if config.enabled
if user
# A successful login will reset the auth failure count from this IP
Rack::Attack::Allow2Ban.reset(@request.ip, config)
else
banned = Rack::Attack::Allow2Ban.filter(@request.ip, config) do
# Unless the IP is whitelisted, return true so that Allow2Ban # Unless the IP is whitelisted, return true so that Allow2Ban
# increments the counter (stored in Rails.cache) for the IP # increments the counter (stored in Rails.cache) for the IP
if config.ip_whitelist.include?(@request.ip) if config.ip_whitelist.include?(@request.ip)
...@@ -103,7 +113,14 @@ module Grack ...@@ -103,7 +113,14 @@ module Grack
end end
end end
nil # No user was found if banned
Rails.logger.info "IP #{@request.ip} failed to login " \
"as #{login} but has been temporarily banned from Git auth"
end
end
end
user
end end
def authorized_request? def authorized_request?
......
# 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
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
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
...@@ -14,7 +14,6 @@ module Gitlab ...@@ -14,7 +14,6 @@ module Gitlab
end end
def self.find_by_dn(dn, adapter) def self.find_by_dn(dn, adapter)
dn = Net::LDAP::Filter.escape(dn)
adapter.user('dn', dn) adapter.user('dn', dn)
end end
......
...@@ -368,11 +368,12 @@ module Gitlab ...@@ -368,11 +368,12 @@ module Gitlab
# ActiveSupport::SafeBuffer, hence the `String.new` # ActiveSupport::SafeBuffer, hence the `String.new`
String.new(text).gsub(Taskable::TASK_PATTERN_HTML) do String.new(text).gsub(Taskable::TASK_PATTERN_HTML) do
checked = $LAST_MATCH_INFO[:checked].downcase == 'x' checked = $LAST_MATCH_INFO[:checked].downcase == 'x'
p_tag = $LAST_MATCH_INFO[:p_tag]
if checked if checked
"#{li_tag}#{checked_box}" "#{li_tag}#{p_tag}#{checked_box}"
else else
"#{li_tag}#{unchecked_box}" "#{li_tag}#{p_tag}#{unchecked_box}"
end end
end end
end end
......
...@@ -67,7 +67,7 @@ module Gitlab ...@@ -67,7 +67,7 @@ module Gitlab
end end
def notes 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 end
def limit_project_ids def limit_project_ids
......
...@@ -25,34 +25,21 @@ describe UsersController do ...@@ -25,34 +25,21 @@ describe UsersController do
end end
describe 'GET #calendar_activities' do describe 'GET #calendar_activities' do
include RepoHelpers let!(:project) { create(:project) }
let(:project) { create(:project) } let!(:user) { create(:user) }
let(:calendar_user) { create(:user, email: sample_commit.author_email) }
let(:commit1) { '0ed8c6c6752e8c6ea63e7b92a517bf5ac1209c80' }
let(:commit2) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' }
before do before do
allow_any_instance_of(User).to receive(:contributed_projects_ids).and_return([project.id]) allow_any_instance_of(User).to receive(:contributed_projects_ids).and_return([project.id])
project.team << [user, :developer] project.team << [user, :developer]
end 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 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')) expect(assigns(:calendar_date)).to eq(Date.parse('2014-07-31'))
end 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 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') expect(response).to render_template('calendar_activities')
end end
end end
......
...@@ -847,6 +847,17 @@ EOT ...@@ -847,6 +847,17 @@ EOT
) )
end 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 it 'should not be confused by whitespace before bullets' do
rendered_text_asterisk = markdown(@source_text_asterisk, rendered_text_asterisk = markdown(@source_text_asterisk,
parse_tasks: true) parse_tasks: true)
......
...@@ -85,6 +85,17 @@ describe Grack::Auth do ...@@ -85,6 +85,17 @@ describe Grack::Auth do
it "responds with status 401" do it "responds with status 401" do
expect(status).to eq(401) expect(status).to eq(401)
end 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 end
context "when authentication succeeds" do context "when authentication succeeds" do
...@@ -109,10 +120,49 @@ describe Grack::Auth do ...@@ -109,10 +120,49 @@ describe Grack::Auth do
end end
context "when the user isn't blocked" do context "when the user isn't blocked" do
before do
expect(Rack::Attack::Allow2Ban).to receive(:reset)
end
it "responds with status 200" do it "responds with status 200" do
expect(status).to eq(200) expect(status).to eq(200)
end end
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 end
context "when the user doesn't have access to the project" do context "when the user doesn't have access to the project" do
......
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
...@@ -640,6 +640,100 @@ describe Notify do ...@@ -640,6 +640,100 @@ describe Notify do
end end
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 describe 'email on push with multiple commits' do
let(:example_site_path) { root_path } let(:example_site_path) { root_path }
let(:user) { create(:user) } let(:user) { create(:user) }
...@@ -648,7 +742,7 @@ describe Notify do ...@@ -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(: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 } 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 it 'is sent as the author' do
sender = subject.header[:from].addrs[0] sender = subject.header[:from].addrs[0]
...@@ -736,7 +830,7 @@ describe Notify do ...@@ -736,7 +830,7 @@ describe Notify do
let(:commits) { Commit.decorate(compare.commits) } let(:commits) { Commit.decorate(compare.commits) }
let(:diff_path) { namespace_project_commit_path(project.namespace, project, commits.first) } 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 it 'is sent as the author' do
sender = subject.header[:from].addrs[0] sender = subject.header[:from].addrs[0]
......
...@@ -13,47 +13,16 @@ describe Repository do ...@@ -13,47 +13,16 @@ describe Repository do
it { is_expected.not_to include('fix') } it { is_expected.not_to include('fix') }
end end
describe :last_commit_for_path do describe :tag_names_contains do
subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id } subject { repository.tag_names_contains(sample_commit.id) }
it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') }
end
context :timestamps_by_user_log do it { is_expected.to include('v1.1.0') }
before do it { is_expected.not_to include('v1.0.0') }
Date.stub(:today).and_return(Date.new(2015, 03, 01))
end end
describe 'single e-mail for user' do describe :last_commit_for_path do
let(:user) { create(:user, email: sample_commit.author_email) } subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id }
subject { repository.timestamps_by_user_log(user) }
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
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)) }
it 'contains the exepected commits' do it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') }
expect(subject.flatten.map(&:id)).to eq([commit1, commit2])
end
end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment