Commit eb42f95c authored by Kamil Trzcinski's avatar Kamil Trzcinski

Merge remote-tracking branch 'origin/master' into docker-registry-view

parents a26ec8a1 8b15628d
...@@ -5,6 +5,7 @@ v 8.8.0 (unreleased) ...@@ -5,6 +5,7 @@ v 8.8.0 (unreleased)
- Fix error when using link to uploads in global snippets - Fix error when using link to uploads in global snippets
- Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen) - Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen)
- Use a case-insensitive comparison in sanitizing URI schemes - Use a case-insensitive comparison in sanitizing URI schemes
- Toggle sign-up confirmation emails in application settings
- Project#open_branches has been cleaned up and no longer loads entire records into memory. - Project#open_branches has been cleaned up and no longer loads entire records into memory.
- Escape HTML in commit titles in system note messages - Escape HTML in commit titles in system note messages
- Improve multiple branch push performance by memoizing permission checking - Improve multiple branch push performance by memoizing permission checking
...@@ -15,6 +16,7 @@ v 8.8.0 (unreleased) ...@@ -15,6 +16,7 @@ v 8.8.0 (unreleased)
- Make build status canceled if any of the jobs was canceled and none failed - Make build status canceled if any of the jobs was canceled and none failed
- Upgrade Sidekiq to 4.1.2 - Upgrade Sidekiq to 4.1.2
- Added /health_check endpoint for checking service status - Added /health_check endpoint for checking service status
- Make 'upcoming' filter for milestones work better across projects
- Sanitize repo paths in new project error message - Sanitize repo paths in new project error message
- Bump mail_room to 0.7.0 to fix stuck IDLE connections - Bump mail_room to 0.7.0 to fix stuck IDLE connections
- Remove future dates from contribution calendar graph. - Remove future dates from contribution calendar graph.
...@@ -29,6 +31,7 @@ v 8.8.0 (unreleased) ...@@ -29,6 +31,7 @@ v 8.8.0 (unreleased)
- Display informative message when new milestone is created - Display informative message when new milestone is created
- Sanitize milestones and labels titles - Sanitize milestones and labels titles
- Support multi-line tag messages. !3833 (Calin Seciu) - Support multi-line tag messages. !3833 (Calin Seciu)
- Force users to reset their password after an admin changes it
- Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea) - Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea)
- Added button to toggle whitespaces changes on diff view - Added button to toggle whitespaces changes on diff view
- Backport GitHub Enterprise import support from EE - Backport GitHub Enterprise import support from EE
...@@ -44,7 +47,7 @@ v 8.8.0 (unreleased) ...@@ -44,7 +47,7 @@ v 8.8.0 (unreleased)
- API support for the 'since' and 'until' operators on commit requests (Paco Guzman) - API support for the 'since' and 'until' operators on commit requests (Paco Guzman)
- Fix Gravatar hint in user profile when Gravatar is disabled. !3988 (Artem Sidorenko) - Fix Gravatar hint in user profile when Gravatar is disabled. !3988 (Artem Sidorenko)
- Expire repository exists? and has_visible_content? caches after a push if necessary - Expire repository exists? and has_visible_content? caches after a push if necessary
- Fix unintentional filtering bug in issues sorted by milestone due (Takuya Noguchi) - Fix unintentional filtering bug in Issue/MR sorted by milestone due (Takuya Noguchi)
- Fix adding a todo for private group members (Ahmad Sherif) - Fix adding a todo for private group members (Ahmad Sherif)
- Bump ace-rails-ap gem version from 2.0.1 to 4.0.2 which upgrades Ace Editor from 1.1.2 to 1.2.3 - Bump ace-rails-ap gem version from 2.0.1 to 4.0.2 which upgrades Ace Editor from 1.1.2 to 1.2.3
- Total method execution timings are no longer tracked - Total method execution timings are no longer tracked
...@@ -52,6 +55,11 @@ v 8.8.0 (unreleased) ...@@ -52,6 +55,11 @@ v 8.8.0 (unreleased)
- Add API endpoints for un/subscribing from/to a label. !4051 (Ahmad Sherif) - Add API endpoints for un/subscribing from/to a label. !4051 (Ahmad Sherif)
- Hide left sidebar on phone screens to give more space for content - Hide left sidebar on phone screens to give more space for content
- Redesign navigation for profile and group pages - Redesign navigation for profile and group pages
- Add counter metrics for rails cache
- Import pull requests from GitHub where the source or target branches were removed
v 8.7.6
- Fix links on wiki pages for relative url setups. !4131 (Artem Sidorenko)
v 8.7.5 v 8.7.5
- Fix relative links in wiki pages. !4050 - Fix relative links in wiki pages. !4050
......
...@@ -285,6 +285,7 @@ class @Notes ...@@ -285,6 +285,7 @@ class @Notes
form.addClass "js-main-target-form" form.addClass "js-main-target-form"
form.find("#note_line_code").remove() form.find("#note_line_code").remove()
form.find("#note_type").remove()
### ###
General note form setup. General note form setup.
...@@ -472,6 +473,7 @@ class @Notes ...@@ -472,6 +473,7 @@ class @Notes
setupDiscussionNoteForm: (dataHolder, form) => setupDiscussionNoteForm: (dataHolder, form) =>
# setup note target # setup note target
form.attr 'id', "new-discussion-note-form-#{dataHolder.data("discussionId")}" form.attr 'id', "new-discussion-note-form-#{dataHolder.data("discussionId")}"
form.find("#note_type").val dataHolder.data("noteType")
form.find("#line_type").val dataHolder.data("lineType") form.find("#line_type").val dataHolder.data("lineType")
form.find("#note_commit_id").val dataHolder.data("commitId") form.find("#note_commit_id").val dataHolder.data("commitId")
form.find("#note_line_code").val dataHolder.data("lineCode") form.find("#note_line_code").val dataHolder.data("lineCode")
......
...@@ -88,8 +88,8 @@ ...@@ -88,8 +88,8 @@
} }
$theme-blue: #2980b9; $theme-blue: #2980b9;
$theme-charcoal: #333c47; $theme-charcoal: #3d454d;
$theme-graphite: #888; $theme-graphite: #666;
$theme-gray: #373737; $theme-gray: #373737;
$theme-green: #019875; $theme-green: #019875;
$theme-violet: #548; $theme-violet: #548;
...@@ -100,11 +100,11 @@ body { ...@@ -100,11 +100,11 @@ body {
} }
&.ui_charcoal { &.ui_charcoal {
@include gitlab-theme(#c5d0de, $theme-charcoal, #2b333d, #24272d); @include gitlab-theme(#d6d7d9, #485157, $theme-charcoal, #353b41);
} }
&.ui_graphite { &.ui_graphite {
@include gitlab-theme(#ccc, $theme-graphite, #777, #666); @include gitlab-theme(#ccc, #777, $theme-graphite, #555);
} }
&.ui_gray { &.ui_gray {
......
...@@ -226,8 +226,7 @@ ul.notes { ...@@ -226,8 +226,7 @@ ul.notes {
} }
} }
.note-action-button, .note-action-button {
.discussion-action-button {
display: inline-block; display: inline-block;
margin-left: 10px; margin-left: 10px;
line-height: 24px; line-height: 24px;
......
...@@ -106,6 +106,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -106,6 +106,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:email_author_in_body, :email_author_in_body,
:repository_checks_enabled, :repository_checks_enabled,
:metrics_packet_size, :metrics_packet_size,
:send_user_confirmation_email,
restricted_visibility_levels: [], restricted_visibility_levels: [],
import_sources: [], import_sources: [],
disabled_oauth_sign_in_sources: [] disabled_oauth_sign_in_sources: []
......
...@@ -119,6 +119,7 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -119,6 +119,7 @@ class Admin::UsersController < Admin::ApplicationController
user_params_with_pass.merge!( user_params_with_pass.merge!(
password: params[:user][:password], password: params[:user][:password],
password_confirmation: params[:user][:password_confirmation], password_confirmation: params[:user][:password_confirmation],
password_expires_at: Time.now
) )
end end
......
...@@ -17,11 +17,11 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -17,11 +17,11 @@ class Projects::CommitController < Projects::ApplicationController
def show def show
apply_diff_view_cookie! apply_diff_view_cookie!
@line_notes = commit.notes.inline @grouped_diff_notes = commit.notes.grouped_diff_notes
@note = @project.build_commit_note(commit) @note = @project.build_commit_note(commit)
@notes = commit.notes.not_inline.fresh @notes = commit.notes.non_diff_notes.fresh
@noteable = @commit @noteable = @commit
@comments_allowed = @reply_allowed = true
@comments_target = { @comments_target = {
noteable_type: 'Commit', noteable_type: 'Commit',
commit_id: @commit.id commit_id: @commit.id
......
...@@ -22,7 +22,8 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -22,7 +22,8 @@ class Projects::CompareController < Projects::ApplicationController
@base_commit = @project.merge_base_commit(@base_ref, @head_ref) @base_commit = @project.merge_base_commit(@base_ref, @head_ref)
@diffs = compare.diffs(diff_options) @diffs = compare.diffs(diff_options)
@diff_refs = [@base_commit, @commit] @diff_refs = [@base_commit, @commit]
@line_notes = [] @diff_notes_disabled = true
@grouped_diff_notes = {}
end end
end end
......
...@@ -73,12 +73,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -73,12 +73,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController
# but we need it for the "View file @ ..." link by deleted files # but we need it for the "View file @ ..." link by deleted files
@base_commit ||= @merge_request.first_commit.parent || @merge_request.first_commit @base_commit ||= @merge_request.first_commit.parent || @merge_request.first_commit
@comments_allowed = @reply_allowed = true
@comments_target = { @comments_target = {
noteable_type: 'MergeRequest', noteable_type: 'MergeRequest',
noteable_id: @merge_request.id noteable_id: @merge_request.id
} }
@line_notes = @merge_request.notes.where("line_code is not null")
@grouped_diff_notes = @merge_request.notes.grouped_diff_notes
respond_to do |format| respond_to do |format|
format.html format.html
...@@ -117,6 +117,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -117,6 +117,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@commit = @merge_request.last_commit @commit = @merge_request.last_commit
@base_commit = @merge_request.diff_base_commit @base_commit = @merge_request.diff_base_commit
@diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare @diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare
@diff_notes_disabled = true
@ci_commit = @merge_request.ci_commit @ci_commit = @merge_request.ci_commit
@statuses = @ci_commit.statuses if @ci_commit @statuses = @ci_commit.statuses if @ci_commit
...@@ -300,7 +301,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -300,7 +301,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
# Build a note object for comment form # Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request) @note = @project.notes.new(noteable: @merge_request)
@notes = @merge_request.mr_and_commit_notes.nonawards.inc_author.fresh @notes = @merge_request.mr_and_commit_notes.nonawards.inc_author.fresh
@discussions = Note.discussions_from_notes(@notes) @discussions = @notes.discussions
@noteable = @merge_request @noteable = @merge_request
# Get commits from repository # Get commits from repository
......
...@@ -96,7 +96,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -96,7 +96,7 @@ class Projects::NotesController < Projects::ApplicationController
end end
def note_to_discussion_html(note) def note_to_discussion_html(note)
return unless note.for_diff_line? return unless note.diff_note?
if params[:view] == 'parallel' if params[:view] == 'parallel'
template = "projects/notes/_diff_notes_with_reply_parallel" template = "projects/notes/_diff_notes_with_reply_parallel"
...@@ -120,7 +120,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -120,7 +120,7 @@ class Projects::NotesController < Projects::ApplicationController
end end
def note_to_discussion_with_diff_html(note) def note_to_discussion_with_diff_html(note)
return unless note.for_diff_line? return unless note.diff_note?
render_to_string( render_to_string(
"projects/notes/_discussion", "projects/notes/_discussion",
...@@ -158,7 +158,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -158,7 +158,7 @@ class Projects::NotesController < Projects::ApplicationController
def note_params def note_params
params.require(:note).permit( params.require(:note).permit(
:note, :noteable, :noteable_id, :noteable_type, :project_id, :note, :noteable, :noteable_id, :noteable_type, :project_id,
:attachment, :line_code, :commit_id :attachment, :line_code, :commit_id, :type
) )
end end
......
...@@ -37,8 +37,8 @@ class RegistrationsController < Devise::RegistrationsController ...@@ -37,8 +37,8 @@ class RegistrationsController < Devise::RegistrationsController
super super
end end
def after_sign_up_path_for(_resource) def after_sign_up_path_for(user)
users_almost_there_path user.confirmed_at.present? ? dashboard_projects_path : users_almost_there_path
end end
def after_inactive_sign_up_path_for(_resource) def after_inactive_sign_up_path_for(_resource)
......
...@@ -252,8 +252,8 @@ class IssuableFinder ...@@ -252,8 +252,8 @@ class IssuableFinder
if filter_by_no_milestone? if filter_by_no_milestone?
items = items.where(milestone_id: [-1, nil]) items = items.where(milestone_id: [-1, nil])
elsif filter_by_upcoming_milestone? elsif filter_by_upcoming_milestone?
upcoming = Milestone.where(project_id: projects).upcoming upcoming_ids = Milestone.upcoming_ids_by_projects(projects)
items = items.joins(:milestone).where(milestones: { title: upcoming.try(:title) }) items = items.joins(:milestone).where(milestone_id: upcoming_ids)
else else
items = items.joins(:milestone).where(milestones: { title: params[:milestone_title] }) items = items.joins(:milestone).where(milestones: { title: params[:milestone_title] })
......
...@@ -10,7 +10,7 @@ class NotesFinder ...@@ -10,7 +10,7 @@ class NotesFinder
notes = notes =
case target_type case target_type
when "commit" when "commit"
project.notes.for_commit_id(target_id).not_inline project.notes.for_commit_id(target_id).non_diff_notes
when "issue" when "issue"
project.issues.find(target_id).notes.nonawards.inc_author project.issues.find(target_id).notes.nonawards.inc_author
when "merge_request" when "merge_request"
......
...@@ -55,22 +55,18 @@ module DiffHelper ...@@ -55,22 +55,18 @@ module DiffHelper
end end
end end
def line_comments def organize_comments(left, right)
@line_comments ||= @line_notes.select(&:active?).sort_by(&:created_at).group_by(&:line_code) notes_left = notes_right = nil
end
def organize_comments(type_left, type_right, line_code_left, line_code_right)
comments_left = comments_right = nil
unless type_left.nil? && type_right == 'new' unless left[:type].nil? && right[:type] == 'new'
comments_left = line_comments[line_code_left] notes_left = @grouped_diff_notes[left[:line_code]]
end end
unless type_left.nil? && type_right.nil? unless left[:type].nil? && right[:type].nil?
comments_right = line_comments[line_code_right] notes_right = @grouped_diff_notes[right[:line_code]]
end end
[comments_left, comments_right] [notes_left, notes_right]
end end
def inline_diff_btn def inline_diff_btn
...@@ -96,8 +92,8 @@ module DiffHelper ...@@ -96,8 +92,8 @@ module DiffHelper
].join(' ').html_safe ].join(' ').html_safe
end end
def commit_for_diff(diff) def commit_for_diff(diff_file)
if diff.deleted_file if diff_file.deleted_file
@base_commit || @commit.parent || @commit @base_commit || @commit.parent || @commit
else else
@commit @commit
......
...@@ -3,7 +3,7 @@ module EventsHelper ...@@ -3,7 +3,7 @@ module EventsHelper
author = event.author author = event.author
if author if author
link_to author.name, user_path(author.username), title: h(author.name) link_to author.name, user_path(author.username), title: author.name
else else
event.author_name event.author_name
end end
...@@ -57,11 +57,7 @@ module EventsHelper ...@@ -57,11 +57,7 @@ module EventsHelper
words << event.ref_name words << event.ref_name
words << "at" words << "at"
elsif event.commented? elsif event.commented?
if event.note_commit? words << event.note_target_reference
words << event.note_short_commit_id
else
words << "##{truncate event.note_target_iid}"
end
words << "at" words << "at"
elsif event.milestone? elsif event.milestone?
words << "##{event.target_iid}" if event.target_iid words << "##{event.target_iid}" if event.target_iid
...@@ -84,22 +80,13 @@ module EventsHelper ...@@ -84,22 +80,13 @@ module EventsHelper
elsif event.merge_request? elsif event.merge_request?
namespace_project_merge_request_url(event.project.namespace, namespace_project_merge_request_url(event.project.namespace,
event.project, event.merge_request) event.project, event.merge_request)
elsif event.note? && event.note_commit? elsif event.note? && event.commit_note?
namespace_project_commit_url(event.project.namespace, event.project, namespace_project_commit_url(event.project.namespace, event.project,
event.note_target) event.note_target)
elsif event.note? elsif event.note?
if event.note_target if event.note_target
if event.note_commit?
namespace_project_commit_path(event.project.namespace, event.project,
event.note_commit_id,
anchor: dom_id(event.target))
elsif event.note_project_snippet?
namespace_project_snippet_path(event.project.namespace,
event.project, event.note_target)
else
event_note_target_path(event) event_note_target_path(event)
end end
end
elsif event.push? elsif event.push?
push_event_feed_url(event) push_event_feed_url(event)
end end
...@@ -134,9 +121,16 @@ module EventsHelper ...@@ -134,9 +121,16 @@ module EventsHelper
end end
def event_note_target_path(event) def event_note_target_path(event)
if event.note? && event.note_commit? if event.note? && event.commit_note?
namespace_project_commit_path(event.project.namespace, event.project, namespace_project_commit_path(event.project.namespace,
event.note_target) event.project,
event.note_target,
anchor: dom_id(event.target))
elsif event.project_snippet_note?
namespace_project_snippet_path(event.project.namespace,
event.project,
event.note_target,
anchor: dom_id(event.target))
else else
polymorphic_path([event.project.namespace.becomes(Namespace), polymorphic_path([event.project.namespace.becomes(Namespace),
event.project, event.note_target], event.project, event.note_target],
...@@ -146,30 +140,11 @@ module EventsHelper ...@@ -146,30 +140,11 @@ module EventsHelper
def event_note_title_html(event) def event_note_title_html(event)
if event.note_target if event.note_target
if event.note_commit? link_to(event_note_target_path(event), title: event.target_title, class: 'has-tooltip') do
link_to( "#{event.note_target_type} #{event.note_target_reference}"
namespace_project_commit_path(event.project.namespace, event.project,
event.note_commit_id,
anchor: dom_id(event.target), title: h(event.target_title)),
class: "commit_short_id"
) do
"#{event.note_target_type} #{event.note_short_commit_id}"
end
elsif event.note_project_snippet?
link_to(namespace_project_snippet_path(event.project.namespace,
event.project,
event.note_target), title: h(event.project.name)) do
"#{event.note_target_type} #{truncate event.note_target.to_reference}"
end end
else else
link_to event_note_target_path(event) do content_tag(:strong, '(deleted)')
"#{event.note_target_type} #{truncate event.note_target.to_reference}"
end
end
else
content_tag :strong do
"(deleted)"
end
end end
end end
......
module NotesHelper module NotesHelper
# Helps to distinguish e.g. commit notes in mr notes list # Helps to distinguish e.g. commit notes in mr notes list
def note_for_main_target?(note) def note_for_main_target?(note)
(@noteable.class.name == note.noteable_type && !note.for_diff_line?) @noteable.class.name == note.noteable_type && !note.diff_note?
end end
def note_target_fields(note) def note_target_fields(note)
...@@ -15,16 +15,6 @@ module NotesHelper ...@@ -15,16 +15,6 @@ module NotesHelper
note.editable? && can?(current_user, :admin_note, note) note.editable? && can?(current_user, :admin_note, note)
end end
def link_to_commit_diff_line_note(note)
if note.for_commit_diff_line?
link_to(
"#{note.diff_file_name}:L#{note.diff_new_line}",
namespace_project_commit_path(@project.namespace, @project,
note.noteable, anchor: note.line_code)
)
end
end
def noteable_json(noteable) def noteable_json(noteable)
{ {
id: noteable.id, id: noteable.id,
...@@ -35,7 +25,7 @@ module NotesHelper ...@@ -35,7 +25,7 @@ module NotesHelper
end end
def link_to_new_diff_note(line_code, line_type = nil) def link_to_new_diff_note(line_code, line_type = nil)
discussion_id = Note.build_discussion_id( discussion_id = LegacyDiffNote.build_discussion_id(
@comments_target[:noteable_type], @comments_target[:noteable_type],
@comments_target[:noteable_id] || @comments_target[:commit_id], @comments_target[:noteable_id] || @comments_target[:commit_id],
line_code line_code
...@@ -45,9 +35,10 @@ module NotesHelper ...@@ -45,9 +35,10 @@ module NotesHelper
noteable_type: @comments_target[:noteable_type], noteable_type: @comments_target[:noteable_type],
noteable_id: @comments_target[:noteable_id], noteable_id: @comments_target[:noteable_id],
commit_id: @comments_target[:commit_id], commit_id: @comments_target[:commit_id],
line_type: line_type,
line_code: line_code, line_code: line_code,
discussion_id: discussion_id, note_type: LegacyDiffNote.name,
line_type: line_type discussion_id: discussion_id
} }
button_tag(class: 'btn add-diff-note js-add-diff-note-button', button_tag(class: 'btn add-diff-note js-add-diff-note-button',
...@@ -57,18 +48,24 @@ module NotesHelper ...@@ -57,18 +48,24 @@ module NotesHelper
end end
end end
def link_to_reply_diff(note, line_type = nil) def link_to_reply_discussion(note, line_type = nil)
return unless current_user return unless current_user
data = { data = {
noteable_type: note.noteable_type, noteable_type: note.noteable_type,
noteable_id: note.noteable_id, noteable_id: note.noteable_id,
commit_id: note.commit_id, commit_id: note.commit_id,
line_code: note.line_code,
discussion_id: note.discussion_id, discussion_id: note.discussion_id,
line_type: line_type line_type: line_type
} }
if note.diff_note?
data.merge!(
line_code: note.line_code,
note_type: LegacyDiffNote.name
)
end
button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button', button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button',
data: data, title: 'Add a reply' data: data, title: 'Add a reply'
end end
......
...@@ -120,7 +120,8 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -120,7 +120,8 @@ class ApplicationSetting < ActiveRecord::Base
recaptcha_enabled: false, recaptcha_enabled: false,
akismet_enabled: false, akismet_enabled: false,
repository_checks_enabled: true, repository_checks_enabled: true,
disabled_oauth_sign_in_sources: [] disabled_oauth_sign_in_sources: [],
send_user_confirmation_email: false
) )
end end
......
...@@ -80,7 +80,7 @@ class Event < ActiveRecord::Base ...@@ -80,7 +80,7 @@ class Event < ActiveRecord::Base
end end
def target_title def target_title
target.title if target && target.respond_to?(:title) target.try(:title)
end end
def created? def created?
...@@ -266,28 +266,20 @@ class Event < ActiveRecord::Base ...@@ -266,28 +266,20 @@ class Event < ActiveRecord::Base
branch? && project.default_branch != branch_name branch? && project.default_branch != branch_name
end end
def note_commit_id
target.commit_id
end
def target_iid def target_iid
target.respond_to?(:iid) ? target.iid : target_id target.respond_to?(:iid) ? target.iid : target_id
end end
def note_short_commit_id def commit_note?
Commit.truncate_sha(note_commit_id) target.for_commit?
end
def note_commit?
target.noteable_type == "Commit"
end end
def issue_note? def issue_note?
note? && target && target.noteable_type == "Issue" note? && target && target.for_issue?
end end
def note_project_snippet? def project_snippet_note?
target.noteable_type == "Snippet" target.for_snippet?
end end
def note_target def note_target
...@@ -295,19 +287,22 @@ class Event < ActiveRecord::Base ...@@ -295,19 +287,22 @@ class Event < ActiveRecord::Base
end end
def note_target_id def note_target_id
if note_commit? if commit_note?
target.commit_id target.commit_id
else else
target.noteable_id.to_s target.noteable_id.to_s
end end
end end
def note_target_iid def note_target_reference
if note_target.respond_to?(:iid) return unless note_target
note_target.iid
# Commit#to_reference returns the full SHA, but we want the short one here
if commit_note?
note_target.short_id
else else
note_target_id note_target.to_reference
end.to_s end
end end
def note_target_type def note_target_type
......
class LegacyDiffNote < Note
serialize :st_diff
validates :line_code, presence: true, line_code: true
before_create :set_diff
class << self
def build_discussion_id(noteable_type, noteable_id, line_code, active = true)
[super(noteable_type, noteable_id), line_code, active].join("-")
end
end
def diff_note?
true
end
def legacy_diff_note?
true
end
def discussion_id
@discussion_id ||= self.class.build_discussion_id(noteable_type, noteable_id || commit_id, line_code, active?)
end
def diff_file_hash
line_code.split('_')[0] if line_code
end
def diff_old_line
line_code.split('_')[1].to_i if line_code
end
def diff_new_line
line_code.split('_')[2].to_i if line_code
end
def diff
@diff ||= Gitlab::Git::Diff.new(st_diff) if st_diff.respond_to?(:map)
end
def diff_file_path
diff.new_path.presence || diff.old_path
end
def diff_lines
@diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
end
def diff_line
@diff_line ||= diff_lines.find { |line| generate_line_code(line) == self.line_code }
end
def diff_line_text
diff_line.try(:text)
end
def diff_line_type
diff_line.try(:type)
end
def highlighted_diff_lines
Gitlab::Diff::Highlight.new(diff_lines).highlight
end
def truncated_diff_lines
max_number_of_lines = 16
prev_match_line = nil
prev_lines = []
highlighted_diff_lines.each do |line|
if line.type == "match"
prev_lines.clear
prev_match_line = line
else
prev_lines << line
break if generate_line_code(line) == self.line_code
prev_lines.shift if prev_lines.length >= max_number_of_lines
end
end
prev_lines
end
# Check if this note is part of an "active" discussion
#
# This will always return true for anything except MergeRequest noteables,
# which have special logic.
#
# If the note's current diff cannot be matched in the MergeRequest's current
# diff, it's considered inactive.
def active?
return @active if defined?(@active)
return true if for_commit?
return true unless self.diff
return false unless noteable
noteable_diff = find_noteable_diff
if noteable_diff
parsed_lines = Gitlab::Diff::Parser.new.parse(noteable_diff.diff.each_line)
@active = parsed_lines.any? { |line_obj| line_obj.text == diff_line_text }
else
@active = false
end
@active
end
private
def find_diff
return nil unless noteable
return @diff if defined?(@diff)
@diff = noteable.diffs(Commit.max_diff_options).find do |d|
d.new_path && Digest::SHA1.hexdigest(d.new_path) == diff_file_hash
end
end
def set_diff
# First lets find notes with same diff
# before iterating over all mr diffs
diff = diff_for_line_code unless for_merge_request?
diff ||= find_diff
self.st_diff = diff.to_hash if diff
end
def diff_for_line_code
attributes = {
noteable_type: noteable_type,
line_code: line_code
}
if for_commit?
attributes[:commit_id] = commit_id
else
attributes[:noteable_id] = noteable_id
end
self.class.where(attributes).last.try(:diff)
end
def generate_line_code(line)
Gitlab::Diff::LineCode.generate(diff_file_path, line.new_pos, line.old_pos)
end
# Find the diff on noteable that matches our own
def find_noteable_diff
diffs = noteable.diffs(Commit.max_diff_options)
diffs.find { |d| d.new_path == self.diff.new_path }
end
end
...@@ -26,6 +26,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -26,6 +26,10 @@ class MergeRequest < ActiveRecord::Base
# when creating new merge request # when creating new merge request
attr_accessor :can_be_created, :compare_commits, :compare attr_accessor :can_be_created, :compare_commits, :compare
# Temporary fields to store target_sha, and base_sha to
# compare when importing pull requests from GitHub
attr_accessor :base_target_sha, :head_source_sha
state_machine :state, initial: :opened do state_machine :state, initial: :opened do
event :close do event :close do
transition [:reopened, :opened] => :closed transition [:reopened, :opened] => :closed
...@@ -490,10 +494,14 @@ class MergeRequest < ActiveRecord::Base ...@@ -490,10 +494,14 @@ class MergeRequest < ActiveRecord::Base
end end
def target_sha def target_sha
@target_sha ||= target_project.repository.commit(target_branch).try(:sha) return @base_target_sha if defined?(@base_target_sha)
target_project.repository.commit(target_branch).try(:sha)
end end
def source_sha def source_sha
return @head_source_sha if defined?(@head_source_sha)
last_commit.try(:sha) || source_tip.try(:sha) last_commit.try(:sha) || source_tip.try(:sha)
end end
......
...@@ -6,7 +6,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -6,7 +6,7 @@ class MergeRequestDiff < ActiveRecord::Base
belongs_to :merge_request belongs_to :merge_request
delegate :target_branch, :source_branch, to: :merge_request, prefix: nil delegate :head_source_sha, :target_branch, :source_branch, to: :merge_request, prefix: nil
state_machine :state, initial: :empty do state_machine :state, initial: :empty do
state :collected state :collected
...@@ -38,8 +38,8 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -38,8 +38,8 @@ class MergeRequestDiff < ActiveRecord::Base
@diffs_no_whitespace ||= begin @diffs_no_whitespace ||= begin
compare = Gitlab::Git::Compare.new( compare = Gitlab::Git::Compare.new(
self.repository.raw_repository, self.repository.raw_repository,
self.target_branch, self.base,
self.source_sha, self.head,
) )
compare.diffs(options) compare.diffs(options)
end end
...@@ -144,7 +144,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -144,7 +144,7 @@ class MergeRequestDiff < ActiveRecord::Base
self.st_diffs = new_diffs self.st_diffs = new_diffs
self.base_commit_sha = self.repository.merge_base(self.source_sha, self.target_branch) self.base_commit_sha = self.repository.merge_base(self.head, self.base)
self.save self.save
end end
...@@ -160,10 +160,24 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -160,10 +160,24 @@ class MergeRequestDiff < ActiveRecord::Base
end end
def source_sha def source_sha
return head_source_sha if head_source_sha.present?
source_commit = merge_request.source_project.commit(source_branch) source_commit = merge_request.source_project.commit(source_branch)
source_commit.try(:sha) source_commit.try(:sha)
end end
def target_sha
merge_request.target_sha
end
def base
self.target_sha || self.target_branch
end
def head
self.source_sha
end
def compare def compare
@compare ||= @compare ||=
begin begin
...@@ -172,8 +186,8 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -172,8 +186,8 @@ class MergeRequestDiff < ActiveRecord::Base
Gitlab::Git::Compare.new( Gitlab::Git::Compare.new(
self.repository.raw_repository, self.repository.raw_repository,
self.target_branch, self.base,
self.source_sha self.head
) )
end end
end end
......
...@@ -67,8 +67,18 @@ class Milestone < ActiveRecord::Base ...@@ -67,8 +67,18 @@ class Milestone < ActiveRecord::Base
@link_reference_pattern ||= super("milestones", /(?<milestone>\d+)/) @link_reference_pattern ||= super("milestones", /(?<milestone>\d+)/)
end end
def self.upcoming def self.upcoming_ids_by_projects(projects)
self.where('due_date > ?', Time.now).reorder(due_date: :asc).first rel = unscoped.of_projects(projects).active.where('due_date > ?', Time.now)
if Gitlab::Database.postgresql?
rel.order(:project_id, :due_date).select('DISTINCT ON (project_id) id')
else
rel.
group(:project_id).
having('due_date = MIN(due_date)').
pluck(:id, :project_id, :due_date).
map(&:first)
end
end end
def to_reference(from_project = nil) def to_reference(from_project = nil)
......
require 'carrierwave/orm/activerecord'
class Note < ActiveRecord::Base class Note < ActiveRecord::Base
extend ActiveModel::Naming
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
include Participable include Participable
include Mentionable include Mentionable
...@@ -20,14 +19,13 @@ class Note < ActiveRecord::Base ...@@ -20,14 +19,13 @@ class Note < ActiveRecord::Base
delegate :gfm_reference, :local_reference, to: :noteable delegate :gfm_reference, :local_reference, to: :noteable
delegate :name, to: :project, prefix: true delegate :name, to: :project, prefix: true
delegate :name, :email, to: :author, prefix: true delegate :name, :email, to: :author, prefix: true
delegate :title, to: :noteable, allow_nil: true
before_validation :set_award! before_validation :set_award!
before_validation :clear_blank_line_code!
validates :note, :project, presence: true validates :note, :project, presence: true
validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, if: ->(n) { n.is_award } validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, if: ->(n) { n.is_award }
validates :note, inclusion: { in: Emoji.emojis_names }, if: ->(n) { n.is_award } validates :note, inclusion: { in: Emoji.emojis_names }, if: ->(n) { n.is_award }
validates :line_code, line_code: true, allow_blank: true
# Attachments are deprecated and are handled by Markdown uploader # Attachments are deprecated and are handled by Markdown uploader
validates :attachment, file_size: { maximum: :max_attachment_size } validates :attachment, file_size: { maximum: :max_attachment_size }
...@@ -41,8 +39,6 @@ class Note < ActiveRecord::Base ...@@ -41,8 +39,6 @@ class Note < ActiveRecord::Base
scope :awards, ->{ where(is_award: true) } scope :awards, ->{ where(is_award: true) }
scope :nonawards, ->{ where(is_award: false) } scope :nonawards, ->{ where(is_award: false) }
scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) } scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
scope :inline, ->{ where("line_code IS NOT NULL") }
scope :not_inline, ->{ where(line_code: nil) }
scope :system, ->{ where(system: true) } scope :system, ->{ where(system: true) }
scope :user, ->{ where(system: false) } scope :user, ->{ where(system: false) }
scope :common, ->{ where(noteable_type: ["", nil]) } scope :common, ->{ where(noteable_type: ["", nil]) }
...@@ -50,38 +46,31 @@ class Note < ActiveRecord::Base ...@@ -50,38 +46,31 @@ class Note < ActiveRecord::Base
scope :inc_author_project, ->{ includes(:project, :author) } scope :inc_author_project, ->{ includes(:project, :author) }
scope :inc_author, ->{ includes(:author) } scope :inc_author, ->{ includes(:author) }
scope :legacy_diff_notes, ->{ where(type: 'LegacyDiffNote') }
scope :non_diff_notes, ->{ where(type: ['Note', nil]) }
scope :with_associations, -> do scope :with_associations, -> do
includes(:author, :noteable, :updated_by, includes(:author, :noteable, :updated_by,
project: [:project_members, { group: [:group_members] }]) project: [:project_members, { group: [:group_members] }])
end end
serialize :st_diff before_validation :clear_blank_line_code!
before_create :set_diff, if: ->(n) { n.line_code.present? }
class << self class << self
def discussions_from_notes(notes) def model_name
discussion_ids = [] ActiveModel::Name.new(self, nil, 'note')
discussions = []
notes.each do |note|
next if discussion_ids.include?(note.discussion_id)
# don't group notes for the main target
if !note.for_diff_line? && note.for_merge_request?
discussions << [note]
else
discussions << notes.select do |other_note|
note.discussion_id == other_note.discussion_id
end
discussion_ids << note.discussion_id
end end
def build_discussion_id(noteable_type, noteable_id)
[:discussion, noteable_type.try(:underscore), noteable_id].join("-")
end end
discussions def discussions
all.group_by(&:discussion_id).values
end end
def build_discussion_id(type, id, line_code) def grouped_diff_notes
[:discussion, type.try(:underscore), id, line_code].join("-").to_sym legacy_diff_notes.select(&:active?).sort_by(&:created_at).group_by(&:line_code)
end end
# Searches for notes matching the given query. # Searches for notes matching the given query.
...@@ -116,167 +105,39 @@ class Note < ActiveRecord::Base ...@@ -116,167 +105,39 @@ class Note < ActiveRecord::Base
system && SystemNoteService.cross_reference?(note) system && SystemNoteService.cross_reference?(note)
end end
def max_attachment_size def diff_note?
current_application_settings.max_attachment_size.megabytes.to_i false
end
def find_diff
return nil unless noteable
return @diff if defined?(@diff)
# Don't use ||= because nil is a valid value for @diff
@diff = noteable.diffs(Commit.max_diff_options).find do |d|
Digest::SHA1.hexdigest(d.new_path) == diff_file_index if d.new_path
end
end
def hook_attrs
attributes
end
def set_diff
# First lets find notes with same diff
# before iterating over all mr diffs
diff = diff_for_line_code unless for_merge_request?
diff ||= find_diff
self.st_diff = diff.to_hash if diff
end end
def diff def legacy_diff_note?
@diff ||= Gitlab::Git::Diff.new(st_diff) if st_diff.respond_to?(:map) false
end end
def diff_for_line_code
Note.where(noteable_id: noteable_id, noteable_type: noteable_type, line_code: line_code).last.try(:diff)
end
# Check if this note is part of an "active" discussion
#
# This will always return true for anything except MergeRequest noteables,
# which have special logic.
#
# If the note's current diff cannot be matched in the MergeRequest's current
# diff, it's considered inactive.
def active? def active?
return true unless self.diff true
return false unless noteable
return @active if defined?(@active)
noteable_diff = find_noteable_diff
if noteable_diff
parsed_lines = Gitlab::Diff::Parser.new.parse(noteable_diff.diff.each_line)
@active = parsed_lines.any? { |line_obj| line_obj.text == diff_line }
else
@active = false
end
@active
end
def diff_file_index
line_code.split('_')[0] if line_code
end
def diff_file_name
diff.new_path if diff
end
def file_path
if diff.new_path.present?
diff.new_path
elsif diff.old_path.present?
diff.old_path
end
end
def diff_old_line
line_code.split('_')[1].to_i if line_code
end
def diff_new_line
line_code.split('_')[2].to_i if line_code
end
def generate_line_code(line)
Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos)
end
def diff_line
return @diff_line if @diff_line
if diff
diff_lines.each do |line|
if generate_line_code(line) == self.line_code
@diff_line = line.text
end
end
end end
@diff_line def discussion_id
end @discussion_id ||=
if for_merge_request?
def diff_line_type [:discussion, :note, id].join("-")
return @diff_line_type if @diff_line_type
if diff
diff_lines.each do |line|
if generate_line_code(line) == self.line_code
@diff_line_type = line.type
end
end
end
@diff_line_type
end
def truncated_diff_lines
max_number_of_lines = 16
prev_match_line = nil
prev_lines = []
highlighted_diff_lines.each do |line|
if line.type == "match"
prev_lines.clear
prev_match_line = line
else else
prev_lines << line self.class.build_discussion_id(noteable_type, noteable_id || commit_id)
break if generate_line_code(line) == self.line_code
prev_lines.shift if prev_lines.length >= max_number_of_lines
end
end
prev_lines
end end
def diff_lines
@diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
end end
def highlighted_diff_lines def max_attachment_size
Gitlab::Diff::Highlight.new(diff_lines).highlight current_application_settings.max_attachment_size.megabytes.to_i
end end
def discussion_id def hook_attrs
@discussion_id ||= Note.build_discussion_id(noteable_type, noteable_id || commit_id, line_code) attributes
end end
def for_commit? def for_commit?
noteable_type == "Commit" noteable_type == "Commit"
end end
def for_commit_diff_line?
for_commit? && for_diff_line?
end
def for_diff_line?
line_code.present?
end
def for_issue? def for_issue?
noteable_type == "Issue" noteable_type == "Issue"
end end
...@@ -285,10 +146,6 @@ class Note < ActiveRecord::Base ...@@ -285,10 +146,6 @@ class Note < ActiveRecord::Base
noteable_type == "MergeRequest" noteable_type == "MergeRequest"
end end
def for_merge_request_diff_line?
for_merge_request? && for_diff_line?
end
def for_snippet? def for_snippet?
noteable_type == "Snippet" noteable_type == "Snippet"
end end
...@@ -361,14 +218,8 @@ class Note < ActiveRecord::Base ...@@ -361,14 +218,8 @@ class Note < ActiveRecord::Base
self.line_code = nil if self.line_code.blank? self.line_code = nil if self.line_code.blank?
end end
# Find the diff on noteable that matches our own
def find_noteable_diff
diffs = noteable.diffs(Commit.max_diff_options)
diffs.find { |d| d.new_path == self.diff.new_path }
end
def awards_supported? def awards_supported?
(for_issue? || for_merge_request?) && !for_diff_line? (for_issue? || for_merge_request?) && !diff_note?
end end
def contains_emoji_only? def contains_emoji_only?
......
...@@ -50,6 +50,8 @@ class Project < ActiveRecord::Base ...@@ -50,6 +50,8 @@ class Project < ActiveRecord::Base
attr_accessor :new_default_branch attr_accessor :new_default_branch
attr_accessor :old_path_with_namespace attr_accessor :old_path_with_namespace
alias_attribute :title, :name
# Relations # Relations
belongs_to :creator, foreign_key: 'creator_id', class_name: 'User' belongs_to :creator, foreign_key: 'creator_id', class_name: 'User'
belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id' belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id'
......
...@@ -40,7 +40,7 @@ class ProjectWiki ...@@ -40,7 +40,7 @@ class ProjectWiki
end end
def wiki_base_path def wiki_base_path
["/", @project.path_with_namespace, "/wikis"].join('') [Gitlab.config.gitlab.relative_url_root, "/", @project.path_with_namespace, "/wikis"].join('')
end end
# Returns the Gollum::Wiki object. # Returns the Gollum::Wiki object.
......
...@@ -195,6 +195,10 @@ class Repository ...@@ -195,6 +195,10 @@ class Repository
cache.fetch(:branch_names) { branches.map(&:name) } cache.fetch(:branch_names) { branches.map(&:name) }
end end
def branch_exists?(branch_name)
branch_names.include?(branch_name)
end
def tag_names def tag_names
cache.fetch(:tag_names) { raw_repository.tag_names } cache.fetch(:tag_names) { raw_repository.tag_names }
end end
......
...@@ -112,6 +112,7 @@ class User < ActiveRecord::Base ...@@ -112,6 +112,7 @@ class User < ActiveRecord::Base
before_save :ensure_external_user_rights before_save :ensure_external_user_rights
after_save :ensure_namespace_correct after_save :ensure_namespace_correct
after_initialize :set_projects_limit after_initialize :set_projects_limit
before_create :check_confirmation_email
after_create :post_create_hook after_create :post_create_hook
after_destroy :post_destroy_hook after_destroy :post_destroy_hook
...@@ -307,6 +308,10 @@ class User < ActiveRecord::Base ...@@ -307,6 +308,10 @@ class User < ActiveRecord::Base
@reset_token @reset_token
end end
def check_confirmation_email
skip_confirmation! unless current_application_settings.send_user_confirmation_email
end
def recently_sent_password_reset? def recently_sent_password_reset?
reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
end end
......
...@@ -103,6 +103,12 @@ ...@@ -103,6 +103,12 @@
= f.label :signup_enabled do = f.label :signup_enabled do
= f.check_box :signup_enabled = f.check_box :signup_enabled
Sign-up enabled Sign-up enabled
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :send_user_confirmation_email do
= f.check_box :send_user_confirmation_email
Send confirmation email on sign-up
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
.checkbox .checkbox
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
= event_action_name(event) = event_action_name(event)
- if event.target - if event.target
%strong= link_to event.target.reference_link_text, [event.project.namespace.becomes(Namespace), event.project, event.target] %strong= link_to event.target.reference_link_text, [event.project.namespace.becomes(Namespace), event.project, event.target], title: event.target_title
= event_preposition(event) = event_preposition(event)
......
- if @note.diff_file_name - if @note.legacy_diff_note?
%p.details %p.details
New comment on diff for New comment on diff for
= link_to @note.diff_file_name, @target_url = link_to @note.diff_file_path, @target_url
\: \:
= render 'note_message' = render 'note_message'
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
= link_text = link_text
- else - else
= link_to "", "##{line_code}", id: line_code, data: { linenumber: link_text } = link_to "", "##{line_code}", id: line_code, data: { linenumber: link_text }
- if @comments_allowed && can?(current_user, :create_note, @project) - if !@diff_notes_disabled && can?(current_user, :create_note, @project)
= link_to_new_diff_note(line_code) = link_to_new_diff_note(line_code)
%td.new_line.diff-line-num{ class: type, data: { linenumber: line.new_pos } } %td.new_line.diff-line-num{ class: type, data: { linenumber: line.new_pos } }
- link_text = type == "old" ? "&nbsp;".html_safe : line.new_pos - link_text = type == "old" ? "&nbsp;".html_safe : line.new_pos
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
- else - else
%td.old_line.diff-line-num{id: left[:line_code], class: "#{left[:type]} #{'empty-cell' if !left[:number]}"} %td.old_line.diff-line-num{id: left[:line_code], class: "#{left[:type]} #{'empty-cell' if !left[:number]}"}
= link_to raw(left[:number]), "##{left[:line_code]}", id: left[:line_code] = link_to raw(left[:number]), "##{left[:line_code]}", id: left[:line_code]
- if @comments_allowed && can?(current_user, :create_note, @project) - if !@diff_notes_disabled && can?(current_user, :create_note, @project)
= link_to_new_diff_note(left[:line_code], 'old') = link_to_new_diff_note(left[:line_code], 'old')
%td.line_content{class: "parallel noteable_line #{left[:type]} #{left[:line_code]} #{'empty-cell' if left[:text].empty?}", data: { line_code: left[:line_code] }}= diff_line_content(left[:text]) %td.line_content{class: "parallel noteable_line #{left[:type]} #{left[:line_code]} #{'empty-cell' if left[:text].empty?}", data: { line_code: left[:line_code] }}= diff_line_content(left[:text])
...@@ -29,14 +29,14 @@ ...@@ -29,14 +29,14 @@
%td.new_line.diff-line-num{id: new_line_code, class: "#{new_line_class} #{'empty-cell' if !right[:number]}", data: { linenumber: right[:number] }} %td.new_line.diff-line-num{id: new_line_code, class: "#{new_line_class} #{'empty-cell' if !right[:number]}", data: { linenumber: right[:number] }}
= link_to raw(right[:number]), "##{new_line_code}", id: new_line_code = link_to raw(right[:number]), "##{new_line_code}", id: new_line_code
- if @comments_allowed && can?(current_user, :create_note, @project) - if !@diff_notes_disabled && can?(current_user, :create_note, @project)
= link_to_new_diff_note(right[:line_code], 'new') = link_to_new_diff_note(new_line_code, 'new')
%td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code} #{'empty-cell' if right[:text].empty?}", data: { line_code: new_line_code }}= diff_line_content(right[:text]) %td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code} #{'empty-cell' if right[:text].empty?}", data: { line_code: new_line_code }}= diff_line_content(right[:text])
- if @reply_allowed - unless @diff_notes_disabled
- comments_left, comments_right = organize_comments(left[:type], right[:type], left[:line_code], right[:line_code]) - notes_left, notes_right = organize_comments(left, right)
- if comments_left.present? || comments_right.present? - if notes_left.present? || notes_right.present?
= render "projects/notes/diff_notes_with_reply_parallel", notes_left: comments_left, notes_right: comments_right = render "projects/notes/diff_notes_with_reply_parallel", notes_left: notes_left, notes_right: notes_right
- if diff_file.diff.diff.blank? && diff_file.mode_changed? - if diff_file.diff.diff.blank? && diff_file.mode_changed?
.file-mode-changed .file-mode-changed
......
...@@ -6,16 +6,15 @@ ...@@ -6,16 +6,15 @@
%table.text-file.code.js-syntax-highlight{ class: too_big ? 'hide' : '' } %table.text-file.code.js-syntax-highlight{ class: too_big ? 'hide' : '' }
- last_line = 0 - last_line = 0
- raw_diff_lines = diff_file.diff_lines.to_a
- diff_file.highlighted_diff_lines.each_with_index do |line, index| - diff_file.highlighted_diff_lines.each_with_index do |line, index|
- line_code = generate_line_code(diff_file.file_path, line) - line_code = generate_line_code(diff_file.file_path, line)
- last_line = line.new_pos - last_line = line.new_pos
= render "projects/diffs/line", {line: line, diff_file: diff_file, line_code: line_code} = render "projects/diffs/line", {line: line, diff_file: diff_file, line_code: line_code}
- if @reply_allowed - unless @diff_notes_disabled
- comments = @line_notes.select { |n| n.line_code == line_code && n.active? }.sort_by(&:created_at) - diff_notes = @grouped_diff_notes[line_code]
- unless comments.empty? - if diff_notes
= render "projects/notes/diff_notes_with_reply", notes: comments, line: raw_diff_lines[index].text = render "projects/notes/diff_notes_with_reply", notes: diff_notes
- if last_line > 0 - if last_line > 0
= render "projects/diffs/match_line", { line: "", = render "projects/diffs/match_line", { line: "",
......
- note = notes.first # example note - note = notes.first
-# Check if line want not changed since comment was left %tr.notes_holder
- if !defined?(line) || line == note.diff_line
%tr.notes_holder
%td.notes_line{ colspan: 2 } %td.notes_line{ colspan: 2 }
%td.notes_content %td.notes_content
%ul.notes{ data: { discussion_id: note.discussion_id } } %ul.notes{ data: { discussion_id: note.discussion_id } }
= render notes = render partial: "projects/notes/note", collection: notes, as: :note
.discussion-reply-holder .discussion-reply-holder
= link_to_reply_diff(note) = link_to_reply_discussion(note)
- note1 = notes_left.present? ? notes_left.first : nil - note_left = notes_left.present? ? notes_left.first : nil
- note2 = notes_right.present? ? notes_right.first : nil - note_right = notes_right.present? ? notes_right.first : nil
%tr.notes_holder %tr.notes_holder
- if note1 - if note_left
%td.notes_line.old %td.notes_line.old
%td.notes_content.parallel.old %td.notes_content.parallel.old
%ul.notes{ data: { discussion_id: note1.discussion_id } } %ul.notes{ data: { discussion_id: note_left.discussion_id } }
= render notes_left = render partial: "projects/notes/note", collection: notes_left, as: :note
.discussion-reply-holder .discussion-reply-holder
= link_to_reply_diff(note1, 'old') = link_to_reply_discussion(note_left, 'old')
- else - else
%td.notes_line.old= "" %td.notes_line.old= ""
%td.notes_content.parallel.old= "" %td.notes_content.parallel.old= ""
- if note2 - if note_right
%td.notes_line.new %td.notes_line.new
%td.notes_content.parallel.new %td.notes_content.parallel.new
%ul.notes{ data: { discussion_id: note2.discussion_id } } %ul.notes{ data: { discussion_id: note_right.discussion_id } }
= render notes_right = render partial: "projects/notes/note", collection: notes_right, as: :note
.discussion-reply-holder .discussion-reply-holder
= link_to_reply_diff(note2, 'new') = link_to_reply_discussion(note_right, 'new')
- else - else
%td.notes_line.new= "" %td.notes_line.new= ""
%td.notes_content.parallel.new= "" %td.notes_content.parallel.new= ""
- note = discussion_notes.first - note = discussion_notes.first
- expanded = !note.diff_note? || note.active?
%li.note.note-discussion.timeline-entry %li.note.note-discussion.timeline-entry
.timeline-entry-inner .timeline-entry-inner
.timeline-icon .timeline-icon
= link_to user_path(note.author) do = link_to user_path(note.author) do
= image_tag avatar_icon(note.author_email), class: "avatar s40" = image_tag avatar_icon(note.author), class: "avatar s40"
.timeline-content .timeline-content
- if note.for_merge_request? .discussion.js-toggle-container{ class: note.discussion_id }
- (active_notes, outdated_notes) = discussion_notes.partition(&:active?) .discussion-header
= render "projects/notes/discussions/active", discussion_notes: active_notes if active_notes.length > 0 = link_to_member(@project, note.author, avatar: false)
= render "projects/notes/discussions/outdated", discussion_notes: outdated_notes if outdated_notes.length > 0
.inline.discussion-headline-light
= note.author.to_reference
started a discussion on
- if note.for_commit?
- commit = note.noteable
- if commit
commit
= link_to commit.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code), class: 'monospace'
- else - else
= render "projects/notes/discussions/commit", discussion_notes: discussion_notes a deleted commit
- else
- if note.active?
= link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do
the diff
- else
an outdated diff
= time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "note-created-ago")
.discussion-actions
= link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
- if expanded
= icon("chevron-up")
- else
= icon("chevron-down")
Toggle discussion
.discussion-body.js-toggle-content{ class: ("hide" unless expanded) }
- if note.diff_note?
= render "projects/notes/discussions/diff_with_notes", discussion_notes: discussion_notes
- else
= render "projects/notes/discussions/notes", discussion_notes: discussion_notes
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
= f.hidden_field :line_code = f.hidden_field :line_code
= f.hidden_field :noteable_id = f.hidden_field :noteable_id
= f.hidden_field :noteable_type = f.hidden_field :noteable_type
= f.hidden_field :type
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
= render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text', placeholder: "Write a comment or drag your files here..." = render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text', placeholder: "Write a comment or drag your files here..."
......
- return unless note.author
- return if note.cross_reference_not_visible_for?(current_user)
- note_editable = note_editable?(note) - note_editable = note_editable?(note)
%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable} } %li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable} }
.timeline-entry-inner .timeline-entry-inner
.timeline-icon .timeline-icon
%a{href: user_path(note.author)} %a{href: user_path(note.author)}
...@@ -8,8 +11,8 @@ ...@@ -8,8 +11,8 @@
.note-header .note-header
= link_to_member(note.project, note.author, avatar: false) = link_to_member(note.project, note.author, avatar: false)
.inline.note-headline-light .inline.note-headline-light
= "#{note.author.to_reference}" = note.author.to_reference
- if !note.system - unless note.system
commented commented
%a{ href: "##{dom_id(note)}" } %a{ href: "##{dom_id(note)}" }
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago') = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
......
...@@ -2,14 +2,9 @@ ...@@ -2,14 +2,9 @@
- @discussions.each do |discussion_notes| - @discussions.each do |discussion_notes|
- note = discussion_notes.first - note = discussion_notes.first
- if note_for_main_target?(note) - if note_for_main_target?(note)
- next if note.cross_reference_not_visible_for?(current_user) = render partial: "projects/notes/note", object: note, as: :note
= render discussion_notes
- else - else
= render 'projects/notes/discussion', discussion_notes: discussion_notes = render 'projects/notes/discussion', discussion_notes: discussion_notes
- else - else
- @notes.each do |note| - @notes.each do |note|
- next unless note.author = render partial: "projects/notes/note", object: note, as: :note
- next if note.cross_reference_not_visible_for?(current_user)
= render note
- note = discussion_notes.first
.discussion.js-toggle-container{ class: note.discussion_id }
.discussion-header
= link_to_member(@project, note.author, avatar: false)
.inline.discussion-headline-light
= "#{note.author.to_reference} started a discussion"
= link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do
on the diff
= time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
.discussion-actions
= link_to "#", class: "discussion-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-up
Show/hide discussion
.discussion-body.js-toggle-content
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
- note = discussion_notes.first
- commit = note.noteable
- commit_description = commit ? 'commit' : 'a deleted commit'
.discussion.js-toggle-container{ class: note.discussion_id }
.discussion-header
= link_to_member(@project, note.author, avatar: false)
.inline.discussion-headline-light
= "#{note.author.to_reference} started a discussion on #{commit_description}"
- if commit
= link_to(commit.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable), class: 'monospace')
= time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
.discussion-actions
= link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-up
Show/hide discussion
.discussion-body.js-toggle-content
- if note.for_diff_line?
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
- else
.panel.panel-default
.notes{ data: { discussion_id: discussion_notes.first.discussion_id } }
%ul.notes.timeline
= render discussion_notes
.discussion-reply-holder
= link_to_reply_diff(discussion_notes.first)
- diff = note.diff
- if diff
.diff-file
.diff-header
%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.code.js-syntax-highlight
%table
- note.truncated_diff_lines.each do |line|
- type = line.type
- line_code = generate_line_code(note.file_path, line)
%tr.line_holder{ id: line_code, class: "#{type}" }
- if type == "match"
%td.old_line.diff-line-num= "..."
%td.new_line.diff-line-num= "..."
%td.line_content.match= line.text
- else
%td.old_line.diff-line-num{ data: { linenumber: type == "new" ? "&nbsp;".html_safe : line.old_pos } }
%td.new_line.diff-line-num{ data: { linenumber: type == "old" ? "&nbsp;".html_safe : line.new_pos } }
%td.line_content{ class: ['noteable_line', type, line_code], line_code: line_code }= diff_line_content(line.text, type)
- if line_code == note.line_code
= render "projects/notes/diff_notes_with_reply", notes: discussion_notes
- note = discussion_notes.first
- diff = note.diff
- return unless diff
.diff-file
.diff-header
%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.code.js-syntax-highlight
%table
- note.truncated_diff_lines.each do |line|
- type = line.type
- line_code = generate_line_code(note.diff_file_path, line)
%tr.line_holder{ id: line_code, class: "#{type}" }
- if type == "match"
%td.old_line.diff-line-num= "..."
%td.new_line.diff-line-num= "..."
%td.line_content.match= line.text
- else
%td.old_line.diff-line-num{ data: { linenumber: type == "new" ? "&nbsp;".html_safe : line.old_pos } }
%td.new_line.diff-line-num{ data: { linenumber: type == "old" ? "&nbsp;".html_safe : line.new_pos } }
%td.line_content{ class: ['noteable_line', type, line_code], line_code: line_code }= diff_line_content(line.text, type)
- if line_code == note.line_code
= render "projects/notes/diff_notes_with_reply", notes: discussion_notes
- note = discussion_notes.first
.panel.panel-default
.notes{ data: { discussion_id: note.discussion_id } }
%ul.notes.timeline
= render partial: "projects/notes/note", collection: discussion_notes, as: :note
.discussion-reply-holder
= link_to_reply_discussion(note)
- note = discussion_notes.first
.discussion.js-toggle-container{ class: note.discussion_id }
.discussion-header
= link_to_member(@project, note.author, avatar: false)
.inline.discussion-headline-light
= "#{note.author.to_reference} started a discussion"
on the outdated diff
= time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
.discussion-actions
= link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-down
Show/hide discussion
.discussion-body.js-toggle-content.hide
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
class AddTypeToNotes < ActiveRecord::Migration
def change
add_column :notes, :type, :string
end
end
class SetTypeOnLegacyDiffNotes < ActiveRecord::Migration
def change
execute "UPDATE notes SET type = 'LegacyDiffNote' WHERE line_code IS NOT NULL"
end
end
class AddSendUserConfirmationEmailToApplicationSettings < ActiveRecord::Migration
def up
add_column :application_settings, :send_user_confirmation_email, :boolean, default: false
#Sets confirmation email to true by default on existing installations.
execute "UPDATE application_settings SET send_user_confirmation_email=true"
end
def down
remove_column :application_settings, :send_user_confirmation_email
end
end
...@@ -636,6 +636,7 @@ ActiveRecord::Schema.define(version: 20160509201028) do ...@@ -636,6 +636,7 @@ ActiveRecord::Schema.define(version: 20160509201028) do
t.text "st_diff" t.text "st_diff"
t.integer "updated_by_id" t.integer "updated_by_id"
t.boolean "is_award", default: false, null: false t.boolean "is_award", default: false, null: false
t.string "type"
end end
add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree
......
...@@ -58,4 +58,4 @@ to the naming scheme `GITLAB_#{name in 1_settings.rb in upper case}`. ...@@ -58,4 +58,4 @@ to the naming scheme `GITLAB_#{name in 1_settings.rb in upper case}`.
It's possible to preconfigure the GitLab docker image by adding the environment It's possible to preconfigure the GitLab docker image by adding the environment
variable `GITLAB_OMNIBUS_CONFIG` to the `docker run` command. variable `GITLAB_OMNIBUS_CONFIG` to the `docker run` command.
For more information see the ['preconfigure-docker-container' section in the Omnibus documentation](http://doc.gitlab.com/omnibus/docker/#preconfigure-docker-container). For more information see the ['preconfigure-docker-container' section in the Omnibus documentation](http://docs.gitlab.com/omnibus/docker/#preconfigure-docker-container).
...@@ -60,4 +60,4 @@ Read more on high-availability configuration: ...@@ -60,4 +60,4 @@ Read more on high-availability configuration:
configure custom domains with custom SSL, which would not be possible configure custom domains with custom SSL, which would not be possible
if SSL was terminated at the load balancer. if SSL was terminated at the load balancer.
[gitlab-pages]: http://doc.gitlab.com/ee/pages/administration.html [gitlab-pages]: http://docs.gitlab.com/ee/pages/administration.html
...@@ -113,4 +113,4 @@ Read more on high-availability configuration: ...@@ -113,4 +113,4 @@ Read more on high-availability configuration:
1. [Configure the GitLab application servers](gitlab.md) 1. [Configure the GitLab application servers](gitlab.md)
1. [Configure the load balancers](load_balancer.md) 1. [Configure the load balancers](load_balancer.md)
[udp-log-shipping]: http://doc.gitlab.com/omnibus/settings/logs.html#udp-log-shipping-gitlab-enterprise-edition-only "UDP log shipping" [udp-log-shipping]: http://docs.gitlab.com/omnibus/settings/logs.html#udp-log-shipping-gitlab-enterprise-edition-only "UDP log shipping"
...@@ -491,7 +491,7 @@ Jira issue tracker ...@@ -491,7 +491,7 @@ Jira issue tracker
Set JIRA service for a project. Set JIRA service for a project.
> Setting `project_url`, `issues_url` and `new_issue_url` will allow a user to easily navigate to the Jira issue tracker. See the [integration doc](http://doc.gitlab.com/ce/integration/external-issue-tracker.html) for details. Support for referencing commits and automatic closing of Jira issues directly from GitLab is [available in GitLab EE.](http://doc.gitlab.com/ee/integration/jira.html) > Setting `project_url`, `issues_url` and `new_issue_url` will allow a user to easily navigate to the Jira issue tracker. See the [integration doc](http://docs.gitlab.com/ce/integration/external-issue-tracker.html) for details. Support for referencing commits and automatic closing of Jira issues directly from GitLab is [available in GitLab EE.](http://docs.gitlab.com/ee/integration/jira.html)
``` ```
PUT /projects/:id/services/jira PUT /projects/:id/services/jira
......
...@@ -127,7 +127,7 @@ Inside the document: ...@@ -127,7 +127,7 @@ Inside the document:
``` ```
If the document you are editing resides in a place other than the GitLab CE/EE If the document you are editing resides in a place other than the GitLab CE/EE
`doc/` directory, instead of the relative link, use the full path: `doc/` directory, instead of the relative link, use the full path:
`http://doc.gitlab.com/ce/administration/restart_gitlab.html`. `http://docs.gitlab.com/ce/administration/restart_gitlab.html`.
Replace `reconfigure` with `restart` where appropriate. Replace `reconfigure` with `restart` where appropriate.
## Installation guide ## Installation guide
...@@ -266,5 +266,5 @@ curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -d "restricted_signup_domai ...@@ -266,5 +266,5 @@ curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -d "restricted_signup_domai
[cURL]: http://curl.haxx.se/ "cURL website" [cURL]: http://curl.haxx.se/ "cURL website"
[single spaces]: http://www.slate.com/articles/technology/technology/2011/01/space_invaders.html [single spaces]: http://www.slate.com/articles/technology/technology/2011/01/space_invaders.html
[gfm]: http://doc.gitlab.com/ce/markdown/markdown.html#newlines "GitLab flavored markdown documentation" [gfm]: http://docs.gitlab.com/ce/markdown/markdown.html#newlines "GitLab flavored markdown documentation"
[doc-restart]: ../administration/restart_gitlab.md "GitLab restart documentation" [doc-restart]: ../administration/restart_gitlab.md "GitLab restart documentation"
...@@ -24,4 +24,4 @@ You may assign the Issue to a user, add a milestone and add labels (they are all ...@@ -24,4 +24,4 @@ You may assign the Issue to a user, add a milestone and add labels (they are all
![Submit new issue](basicsimages/submit_new_issue.png) ![Submit new issue](basicsimages/submit_new_issue.png)
Your Issue will now be added to the Issue Tracker and will be ready to be reviewed. You can comment on it and mention the people involved. You can also link Issues to the Merge Requests where the Issues are solved. To do this, you can use an [Issue closing pattern](http://doc.gitlab.com/ce/customization/issue_closing.html). Your Issue will now be added to the Issue Tracker and will be ready to be reviewed. You can comment on it and mention the people involved. You can also link Issues to the Merge Requests where the Issues are solved. To do this, you can use an [Issue closing pattern](http://docs.gitlab.com/ce/customization/issue_closing.html).
...@@ -14,7 +14,7 @@ Fill out the required information: ...@@ -14,7 +14,7 @@ Fill out the required information:
1. Select a [visibility level](https://gitlab.com/help/public_access/public_access) 1. Select a [visibility level](https://gitlab.com/help/public_access/public_access)
1. You can also [import your existing projects](http://doc.gitlab.com/ce/workflow/importing/README.html) 1. You can also [import your existing projects](http://docs.gitlab.com/ce/workflow/importing/README.html)
1. Click on "create project" 1. Click on "create project"
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
**Note: Custom git hooks must be configured on the filesystem of the GitLab **Note: Custom git hooks must be configured on the filesystem of the GitLab
server. Only GitLab server administrators will be able to complete these tasks. server. Only GitLab server administrators will be able to complete these tasks.
Please explore [webhooks](../web_hooks/web_hooks.md) as an option if you do not have filesystem access. For a user configurable Git Hooks interface, please see [GitLab Enterprise Edition Git Hooks](http://doc.gitlab.com/ee/git_hooks/git_hooks.html).** Please explore [webhooks](../web_hooks/web_hooks.md) as an option if you do not have filesystem access. For a user configurable Git Hooks interface, please see [GitLab Enterprise Edition Git Hooks](http://docs.gitlab.com/ee/git_hooks/git_hooks.html).**
Git natively supports hooks that are executed on different actions. Git natively supports hooks that are executed on different actions.
Examples of server-side git hooks include pre-receive, post-receive, and update. Examples of server-side git hooks include pre-receive, post-receive, and update.
......
...@@ -6,7 +6,7 @@ Since an installation from source is a lot of work and error prone we strongly r ...@@ -6,7 +6,7 @@ Since an installation from source is a lot of work and error prone we strongly r
One reason the Omnibus package is more reliable is its use of Runit to restart any of the GitLab processes in case one crashes. One reason the Omnibus package is more reliable is its use of Runit to restart any of the GitLab processes in case one crashes.
On heavily used GitLab instances the memory usage of the Sidekiq background worker will grow over time. On heavily used GitLab instances the memory usage of the Sidekiq background worker will grow over time.
Omnibus packages solve this by [letting the Sidekiq terminate gracefully](http://doc.gitlab.com/ce/operations/sidekiq_memory_killer.html) if it uses too much memory. Omnibus packages solve this by [letting the Sidekiq terminate gracefully](http://docs.gitlab.com/ce/operations/sidekiq_memory_killer.html) if it uses too much memory.
After this termination Runit will detect Sidekiq is not running and will start it. After this termination Runit will detect Sidekiq is not running and will start it.
Since installations from source don't have Runit, Sidekiq can't be terminated and its memory usage will grow over time. Since installations from source don't have Runit, Sidekiq can't be terminated and its memory usage will grow over time.
......
...@@ -132,5 +132,5 @@ To disable the relative URL: ...@@ -132,5 +132,5 @@ To disable the relative URL:
1. Follow the same as above starting from 2. and set up the 1. Follow the same as above starting from 2. and set up the
GitLab URL to one that doesn't contain a relative path. GitLab URL to one that doesn't contain a relative path.
[omnibus-rel]: http://doc.gitlab.com/omnibus/settings/configuration.html#configuring-a-relative-url-for-gitlab "How to setup relative URL in Omnibus GitLab" [omnibus-rel]: http://docs.gitlab.com/omnibus/settings/configuration.html#configuring-a-relative-url-for-gitlab "How to setup relative URL in Omnibus GitLab"
[restart gitlab]: ../administration/restart_gitlab.md#installations-from-source "How to restart GitLab" [restart gitlab]: ../administration/restart_gitlab.md#installations-from-source "How to restart GitLab"
...@@ -19,7 +19,7 @@ See the documentation below for details on how to configure these services. ...@@ -19,7 +19,7 @@ See the documentation below for details on how to configure these services.
GitLab Enterprise Edition contains [advanced Jenkins support][jenkins]. GitLab Enterprise Edition contains [advanced Jenkins support][jenkins].
[jenkins]: http://doc.gitlab.com/ee/integration/jenkins.html [jenkins]: http://docs.gitlab.com/ee/integration/jenkins.html
## Project services ## Project services
......
...@@ -39,4 +39,4 @@ Install and update your GitLab installation. ...@@ -39,4 +39,4 @@ Install and update your GitLab installation.
- [Install GitLab](https://about.gitlab.com/installation/) - [Install GitLab](https://about.gitlab.com/installation/)
- [Update GitLab](https://about.gitlab.com/update/) - [Update GitLab](https://about.gitlab.com/update/)
- [Explore Omnibus GitLab configuration options](http://doc.gitlab.com/omnibus/settings/configuration.html) - [Explore Omnibus GitLab configuration options](http://docs.gitlab.com/omnibus/settings/configuration.html)
## Log system ## Log system
GitLab has advanced log system so everything is logging and you can analize your instance using various system log files. GitLab has advanced log system so everything is logging and you can analize your instance using various system log files.
In addition to system log files, GitLab Enterprise Edition comes with Audit Events. Find more about them [in Audit Events documentation](http://doc.gitlab.com/ee/administration/audit_events.html) In addition to system log files, GitLab Enterprise Edition comes with Audit Events. Find more about them [in Audit Events documentation](http://docs.gitlab.com/ee/administration/audit_events.html)
System log files are typically plain text in a standard log file format. This guide talks about how to read and use these system log files. System log files are typically plain text in a standard log file format. This guide talks about how to read and use these system log files.
......
...@@ -8,4 +8,4 @@ ...@@ -8,4 +8,4 @@
- [User management](user_management.md) - [User management](user_management.md)
- [Webhooks](web_hooks.md) - [Webhooks](web_hooks.md)
- [Import](import.md) of git repositories in bulk - [Import](import.md) of git repositories in bulk
- [Rebuild authorized_keys file](http://doc.gitlab.com/ce/raketasks/maintenance.html#rebuild-authorized_keys-file) task for administrators - [Rebuild authorized_keys file](http://docs.gitlab.com/ce/raketasks/maintenance.html#rebuild-authorized_keys-file) task for administrators
...@@ -8,3 +8,4 @@ ...@@ -8,3 +8,4 @@
- [User File Uploads](user_file_uploads.md) - [User File Uploads](user_file_uploads.md)
- [How we manage the CRIME vulnerability](crime_vulnerability.md) - [How we manage the CRIME vulnerability](crime_vulnerability.md)
- [Enforce Two-factor authentication](two_factor_authentication.md) - [Enforce Two-factor authentication](two_factor_authentication.md)
- [Send email confirmation on sign-up](user_email_confirmation.md)
# User email confirmation at sign-up
Gitlab admin can enable email confirmation on sign-up, if you want to confirm all
user emails before they are able to sign-in.
In the Admin area under **Settings** (`/admin/application_settings`), go to section
**Sign-in Restrictions** and look for **Send confirmation email on sign-up** option.
...@@ -29,7 +29,7 @@ Based on your installation, choose a section below that fits your needs. ...@@ -29,7 +29,7 @@ Based on your installation, choose a section below that fits your needs.
## Omnibus Packages ## Omnibus Packages
- The [Omnibus update guide](http://doc.gitlab.com/omnibus/update/README.html) - The [Omnibus update guide](http://docs.gitlab.com/omnibus/update/README.html)
contains the steps needed to update an Omnibus GitLab package. contains the steps needed to update an Omnibus GitLab package.
## Installation from source ## Installation from source
...@@ -86,10 +86,10 @@ possible. ...@@ -86,10 +86,10 @@ possible.
information about configuring GitLab to work with a MySQL database. information about configuring GitLab to work with a MySQL database.
- [Restoring from backup after a failed upgrade](restore_after_failure.md) - [Restoring from backup after a failed upgrade](restore_after_failure.md)
[omnidocker]: http://doc.gitlab.com/omnibus/docker/README.html [omnidocker]: http://docs.gitlab.com/omnibus/docker/README.html
[source-ee]: https://gitlab.com/gitlab-org/gitlab-ee/tree/master/doc/update [source-ee]: https://gitlab.com/gitlab-org/gitlab-ee/tree/master/doc/update
[source-ce]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update [source-ce]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update
[ee-ce]: ../downgrade_ee_to_ce/README.md [ee-ce]: ../downgrade_ee_to_ce/README.md
[ce]: https://about.gitlab.com/features/#community [ce]: https://about.gitlab.com/features/#community
[ee]: https://about.gitlab.com/features/#enterprise [ee]: https://about.gitlab.com/features/#enterprise
[omni-ce-ee]: http://doc.gitlab.com/omnibus/update/README.html#from-community-edition-to-enterprise-edition [omni-ce-ee]: http://docs.gitlab.com/omnibus/update/README.html#from-community-edition-to-enterprise-edition
...@@ -131,7 +131,7 @@ When you feel comfortable with it to be merged you assign it to the person that ...@@ -131,7 +131,7 @@ When you feel comfortable with it to be merged you assign it to the person that
There is room for more feedback and after the assigned person feels comfortable with the result the branch is merged. There is room for more feedback and after the assigned person feels comfortable with the result the branch is merged.
If the assigned person does not feel comfortable they can close the merge request without merging. If the assigned person does not feel comfortable they can close the merge request without merging.
In GitLab it is common to protect the long-lived branches (e.g. the master branch) so that normal developers [can't modify these protected branches](http://doc.gitlab.com/ce/permissions/permissions.html). In GitLab it is common to protect the long-lived branches (e.g. the master branch) so that normal developers [can't modify these protected branches](http://docs.gitlab.com/ce/permissions/permissions.html).
So if you want to merge it into a protected branch you assign it to someone with master authorizations. So if you want to merge it into a protected branch you assign it to someone with master authorizations.
## Issues with GitLab flow ## Issues with GitLab flow
...@@ -187,7 +187,7 @@ If you have an issue that spans across multiple repositories, the best thing is ...@@ -187,7 +187,7 @@ If you have an issue that spans across multiple repositories, the best thing is
![Vim screen showing the rebase view](rebase.png) ![Vim screen showing the rebase view](rebase.png)
With git you can use an interactive rebase (`rebase -i`) to squash multiple commits into one and reorder them. With git you can use an interactive rebase (`rebase -i`) to squash multiple commits into one and reorder them.
In GitLab EE and .com you can also [rebase before merge](http://doc.gitlab.com/ee/workflow/rebase_before_merge.html) from the web interface. In GitLab EE and .com you can also [rebase before merge](http://docs.gitlab.com/ee/workflow/rebase_before_merge.html) from the web interface.
This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical. This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical.
However you should never rebase commits you have pushed to a remote server. However you should never rebase commits you have pushed to a remote server.
Somebody can have referred to the commits or cherry-picked them. Somebody can have referred to the commits or cherry-picked them.
......
...@@ -54,7 +54,7 @@ If necessary, you can increase the access level of an individual user for a spec ...@@ -54,7 +54,7 @@ If necessary, you can increase the access level of an individual user for a spec
## Managing group memberships via LDAP ## Managing group memberships via LDAP
In GitLab Enterprise Edition it is possible to manage GitLab group memberships using LDAP groups. In GitLab Enterprise Edition it is possible to manage GitLab group memberships using LDAP groups.
See [the GitLab Enterprise Edition documentation](http://doc.gitlab.com/ee/integration/ldap.html) for more information. See [the GitLab Enterprise Edition documentation](http://docs.gitlab.com/ee/integration/ldap.html) for more information.
## Allowing only admins to create groups ## Allowing only admins to create groups
......
...@@ -44,5 +44,5 @@ case the namespace is taken, the project will be imported on the user's ...@@ -44,5 +44,5 @@ case the namespace is taken, the project will be imported on the user's
namespace. namespace.
[gh-import]: ../../integration/github.md "GitHub integration" [gh-import]: ../../integration/github.md "GitHub integration"
[ee-gh]: http://doc.gitlab.com/ee/integration/github.html "GitHub integration for GitLab EE" [ee-gh]: http://docs.gitlab.com/ee/integration/github.html "GitHub integration for GitLab EE"
[new-project]: ../../gitlab-basics/create-project.md "How to create a new project in GitLab" [new-project]: ../../gitlab-basics/create-project.md "How to create a new project in GitLab"
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
You can import your existing GitLab.com projects to your GitLab instance. But keep in mind that it is possible only if You can import your existing GitLab.com projects to your GitLab instance. But keep in mind that it is possible only if
GitLab support is enabled on your GitLab instance. GitLab support is enabled on your GitLab instance.
You can read more about GitLab support [here](http://doc.gitlab.com/ce/integration/gitlab.html) You can read more about GitLab support [here](http://docs.gitlab.com/ce/integration/gitlab.html)
To get to the importer page you need to go to "New project" page. To get to the importer page you need to go to "New project" page.
![New project page](gitlab_importer/new_project_page.png) ![New project page](gitlab_importer/new_project_page.png)
......
...@@ -4,7 +4,7 @@ Managing large files such as audio, video and graphics files has always been one ...@@ -4,7 +4,7 @@ Managing large files such as audio, video and graphics files has always been one
of the shortcomings of Git. The general recommendation is to not have Git repositories of the shortcomings of Git. The general recommendation is to not have Git repositories
larger than 1GB to preserve performance. larger than 1GB to preserve performance.
GitLab already supports [managing large files with git annex](http://doc.gitlab.com/ee/workflow/git_annex.html) GitLab already supports [managing large files with git annex](http://docs.gitlab.com/ee/workflow/git_annex.html)
(EE only), however in certain environments it is not always convenient to use (EE only), however in certain environments it is not always convenient to use
different commands to differentiate between the large files and regular ones. different commands to differentiate between the large files and regular ones.
......
...@@ -23,7 +23,7 @@ module SharedDiffNote ...@@ -23,7 +23,7 @@ module SharedDiffNote
page.within(diff_file_selector) do page.within(diff_file_selector) do
click_diff_line(sample_commit.line_code) click_diff_line(sample_commit.line_code)
page.within("form[id$='#{sample_commit.line_code}']") do page.within("form[id$='#{sample_commit.line_code}-true']") do
fill_in "note[note]", with: "Typo, please fix" fill_in "note[note]", with: "Typo, please fix"
find(".js-comment-button").trigger("click") find(".js-comment-button").trigger("click")
sleep 0.05 sleep 0.05
...@@ -33,7 +33,7 @@ module SharedDiffNote ...@@ -33,7 +33,7 @@ module SharedDiffNote
step 'I leave a diff comment in a parallel view on the left side like "Old comment"' do step 'I leave a diff comment in a parallel view on the left side like "Old comment"' do
click_parallel_diff_line(sample_commit.line_code, 'old') click_parallel_diff_line(sample_commit.line_code, 'old')
page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}']") do page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}-true']") do
fill_in "note[note]", with: "Old comment" fill_in "note[note]", with: "Old comment"
find(".js-comment-button").trigger("click") find(".js-comment-button").trigger("click")
end end
...@@ -41,7 +41,7 @@ module SharedDiffNote ...@@ -41,7 +41,7 @@ module SharedDiffNote
step 'I leave a diff comment in a parallel view on the right side like "New comment"' do step 'I leave a diff comment in a parallel view on the right side like "New comment"' do
click_parallel_diff_line(sample_commit.line_code, 'new') click_parallel_diff_line(sample_commit.line_code, 'new')
page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}']") do page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}-true']") do
fill_in "note[note]", with: "New comment" fill_in "note[note]", with: "New comment"
find(".js-comment-button").trigger("click") find(".js-comment-button").trigger("click")
end end
...@@ -51,7 +51,7 @@ module SharedDiffNote ...@@ -51,7 +51,7 @@ module SharedDiffNote
page.within(diff_file_selector) do page.within(diff_file_selector) do
click_diff_line(sample_commit.line_code) click_diff_line(sample_commit.line_code)
page.within("form[id$='#{sample_commit.line_code}']") do page.within("form[id$='#{sample_commit.line_code}-true']") do
fill_in "note[note]", with: "Should fix it :smile:" fill_in "note[note]", with: "Should fix it :smile:"
find('.js-md-preview-button').click find('.js-md-preview-button').click
end end
...@@ -62,7 +62,7 @@ module SharedDiffNote ...@@ -62,7 +62,7 @@ module SharedDiffNote
page.within(diff_file_selector) do page.within(diff_file_selector) do
click_diff_line(sample_commit.del_line_code) click_diff_line(sample_commit.del_line_code)
page.within("form[id$='#{sample_commit.del_line_code}']") do page.within("form[id$='#{sample_commit.del_line_code}-true']") do
fill_in "note[note]", with: "DRY this up" fill_in "note[note]", with: "DRY this up"
find('.js-md-preview-button').click find('.js-md-preview-button').click
end end
...@@ -91,7 +91,7 @@ module SharedDiffNote ...@@ -91,7 +91,7 @@ module SharedDiffNote
page.within(diff_file_selector) do page.within(diff_file_selector) do
click_diff_line(sample_commit.line_code) click_diff_line(sample_commit.line_code)
page.within("form[id$='#{sample_commit.line_code}']") do page.within("form[id$='#{sample_commit.line_code}-true']") do
fill_in 'note[note]', with: ':smile:' fill_in 'note[note]', with: ':smile:'
click_button('Comment') click_button('Comment')
end end
......
...@@ -107,6 +107,8 @@ module API ...@@ -107,6 +107,8 @@ module API
break if opts[:line_code] break if opts[:line_code]
end end
opts[:type] = LegacyDiffNote.name if opts[:line_code]
end end
note = ::Notes::CreateService.new(user_project, current_user, opts).execute note = ::Notes::CreateService.new(user_project, current_user, opts).execute
......
...@@ -228,9 +228,9 @@ module API ...@@ -228,9 +228,9 @@ module API
class CommitNote < Grape::Entity class CommitNote < Grape::Entity
expose :note expose :note
expose(:path) { |note| note.diff_file_name } expose(:path) { |note| note.diff_file_path if note.legacy_diff_note? }
expose(:line) { |note| note.diff_new_line } expose(:line) { |note| note.diff_new_line if note.legacy_diff_note? }
expose(:line_type) { |note| note.diff_line_type } expose(:line_type) { |note| note.diff_line_type if note.legacy_diff_note? }
expose :author, using: Entities::UserBasic expose :author, using: Entities::UserBasic
expose :created_at expose :created_at
end end
......
module Gitlab
module GithubImport
class BranchFormatter < BaseFormatter
delegate :repo, :sha, :ref, to: :raw_data
def exists?
project.repository.branch_exists?(ref)
end
def name
@name ||= exists? ? ref : "#{ref}-#{short_id}"
end
def valid?
repo.present?
end
def valid?
repo.present?
end
private
def short_id
sha.to_s[0..7]
end
end
end
end
...@@ -3,12 +3,15 @@ module Gitlab ...@@ -3,12 +3,15 @@ module Gitlab
class Importer class Importer
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
attr_reader :project, :client attr_reader :client, :project, :repo, :repo_url
def initialize(project) def initialize(project)
@project = project @project = project
if import_data_credentials @repo = project.import_source
@client = Client.new(import_data_credentials[:user]) @repo_url = project.import_url
if credentials
@client = Client.new(credentials[:user])
@formatter = Gitlab::ImportFormatter.new @formatter = Gitlab::ImportFormatter.new
else else
raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}" raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}"
...@@ -22,12 +25,12 @@ module Gitlab ...@@ -22,12 +25,12 @@ module Gitlab
private private
def import_data_credentials def credentials
@import_data_credentials ||= project.import_data.credentials if project.import_data @credentials ||= project.import_data.credentials if project.import_data
end end
def import_labels def import_labels
client.labels(project.import_source).each do |raw_data| client.labels(repo).each do |raw_data|
Label.create!(LabelFormatter.new(project, raw_data).attributes) Label.create!(LabelFormatter.new(project, raw_data).attributes)
end end
...@@ -37,7 +40,7 @@ module Gitlab ...@@ -37,7 +40,7 @@ module Gitlab
end end
def import_milestones def import_milestones
client.list_milestones(project.import_source, state: :all).each do |raw_data| client.list_milestones(repo, state: :all).each do |raw_data|
Milestone.create!(MilestoneFormatter.new(project, raw_data).attributes) Milestone.create!(MilestoneFormatter.new(project, raw_data).attributes)
end end
...@@ -47,9 +50,7 @@ module Gitlab ...@@ -47,9 +50,7 @@ module Gitlab
end end
def import_issues def import_issues
client.list_issues(project.import_source, state: :all, client.list_issues(repo, state: :all, sort: :created, direction: :asc).each do |raw_data|
sort: :created,
direction: :asc).each do |raw_data|
gh_issue = IssueFormatter.new(project, raw_data) gh_issue = IssueFormatter.new(project, raw_data)
if gh_issue.valid? if gh_issue.valid?
...@@ -68,12 +69,17 @@ module Gitlab ...@@ -68,12 +69,17 @@ module Gitlab
end end
def import_pull_requests def import_pull_requests
client.pull_requests(project.import_source, state: :all, pull_requests = client.pull_requests(repo, state: :all, sort: :created, direction: :asc)
sort: :created, .map { |raw| PullRequestFormatter.new(project, raw) }
direction: :asc).each do |raw_data| .select(&:valid?)
pull_request = PullRequestFormatter.new(project, raw_data)
source_branches_removed = pull_requests.reject(&:source_branch_exists?).map { |pr| [pr.source_branch_name, pr.source_branch_sha] }
target_branches_removed = pull_requests.reject(&:target_branch_exists?).map { |pr| [pr.target_branch_name, pr.target_branch_sha] }
branches_removed = source_branches_removed | target_branches_removed
create_refs(branches_removed)
if pull_request.valid? pull_requests.each do |pull_request|
merge_request = MergeRequest.new(pull_request.attributes) merge_request = MergeRequest.new(pull_request.attributes)
if merge_request.save if merge_request.save
...@@ -82,15 +88,31 @@ module Gitlab ...@@ -82,15 +88,31 @@ module Gitlab
import_comments_on_diff(pull_request.number, merge_request) import_comments_on_diff(pull_request.number, merge_request)
end end
end end
end
delete_refs(branches_removed)
true true
rescue ActiveRecord::RecordInvalid => e rescue ActiveRecord::RecordInvalid => e
raise Projects::ImportService::Error, e.message raise Projects::ImportService::Error, e.message
end end
def create_refs(branches)
branches.each do |name, sha|
client.create_ref(repo, "refs/heads/#{name}", sha)
end
project.repository.fetch_ref(repo_url, '+refs/heads/*', 'refs/heads/*')
end
def delete_refs(branches)
branches.each do |name, _|
client.delete_ref(repo, "heads/#{name}")
project.repository.rm_branch(project.creator, name)
end
end
def apply_labels(number, issuable) def apply_labels(number, issuable)
issue = client.issue(project.import_source, number) issue = client.issue(repo, number)
if issue.labels.count > 0 if issue.labels.count > 0
label_ids = issue.labels.map do |raw| label_ids = issue.labels.map do |raw|
...@@ -102,12 +124,12 @@ module Gitlab ...@@ -102,12 +124,12 @@ module Gitlab
end end
def import_comments(issue_number, noteable) def import_comments(issue_number, noteable)
comments = client.issue_comments(project.import_source, issue_number) comments = client.issue_comments(repo, issue_number)
create_comments(comments, noteable) create_comments(comments, noteable)
end end
def import_comments_on_diff(pull_request_number, merge_request) def import_comments_on_diff(pull_request_number, merge_request)
comments = client.pull_request_comments(project.import_source, pull_request_number) comments = client.pull_request_comments(repo, pull_request_number)
create_comments(comments, merge_request) create_comments(comments, merge_request)
end end
......
module Gitlab module Gitlab
module GithubImport module GithubImport
class PullRequestFormatter < BaseFormatter class PullRequestFormatter < BaseFormatter
delegate :exists?, :name, :project, :repo, :sha, to: :source_branch, prefix: true
delegate :exists?, :name, :project, :repo, :sha, to: :target_branch, prefix: true
def attributes def attributes
{ {
iid: number, iid: number,
title: raw_data.title, title: raw_data.title,
description: description, description: description,
source_project: source_project, source_project: source_branch_project,
source_branch: source_branch.name, source_branch: source_branch_name,
target_project: target_project, head_source_sha: source_branch_sha,
target_branch: target_branch.name, target_project: target_branch_project,
target_branch: target_branch_name,
base_target_sha: target_branch_sha,
state: state, state: state,
milestone: milestone, milestone: milestone,
author_id: author_id, author_id: author_id,
...@@ -24,7 +29,15 @@ module Gitlab ...@@ -24,7 +29,15 @@ module Gitlab
end end
def valid? def valid?
!cross_project? && source_branch.present? && target_branch.present? source_branch.valid? && target_branch.valid? && !cross_project?
end
def source_branch
@source_branch ||= BranchFormatter.new(project, raw_data.head)
end
def target_branch
@target_branch ||= BranchFormatter.new(project, raw_data.base)
end end
private private
...@@ -52,7 +65,7 @@ module Gitlab ...@@ -52,7 +65,7 @@ module Gitlab
end end
def cross_project? def cross_project?
source_repo.present? && target_repo.present? && source_repo.id != target_repo.id source_branch_repo.id != target_branch_repo.id
end end
def description def description
...@@ -65,30 +78,6 @@ module Gitlab ...@@ -65,30 +78,6 @@ module Gitlab
end end
end end
def source_project
project
end
def source_repo
raw_data.head.repo
end
def source_branch
source_project.repository.find_branch(raw_data.head.ref)
end
def target_project
project
end
def target_repo
raw_data.base.repo
end
def target_branch
target_project.repository.find_branch(raw_data.base.ref)
end
def state def state
@state ||= case true @state ||= case true
when raw_data.state == 'closed' && raw_data.merged_at.present? when raw_data.state == 'closed' && raw_data.merged_at.present?
......
...@@ -6,26 +6,28 @@ module Gitlab ...@@ -6,26 +6,28 @@ module Gitlab
attach_to :active_support attach_to :active_support
def cache_read(event) def cache_read(event)
increment(:cache_read_duration, event.duration) increment(:cache_read, event.duration)
end end
def cache_write(event) def cache_write(event)
increment(:cache_write_duration, event.duration) increment(:cache_write, event.duration)
end end
def cache_delete(event) def cache_delete(event)
increment(:cache_delete_duration, event.duration) increment(:cache_delete, event.duration)
end end
def cache_exist?(event) def cache_exist?(event)
increment(:cache_exists_duration, event.duration) increment(:cache_exists, event.duration)
end end
def increment(key, duration) def increment(key, duration)
return unless current_transaction return unless current_transaction
current_transaction.increment(:cache_duration, duration) current_transaction.increment(:cache_duration, duration)
current_transaction.increment(key, duration) current_transaction.increment(:cache_count, 1)
current_transaction.increment("#{key}_duration".to_sym, duration)
current_transaction.increment("#{key}_count".to_sym, 1)
end end
private private
......
...@@ -62,7 +62,7 @@ module Gitlab ...@@ -62,7 +62,7 @@ module Gitlab
end end
def wiki_page_url def wiki_page_url
"#{Gitlab.config.gitlab.url}#{object.wiki.wiki_base_path}/#{object.slug}" namespace_project_wiki_url(object.wiki.project.namespace, object.wiki.project, object.slug)
end end
end end
end end
...@@ -65,3 +65,4 @@ Disallow: /*/*/deploy_keys ...@@ -65,3 +65,4 @@ Disallow: /*/*/deploy_keys
Disallow: /*/*/hooks Disallow: /*/*/hooks
Disallow: /*/*/services Disallow: /*/*/services
Disallow: /*/*/protected_branches Disallow: /*/*/protected_branches
Disallow: /*/*/uploads/
...@@ -114,6 +114,82 @@ describe Admin::UsersController do ...@@ -114,6 +114,82 @@ describe Admin::UsersController do
end end
end end
describe 'POST update' do
context 'when the password has changed' do
def update_password(user, password, password_confirmation = nil)
params = {
id: user.to_param,
user: {
password: password,
password_confirmation: password_confirmation || password
}
}
post :update, params
end
context 'when the new password is valid' do
it 'redirects to the user' do
update_password(user, 'AValidPassword1')
expect(response).to redirect_to(admin_user_path(user))
end
it 'updates the password' do
update_password(user, 'AValidPassword1')
expect { user.reload }.to change { user.encrypted_password }
end
it 'sets the new password to expire immediately' do
update_password(user, 'AValidPassword1')
expect { user.reload }.to change { user.password_expires_at }.to(a_value <= Time.now)
end
end
context 'when the new password is invalid' do
it 'shows the edit page again' do
update_password(user, 'invalid')
expect(response).to render_template(:edit)
end
it 'returns the error message' do
update_password(user, 'invalid')
expect(assigns[:user].errors).to contain_exactly(a_string_matching(/too short/))
end
it 'does not update the password' do
update_password(user, 'invalid')
expect { user.reload }.not_to change { user.encrypted_password }
end
end
context 'when the new password does not match the password confirmation' do
it 'shows the edit page again' do
update_password(user, 'AValidPassword1', 'AValidPassword2')
expect(response).to render_template(:edit)
end
it 'returns the error message' do
update_password(user, 'AValidPassword1', 'AValidPassword2')
expect(assigns[:user].errors).to contain_exactly(a_string_matching(/doesn't match/))
end
it 'does not update the password' do
update_password(user, 'AValidPassword1', 'AValidPassword2')
expect { user.reload }.not_to change { user.encrypted_password }
end
end
end
end
describe "POST impersonate" do describe "POST impersonate" do
context "when the user is blocked" do context "when the user is blocked" do
before do before do
......
require 'spec_helper'
describe RegistrationsController do
describe '#create' do
around(:each) do |example|
perform_enqueued_jobs do
example.run
end
end
let(:user_params) { { user: { name: "new_user", username: "new_username", email: "new@user.com", password: "Any_password" } } }
context 'when sending email confirmation' do
before { allow(current_application_settings).to receive(:send_user_confirmation_email).and_return(false) }
it 'logs user in directly' do
post(:create, user_params)
expect(ActionMailer::Base.deliveries.last).to be_nil
expect(subject.current_user).to_not be_nil
end
end
context 'when not sending email confirmation' do
before { allow(current_application_settings).to receive(:send_user_confirmation_email).and_return(true) }
it 'does not authenticate user and sends confirmation email' do
post(:create, user_params)
expect(ActionMailer::Base.deliveries.last.to.first).to eq(user_params[:user][:email])
expect(subject.current_user).to be_nil
end
end
end
end
...@@ -9,10 +9,10 @@ FactoryGirl.define do ...@@ -9,10 +9,10 @@ FactoryGirl.define do
author author
factory :note_on_commit, traits: [:on_commit] factory :note_on_commit, traits: [:on_commit]
factory :note_on_commit_diff, traits: [:on_commit, :on_diff] factory :note_on_commit_diff, traits: [:on_commit, :on_diff], class: LegacyDiffNote
factory :note_on_issue, traits: [:on_issue], aliases: [:votable_note] factory :note_on_issue, traits: [:on_issue], aliases: [:votable_note]
factory :note_on_merge_request, traits: [:on_merge_request] factory :note_on_merge_request, traits: [:on_merge_request]
factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff] factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff], class: LegacyDiffNote
factory :note_on_project_snippet, traits: [:on_project_snippet] factory :note_on_project_snippet, traits: [:on_project_snippet]
factory :system_note, traits: [:system] factory :system_note, traits: [:system]
factory :downvote_note, traits: [:award, :downvote] factory :downvote_note, traits: [:award, :downvote]
......
...@@ -210,6 +210,8 @@ describe "Admin::Users", feature: true do ...@@ -210,6 +210,8 @@ describe "Admin::Users", feature: true do
before do before do
fill_in "user_name", with: "Big Bang" fill_in "user_name", with: "Big Bang"
fill_in "user_email", with: "bigbang@mail.com" fill_in "user_email", with: "bigbang@mail.com"
fill_in "user_password", with: "AValidPassword1"
fill_in "user_password_confirmation", with: "AValidPassword1"
check "user_admin" check "user_admin"
click_button "Save changes" click_button "Save changes"
end end
...@@ -223,6 +225,7 @@ describe "Admin::Users", feature: true do ...@@ -223,6 +225,7 @@ describe "Admin::Users", feature: true do
@simple_user.reload @simple_user.reload
expect(@simple_user.name).to eq('Big Bang') expect(@simple_user.name).to eq('Big Bang')
expect(@simple_user.is_admin?).to be_truthy expect(@simple_user.is_admin?).to be_truthy
expect(@simple_user.password_expires_at).to be <= Time.now
end end
end end
end end
......
...@@ -38,6 +38,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true ...@@ -38,6 +38,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
expect(page).to have_content 'lfs' expect(page).to have_content 'lfs'
expect(page).not_to have_content 'fix' expect(page).not_to have_content 'fix'
expect(page).not_to have_content 'markdown' expect(page).not_to have_content 'markdown'
expect(count_merge_requests).to eq(1)
end end
it 'filters on a specific assignee' do it 'filters on a specific assignee' do
...@@ -46,6 +47,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true ...@@ -46,6 +47,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
expect(page).not_to have_content 'lfs' expect(page).not_to have_content 'lfs'
expect(page).to have_content 'fix' expect(page).to have_content 'fix'
expect(page).to have_content 'markdown' expect(page).to have_content 'markdown'
expect(count_merge_requests).to eq(2)
end end
it 'sorts by newest' do it 'sorts by newest' do
...@@ -53,6 +55,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true ...@@ -53,6 +55,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
expect(first_merge_request).to include('lfs') expect(first_merge_request).to include('lfs')
expect(last_merge_request).to include('fix') expect(last_merge_request).to include('fix')
expect(count_merge_requests).to eq(3)
end end
it 'sorts by oldest' do it 'sorts by oldest' do
...@@ -60,30 +63,35 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true ...@@ -60,30 +63,35 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
expect(first_merge_request).to include('fix') expect(first_merge_request).to include('fix')
expect(last_merge_request).to include('lfs') expect(last_merge_request).to include('lfs')
expect(count_merge_requests).to eq(3)
end end
it 'sorts by last updated' do it 'sorts by last updated' do
visit_merge_requests(project, sort: sort_value_recently_updated) visit_merge_requests(project, sort: sort_value_recently_updated)
expect(first_merge_request).to include('lfs') expect(first_merge_request).to include('lfs')
expect(count_merge_requests).to eq(3)
end end
it 'sorts by oldest updated' do it 'sorts by oldest updated' do
visit_merge_requests(project, sort: sort_value_oldest_updated) visit_merge_requests(project, sort: sort_value_oldest_updated)
expect(first_merge_request).to include('markdown') expect(first_merge_request).to include('markdown')
expect(count_merge_requests).to eq(3)
end end
it 'sorts by milestone due soon' do it 'sorts by milestone due soon' do
visit_merge_requests(project, sort: sort_value_milestone_soon) visit_merge_requests(project, sort: sort_value_milestone_soon)
expect(first_merge_request).to include('fix') expect(first_merge_request).to include('fix')
expect(count_merge_requests).to eq(3)
end end
it 'sorts by milestone due later' do it 'sorts by milestone due later' do
visit_merge_requests(project, sort: sort_value_milestone_later) visit_merge_requests(project, sort: sort_value_milestone_later)
expect(first_merge_request).to include('markdown') expect(first_merge_request).to include('markdown')
expect(count_merge_requests).to eq(3)
end end
it 'filters on one label and sorts by due soon' do it 'filters on one label and sorts by due soon' do
...@@ -94,6 +102,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true ...@@ -94,6 +102,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
sort: sort_value_due_date_soon) sort: sort_value_due_date_soon)
expect(first_merge_request).to include('fix') expect(first_merge_request).to include('fix')
expect(count_merge_requests).to eq(1)
end end
context 'while filtering on two labels' do context 'while filtering on two labels' do
...@@ -110,6 +119,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true ...@@ -110,6 +119,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
sort: sort_value_due_date_soon) sort: sort_value_due_date_soon)
expect(first_merge_request).to include('fix') expect(first_merge_request).to include('fix')
expect(count_merge_requests).to eq(1)
end end
context 'filter on assignee and' do context 'filter on assignee and' do
...@@ -119,6 +129,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true ...@@ -119,6 +129,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
sort: sort_value_due_date_soon) sort: sort_value_due_date_soon)
expect(first_merge_request).to include('fix') expect(first_merge_request).to include('fix')
expect(count_merge_requests).to eq(1)
end end
end end
end end
...@@ -134,4 +145,8 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true ...@@ -134,4 +145,8 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
def last_merge_request def last_merge_request
page.all('ul.mr-list > li').last.text page.all('ul.mr-list > li').last.text
end end
def count_merge_requests
page.all('ul.mr-list > li').count
end
end end
...@@ -192,7 +192,7 @@ describe 'Comments', feature: true do ...@@ -192,7 +192,7 @@ describe 'Comments', feature: true do
end end
it 'should be removed when canceled' do it 'should be removed when canceled' do
page.within(".diff-file form[id$='#{line_code}']") do page.within(".diff-file form[id$='#{line_code}-true']") do
find('.js-close-discussion-note-form').trigger('click') find('.js-close-discussion-note-form').trigger('click')
end end
......
...@@ -2,6 +2,10 @@ require 'spec_helper' ...@@ -2,6 +2,10 @@ require 'spec_helper'
feature 'Signup', feature: true do feature 'Signup', feature: true do
describe 'signup with no errors' do describe 'signup with no errors' do
context "when sending confirmation email" do
before { allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(true) }
it 'creates the user account and sends a confirmation email' do it 'creates the user account and sends a confirmation email' do
user = build(:user) user = build(:user)
...@@ -18,6 +22,27 @@ feature 'Signup', feature: true do ...@@ -18,6 +22,27 @@ feature 'Signup', feature: true do
end end
end end
context "when not sending confirmation email" do
before { allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(false) }
it 'creates the user account and goes to dashboard' do
user = build(:user)
visit root_path
fill_in 'new_user_name', with: user.name
fill_in 'new_user_username', with: user.username
fill_in 'new_user_email', with: user.email
fill_in 'new_user_password', with: user.password
click_button "Sign up"
expect(current_path).to eq dashboard_projects_path
expect(page).to have_content("Welcome! You have signed up successfully.")
end
end
end
describe 'signup with errors' do describe 'signup with errors' do
it "displays the errors" do it "displays the errors" do
existing_user = create(:user) existing_user = create(:user)
......
require 'spec_helper' require 'spec_helper'
describe IssuesFinder do describe IssuesFinder do
let(:user) { create :user } let(:user) { create(:user) }
let(:user2) { create :user } let(:user2) { create(:user) }
let(:project1) { create(:project) } let(:project1) { create(:empty_project) }
let(:project2) { create(:project) } let(:project2) { create(:empty_project) }
let(:milestone) { create(:milestone, project: project1) } let(:milestone) { create(:milestone, project: project1) }
let(:label) { create(:label, project: project2) } let(:label) { create(:label, project: project2) }
let(:issue1) { create(:issue, author: user, assignee: user, project: project1, milestone: milestone) } let(:issue1) { create(:issue, author: user, assignee: user, project: project1, milestone: milestone) }
...@@ -16,101 +16,147 @@ describe IssuesFinder do ...@@ -16,101 +16,147 @@ describe IssuesFinder do
project1.team << [user, :master] project1.team << [user, :master]
project2.team << [user, :developer] project2.team << [user, :developer]
project2.team << [user2, :developer] project2.team << [user2, :developer]
end
describe :execute do
before :each do
issue1 issue1
issue2 issue2
issue3 issue3
end end
describe '#execute' do
let(:search_user) { user }
let(:params) { {} }
let(:issues) { IssuesFinder.new(search_user, params.merge(scope: scope, state: 'opened')).execute }
context 'scope: all' do context 'scope: all' do
it 'should filter by all' do let(:scope) { 'all' }
params = { scope: "all", state: 'opened' }
issues = IssuesFinder.new(user, params).execute it 'returns all issues' do
expect(issues.size).to eq(3) expect(issues).to contain_exactly(issue1, issue2, issue3)
end end
it 'should filter by assignee id' do context 'filtering by assignee ID' do
params = { scope: "all", assignee_id: user.id, state: 'opened' } let(:params) { { assignee_id: user.id } }
issues = IssuesFinder.new(user, params).execute
expect(issues.size).to eq(2) it 'returns issues assigned to that user' do
expect(issues).to contain_exactly(issue1, issue2)
end
end end
it 'should filter by author id' do context 'filtering by author ID' do
params = { scope: "all", author_id: user2.id, state: 'opened' } let(:params) { { author_id: user2.id } }
issues = IssuesFinder.new(user, params).execute
expect(issues).to eq([issue3]) it 'returns issues created by that user' do
expect(issues).to contain_exactly(issue3)
end
end end
it 'should filter by milestone id' do context 'filtering by milestone' do
params = { scope: "all", milestone_title: milestone.title, state: 'opened' } let(:params) { { milestone_title: milestone.title } }
issues = IssuesFinder.new(user, params).execute
expect(issues).to eq([issue1]) it 'returns issues assigned to that milestone' do
expect(issues).to contain_exactly(issue1)
end
end end
it 'should filter by no milestone id' do context 'filtering by no milestone' do
params = { scope: "all", milestone_title: Milestone::None.title, state: 'opened' } let(:params) { { milestone_title: Milestone::None.title } }
issues = IssuesFinder.new(user, params).execute
expect(issues).to match_array([issue2, issue3]) it 'returns issues with no milestone' do
expect(issues).to contain_exactly(issue2, issue3)
end
end end
it 'should filter by label name' do context 'filtering by upcoming milestone' do
params = { scope: "all", label_name: label.title, state: 'opened' } let(:params) { { milestone_title: Milestone::Upcoming.name } }
issues = IssuesFinder.new(user, params).execute
expect(issues).to eq([issue2]) let(:project_no_upcoming_milestones) { create(:empty_project, :public) }
let(:project_next_1_1) { create(:empty_project, :public) }
let(:project_next_8_8) { create(:empty_project, :public) }
let(:yesterday) { Date.today - 1.day }
let(:tomorrow) { Date.today + 1.day }
let(:two_days_from_now) { Date.today + 2.days }
let(:ten_days_from_now) { Date.today + 10.days }
let(:milestones) do
[
create(:milestone, :closed, project: project_no_upcoming_milestones),
create(:milestone, project: project_next_1_1, title: '1.1', due_date: two_days_from_now),
create(:milestone, project: project_next_1_1, title: '8.8', due_date: ten_days_from_now),
create(:milestone, project: project_next_8_8, title: '1.1', due_date: yesterday),
create(:milestone, project: project_next_8_8, title: '8.8', due_date: tomorrow)
]
end end
it 'returns unique issues when filtering by multiple labels' do before do
label2 = create(:label, project: project2) milestones.each do |milestone|
create(:issue, project: milestone.project, milestone: milestone, author: user, assignee: user)
end
end
create(:label_link, label: label2, target: issue2) it 'returns issues in the upcoming milestone for each project' do
expect(issues.map { |issue| issue.milestone.title }).to contain_exactly('1.1', '8.8')
expect(issues.map { |issue| issue.milestone.due_date }).to contain_exactly(tomorrow, two_days_from_now)
end
end
context 'filtering by label' do
let(:params) { { label_name: label.title } }
it 'returns issues with that label' do
expect(issues).to contain_exactly(issue2)
end
end
params = { context 'filtering by multiple labels' do
scope: 'all', let(:params) { { label_name: [label.title, label2.title].join(',') } }
label_name: [label.title, label2.title].join(','), let(:label2) { create(:label, project: project2) }
state: 'opened'
}
issues = IssuesFinder.new(user, params).execute before { create(:label_link, label: label2, target: issue2) }
expect(issues).to eq([issue2]) it 'returns the unique issues with any of those labels' do
expect(issues).to contain_exactly(issue2)
end end
end
context 'filtering by no label' do
let(:params) { { label_name: Label::None.title } }
it 'should filter by no label name' do it 'returns issues with no labels' do
params = { scope: "all", label_name: Label::None.title, state: 'opened' } expect(issues).to contain_exactly(issue1, issue3)
issues = IssuesFinder.new(user, params).execute
expect(issues).to match_array([issue1, issue3])
end end
end
context 'when the user is unauthorized' do
let(:search_user) { nil }
it 'should be empty for unauthorized user' do it 'returns no results' do
params = { scope: "all", state: 'opened' } expect(issues).to be_empty
issues = IssuesFinder.new(nil, params).execute
expect(issues.size).to be_zero
end end
end
context 'when the user can see some, but not all, issues' do
let(:search_user) { user2 }
it 'should not include unauthorized issues' do it 'returns only issues they can see' do
params = { scope: "all", state: 'opened' } expect(issues).to contain_exactly(issue2, issue3)
issues = IssuesFinder.new(user2, params).execute end
expect(issues.size).to eq(2)
expect(issues).not_to include(issue1)
expect(issues).to include(issue2)
expect(issues).to include(issue3)
end end
end end
context 'personal scope' do context 'personal scope' do
it 'should filter by assignee' do let(:scope) { 'assigned-to-me' }
params = { scope: "assigned-to-me", state: 'opened' }
issues = IssuesFinder.new(user, params).execute it 'returns issue assigned to the user' do
expect(issues.size).to eq(2) expect(issues).to contain_exactly(issue1, issue2)
end end
it 'should filter by project' do context 'filtering by project' do
params = { scope: "assigned-to-me", state: 'opened', project_id: project1.id } let(:params) { { project_id: project1.id } }
issues = IssuesFinder.new(user, params).execute
expect(issues.size).to eq(1) it 'returns issues assigned to the user in that project' do
expect(issues).to contain_exactly(issue1)
end
end end
end end
end end
......
require 'spec_helper'
describe Gitlab::GithubImport::BranchFormatter, lib: true do
let(:project) { create(:project) }
let(:repo) { double }
let(:raw) do
{
ref: 'feature',
repo: repo,
sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b'
}
end
describe '#exists?' do
it 'returns true when branch exists' do
branch = described_class.new(project, double(raw))
expect(branch.exists?).to eq true
end
it 'returns false when branch does not exist' do
branch = described_class.new(project, double(raw.merge(ref: 'removed-branch')))
expect(branch.exists?).to eq false
end
end
describe '#name' do
it 'returns raw ref when branch exists' do
branch = described_class.new(project, double(raw))
expect(branch.name).to eq 'feature'
end
it 'returns formatted ref when branch does not exist' do
branch = described_class.new(project, double(raw.merge(ref: 'removed-branch')))
expect(branch.name).to eq 'removed-branch-2e5d3239'
end
end
describe '#repo' do
it 'returns raw repo' do
branch = described_class.new(project, double(raw))
expect(branch.repo).to eq repo
end
end
describe '#sha' do
it 'returns raw sha' do
branch = described_class.new(project, double(raw))
expect(branch.sha).to eq '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b'
end
end
describe '#valid?' do
it 'returns true when repository exists' do
branch = described_class.new(project, double(raw))
expect(branch.valid?).to eq true
end
it 'returns false when repository does not exist' do
branch = described_class.new(project, double(raw.merge(repo: nil)))
expect(branch.valid?).to eq false
end
end
end
...@@ -4,9 +4,9 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -4,9 +4,9 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:repository) { double(id: 1, fork: false) } let(:repository) { double(id: 1, fork: false) }
let(:source_repo) { repository } let(:source_repo) { repository }
let(:source_branch) { double(ref: 'feature', repo: source_repo) } let(:source_branch) { double(ref: 'feature', repo: source_repo, sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b') }
let(:target_repo) { repository } let(:target_repo) { repository }
let(:target_branch) { double(ref: 'master', repo: target_repo) } let(:target_branch) { double(ref: 'master', repo: target_repo, sha: '8ffb3c15a5475e59ae909384297fede4badcb4c7') }
let(:octocat) { double(id: 123456, login: 'octocat') } let(:octocat) { double(id: 123456, login: 'octocat') }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') } let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') } let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
...@@ -41,8 +41,10 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -41,8 +41,10 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
description: "*Created by: octocat*\n\nPlease pull these awesome changes", description: "*Created by: octocat*\n\nPlease pull these awesome changes",
source_project: project, source_project: project,
source_branch: 'feature', source_branch: 'feature',
head_source_sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b',
target_project: project, target_project: project,
target_branch: 'master', target_branch: 'master',
base_target_sha: '8ffb3c15a5475e59ae909384297fede4badcb4c7',
state: 'opened', state: 'opened',
milestone: nil, milestone: nil,
author_id: project.creator_id, author_id: project.creator_id,
...@@ -66,8 +68,10 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -66,8 +68,10 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
description: "*Created by: octocat*\n\nPlease pull these awesome changes", description: "*Created by: octocat*\n\nPlease pull these awesome changes",
source_project: project, source_project: project,
source_branch: 'feature', source_branch: 'feature',
head_source_sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b',
target_project: project, target_project: project,
target_branch: 'master', target_branch: 'master',
base_target_sha: '8ffb3c15a5475e59ae909384297fede4badcb4c7',
state: 'closed', state: 'closed',
milestone: nil, milestone: nil,
author_id: project.creator_id, author_id: project.creator_id,
...@@ -91,8 +95,10 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -91,8 +95,10 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
description: "*Created by: octocat*\n\nPlease pull these awesome changes", description: "*Created by: octocat*\n\nPlease pull these awesome changes",
source_project: project, source_project: project,
source_branch: 'feature', source_branch: 'feature',
head_source_sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b',
target_project: project, target_project: project,
target_branch: 'master', target_branch: 'master',
base_target_sha: '8ffb3c15a5475e59ae909384297fede4badcb4c7',
state: 'merged', state: 'merged',
milestone: nil, milestone: nil,
author_id: project.creator_id, author_id: project.creator_id,
...@@ -137,11 +143,11 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -137,11 +143,11 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
let(:milestone) { double(number: 45) } let(:milestone) { double(number: 45) }
let(:raw_data) { double(base_data.merge(milestone: milestone)) } let(:raw_data) { double(base_data.merge(milestone: milestone)) }
it 'returns nil when milestone does not exists' do it 'returns nil when milestone does not exist' do
expect(pull_request.attributes.fetch(:milestone)).to be_nil expect(pull_request.attributes.fetch(:milestone)).to be_nil
end end
it 'returns milestone when is exists' do it 'returns milestone when it exists' do
milestone = create(:milestone, project: project, iid: 45) milestone = create(:milestone, project: project, iid: 45)
expect(pull_request.attributes.fetch(:milestone)).to eq milestone expect(pull_request.attributes.fetch(:milestone)).to eq milestone
...@@ -158,36 +164,16 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -158,36 +164,16 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end end
describe '#valid?' do describe '#valid?' do
let(:invalid_branch) { double(ref: 'invalid-branch').as_null_object } context 'when source, and target repos are not a fork' do
let(:raw_data) { double(base_data) }
context 'when source, and target repositories are the same' do
context 'and source and target branches exists' do
let(:raw_data) { double(base_data.merge(head: source_branch, base: target_branch)) }
it 'returns true' do it 'returns true' do
expect(pull_request.valid?).to eq true expect(pull_request.valid?).to eq true
end end
end end
context 'and source branch doesn not exists' do
let(:raw_data) { double(base_data.merge(head: invalid_branch, base: target_branch)) }
it 'returns false' do
expect(pull_request.valid?).to eq false
end
end
context 'and target branch doesn not exists' do
let(:raw_data) { double(base_data.merge(head: source_branch, base: invalid_branch)) }
it 'returns false' do
expect(pull_request.valid?).to eq false
end
end
end
context 'when source repo is a fork' do context 'when source repo is a fork' do
let(:source_repo) { double(id: 2, fork: true) } let(:source_repo) { double(id: 2) }
let(:raw_data) { double(base_data) } let(:raw_data) { double(base_data) }
it 'returns false' do it 'returns false' do
...@@ -196,7 +182,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -196,7 +182,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end end
context 'when target repo is a fork' do context 'when target repo is a fork' do
let(:target_repo) { double(id: 2, fork: true) } let(:target_repo) { double(id: 2) }
let(:raw_data) { double(base_data) } let(:raw_data) { double(base_data) }
it 'returns false' do it 'returns false' do
......
...@@ -9,7 +9,7 @@ describe Gitlab::Metrics::Subscribers::RailsCache do ...@@ -9,7 +9,7 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
describe '#cache_read' do describe '#cache_read' do
it 'increments the cache_read duration' do it 'increments the cache_read duration' do
expect(subscriber).to receive(:increment). expect(subscriber).to receive(:increment).
with(:cache_read_duration, event.duration) with(:cache_read, event.duration)
subscriber.cache_read(event) subscriber.cache_read(event)
end end
...@@ -18,7 +18,7 @@ describe Gitlab::Metrics::Subscribers::RailsCache do ...@@ -18,7 +18,7 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
describe '#cache_write' do describe '#cache_write' do
it 'increments the cache_write duration' do it 'increments the cache_write duration' do
expect(subscriber).to receive(:increment). expect(subscriber).to receive(:increment).
with(:cache_write_duration, event.duration) with(:cache_write, event.duration)
subscriber.cache_write(event) subscriber.cache_write(event)
end end
...@@ -27,7 +27,7 @@ describe Gitlab::Metrics::Subscribers::RailsCache do ...@@ -27,7 +27,7 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
describe '#cache_delete' do describe '#cache_delete' do
it 'increments the cache_delete duration' do it 'increments the cache_delete duration' do
expect(subscriber).to receive(:increment). expect(subscriber).to receive(:increment).
with(:cache_delete_duration, event.duration) with(:cache_delete, event.duration)
subscriber.cache_delete(event) subscriber.cache_delete(event)
end end
...@@ -36,7 +36,7 @@ describe Gitlab::Metrics::Subscribers::RailsCache do ...@@ -36,7 +36,7 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
describe '#cache_exist?' do describe '#cache_exist?' do
it 'increments the cache_exists duration' do it 'increments the cache_exists duration' do
expect(subscriber).to receive(:increment). expect(subscriber).to receive(:increment).
with(:cache_exists_duration, event.duration) with(:cache_exists, event.duration)
subscriber.cache_exist?(event) subscriber.cache_exist?(event)
end end
...@@ -61,10 +61,16 @@ describe Gitlab::Metrics::Subscribers::RailsCache do ...@@ -61,10 +61,16 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
expect(transaction).to receive(:increment). expect(transaction).to receive(:increment).
with(:cache_duration, event.duration) with(:cache_duration, event.duration)
expect(transaction).to receive(:increment).
with(:cache_count, 1)
expect(transaction).to receive(:increment). expect(transaction).to receive(:increment).
with(:cache_delete_duration, event.duration) with(:cache_delete_duration, event.duration)
subscriber.increment(:cache_delete_duration, event.duration) expect(transaction).to receive(:increment).
with(:cache_delete_count, 1)
subscriber.increment(:cache_delete, event.duration)
end end
end end
end end
......
require 'spec_helper'
describe LegacyDiffNote, models: true do
describe "Commit diff line notes" do
let!(:note) { create(:note_on_commit_diff, note: "+1 from me") }
let!(:commit) { note.noteable }
it "should save a valid note" do
expect(note.commit_id).to eq(commit.id)
expect(note.noteable.id).to eq(commit.id)
end
it "should be recognized by #legacy_diff_note?" do
expect(note).to be_legacy_diff_note
end
end
describe '#active?' do
it 'is always true when the note has no associated diff' do
note = build(:note_on_merge_request_diff)
expect(note).to receive(:diff).and_return(nil)
expect(note).to be_active
end
it 'is never true when the note has no noteable associated' do
note = build(:note_on_merge_request_diff)
expect(note).to receive(:diff).and_return(double)
expect(note).to receive(:noteable).and_return(nil)
expect(note).not_to be_active
end
it 'returns the memoized value if defined' do
note = build(:note_on_merge_request_diff)
note.instance_variable_set(:@active, 'foo')
expect(note).not_to receive(:find_noteable_diff)
expect(note.active?).to eq 'foo'
end
context 'for a merge request noteable' do
it 'is false when noteable has no matching diff' do
merge = build_stubbed(:merge_request, :simple)
note = build(:note_on_merge_request_diff, noteable: merge)
allow(note).to receive(:diff).and_return(double)
expect(note).to receive(:find_noteable_diff).and_return(nil)
expect(note).not_to be_active
end
it 'is true when noteable has a matching diff' do
merge = create(:merge_request, :simple)
# Generate a real line_code value so we know it will match. We use a
# random line from a random diff just for funsies.
diff = merge.diffs.to_a.sample
line = Gitlab::Diff::Parser.new.parse(diff.diff.each_line).to_a.sample
code = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos)
# We're persisting in order to trigger the set_diff callback
note = create(:note_on_merge_request_diff, noteable: merge, line_code: code)
# Make sure we don't get a false positive from a guard clause
expect(note).to receive(:find_noteable_diff).and_call_original
expect(note).to be_active
end
end
end
end
...@@ -64,7 +64,13 @@ describe MergeRequest, models: true do ...@@ -64,7 +64,13 @@ describe MergeRequest, models: true do
describe '#target_sha' do describe '#target_sha' do
context 'when the target branch does not exist anymore' do context 'when the target branch does not exist anymore' do
subject { create(:merge_request).tap { |mr| mr.update_attribute(:target_branch, 'deleted') } } let(:project) { create(:project) }
subject { create(:merge_request, source_project: project, target_project: project) }
before do
project.repository.raw_repository.delete_branch(subject.target_branch)
end
it 'returns nil' do it 'returns nil' do
expect(subject.target_sha).to be_nil expect(subject.target_sha).to be_nil
...@@ -289,7 +295,12 @@ describe MergeRequest, models: true do ...@@ -289,7 +295,12 @@ describe MergeRequest, models: true do
let(:fork_project) { create(:project, forked_from_project: project) } let(:fork_project) { create(:project, forked_from_project: project) }
context 'when the target branch does not exist anymore' do context 'when the target branch does not exist anymore' do
subject { create(:merge_request).tap { |mr| mr.update_attribute(:target_branch, 'deleted') } } subject { create(:merge_request, source_project: project, target_project: project) }
before do
project.repository.raw_repository.delete_branch(subject.target_branch)
subject.reload
end
it 'does not crash' do it 'does not crash' do
expect{ subject.diverged_commits_count }.not_to raise_error expect{ subject.diverged_commits_count }.not_to raise_error
......
...@@ -204,4 +204,37 @@ describe Milestone, models: true do ...@@ -204,4 +204,37 @@ describe Milestone, models: true do
to eq([milestone]) to eq([milestone])
end end
end end
describe '.upcoming_ids_by_projects' do
let(:project_1) { create(:empty_project) }
let(:project_2) { create(:empty_project) }
let(:project_3) { create(:empty_project) }
let(:projects) { [project_1, project_2, project_3] }
let!(:past_milestone_project_1) { create(:milestone, project: project_1, due_date: Time.now - 1.day) }
let!(:current_milestone_project_1) { create(:milestone, project: project_1, due_date: Time.now + 1.day) }
let!(:future_milestone_project_1) { create(:milestone, project: project_1, due_date: Time.now + 2.days) }
let!(:past_milestone_project_2) { create(:milestone, project: project_2, due_date: Time.now - 1.day) }
let!(:closed_milestone_project_2) { create(:milestone, :closed, project: project_2, due_date: Time.now + 1.day) }
let!(:current_milestone_project_2) { create(:milestone, project: project_2, due_date: Time.now + 2.days) }
let!(:past_milestone_project_3) { create(:milestone, project: project_3, due_date: Time.now - 1.day) }
# The call to `#try` is because this returns a relation with a Postgres DB,
# and an array of IDs with a MySQL DB.
let(:milestone_ids) { Milestone.upcoming_ids_by_projects(projects).map { |id| id.try(:id) || id } }
it 'returns the next upcoming open milestone ID for each project' do
expect(milestone_ids).to contain_exactly(current_milestone_project_1.id, current_milestone_project_2.id)
end
context 'when the projects have no open upcoming milestones' do
let(:projects) { [project_3] }
it 'returns no results' do
expect(milestone_ids).to be_empty
end
end
end
end end
...@@ -34,24 +34,6 @@ describe Note, models: true do ...@@ -34,24 +34,6 @@ describe Note, models: true do
end end
end end
describe "Commit diff line notes" do
let!(:note) { create(:note_on_commit_diff, note: "+1 from me") }
let!(:commit) { note.noteable }
it "should save a valid note" do
expect(note.commit_id).to eq(commit.id)
expect(note.noteable.id).to eq(commit.id)
end
it "should be recognized by #for_diff_line?" do
expect(note).to be_for_diff_line
end
it "should be recognized by #for_commit_diff_line?" do
expect(note).to be_for_commit_diff_line
end
end
describe 'authorization' do describe 'authorization' do
before do before do
@p1 = create(:project) @p1 = create(:project)
...@@ -148,66 +130,6 @@ describe Note, models: true do ...@@ -148,66 +130,6 @@ describe Note, models: true do
end end
end end
describe '#active?' do
it 'is always true when the note has no associated diff' do
note = build(:note)
expect(note).to receive(:diff).and_return(nil)
expect(note).to be_active
end
it 'is never true when the note has no noteable associated' do
note = build(:note)
expect(note).to receive(:diff).and_return(double)
expect(note).to receive(:noteable).and_return(nil)
expect(note).not_to be_active
end
it 'returns the memoized value if defined' do
note = build(:note)
expect(note).to receive(:diff).and_return(double)
expect(note).to receive(:noteable).and_return(double)
note.instance_variable_set(:@active, 'foo')
expect(note).not_to receive(:find_noteable_diff)
expect(note.active?).to eq 'foo'
end
context 'for a merge request noteable' do
it 'is false when noteable has no matching diff' do
merge = build_stubbed(:merge_request, :simple)
note = build(:note, noteable: merge)
allow(note).to receive(:diff).and_return(double)
expect(note).to receive(:find_noteable_diff).and_return(nil)
expect(note).not_to be_active
end
it 'is true when noteable has a matching diff' do
merge = create(:merge_request, :simple)
# Generate a real line_code value so we know it will match. We use a
# random line from a random diff just for funsies.
diff = merge.diffs.to_a.sample
line = Gitlab::Diff::Parser.new.parse(diff.diff.each_line).to_a.sample
code = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos)
# We're persisting in order to trigger the set_diff callback
note = create(:note, noteable: merge, line_code: code)
# Make sure we don't get a false positive from a guard clause
expect(note).to receive(:find_noteable_diff).and_call_original
expect(note).to be_active
end
end
end
describe "editable?" do describe "editable?" do
it "returns true" do it "returns true" do
note = build(:note) note = build(:note)
...@@ -258,7 +180,7 @@ describe Note, models: true do ...@@ -258,7 +180,7 @@ describe Note, models: true do
end end
it "is not an award emoji when comment is on a diff" do it "is not an award emoji when comment is on a diff" do
note = create(:note, note: ":blowfish:", noteable: merge_request, line_code: "11d5d2e667e9da4f7f610f81d86c974b146b13bd_0_2") note = create(:note_on_merge_request_diff, note: ":blowfish:", noteable: merge_request, line_code: "11d5d2e667e9da4f7f610f81d86c974b146b13bd_0_2")
note = note.reload note = note.reload
expect(note.note).to eq(":blowfish:") expect(note.note).to eq(":blowfish:")
......
...@@ -38,7 +38,8 @@ describe ProjectWiki, models: true do ...@@ -38,7 +38,8 @@ describe ProjectWiki, models: true do
describe "#wiki_base_path" do describe "#wiki_base_path" do
it "returns the wiki base path" do it "returns the wiki base path" do
wiki_base_path = "/#{project.path_with_namespace}/wikis" wiki_base_path = "#{Gitlab.config.gitlab.relative_url_root}/#{project.path_with_namespace}/wikis"
expect(subject.wiki_base_path).to eq(wiki_base_path) expect(subject.wiki_base_path).to eq(wiki_base_path)
end end
end end
......
...@@ -141,6 +141,7 @@ describe User, models: true do ...@@ -141,6 +141,7 @@ describe User, models: true do
end end
describe '#confirm' do describe '#confirm' do
before { allow(current_application_settings).to receive(:send_user_confirmation_email).and_return(true) }
let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: 'test@gitlab.com') } let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: 'test@gitlab.com') }
it 'returns unconfirmed' do it 'returns unconfirmed' do
......
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