mirror of
https://github.com/wahyd4/gitlabhq.git
synced 2026-08-13 14:46:05 +10:00
Merge remote-tracking branch 'origin/master' into with-pipeline-view
This commit is contained in:
@@ -5,6 +5,7 @@ v 8.8.0 (unreleased)
|
||||
- Fix error when using link to uploads in global snippets
|
||||
- Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen)
|
||||
- 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.
|
||||
- Escape HTML in commit titles in system note messages
|
||||
- Improve multiple branch push performance by memoizing permission checking
|
||||
@@ -15,6 +16,7 @@ v 8.8.0 (unreleased)
|
||||
- Make build status canceled if any of the jobs was canceled and none failed
|
||||
- Upgrade Sidekiq to 4.1.2
|
||||
- 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
|
||||
- Bump mail_room to 0.7.0 to fix stuck IDLE connections
|
||||
- Remove future dates from contribution calendar graph.
|
||||
@@ -25,9 +27,11 @@ v 8.8.0 (unreleased)
|
||||
- Update SVG sanitizer to conform to SVG 1.1
|
||||
- Speed up push emails with multiple recipients by only generating the email once
|
||||
- Updated search UI
|
||||
- Added authentication service for Container Registry
|
||||
- Display informative message when new milestone is created
|
||||
- Sanitize milestones and labels titles
|
||||
- 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)
|
||||
- Added button to toggle whitespaces changes on diff view
|
||||
- Backport GitHub Enterprise import support from EE
|
||||
@@ -43,7 +47,7 @@ v 8.8.0 (unreleased)
|
||||
- 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)
|
||||
- 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)
|
||||
- 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
|
||||
@@ -51,6 +55,11 @@ v 8.8.0 (unreleased)
|
||||
- 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
|
||||
- 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
|
||||
- Fix relative links in wiki pages. !4050
|
||||
|
||||
@@ -36,6 +36,7 @@ gem 'omniauth-shibboleth', '~> 1.2.0'
|
||||
gem 'omniauth-twitter', '~> 1.2.0'
|
||||
gem 'omniauth_crowd', '~> 2.2.0'
|
||||
gem 'rack-oauth2', '~> 1.2.1'
|
||||
gem 'jwt'
|
||||
|
||||
# Spam and anti-bot protection
|
||||
gem 'recaptcha', require: 'recaptcha/rails'
|
||||
@@ -224,6 +225,7 @@ gem 'request_store', '~> 1.3.0'
|
||||
gem 'select2-rails', '~> 3.5.9'
|
||||
gem 'virtus', '~> 1.0.1'
|
||||
gem 'net-ssh', '~> 3.0.1'
|
||||
gem 'base32', '~> 0.3.0'
|
||||
|
||||
# Sentry integration
|
||||
gem 'sentry-raven', '~> 0.15'
|
||||
|
||||
@@ -70,6 +70,7 @@ GEM
|
||||
ice_nine (~> 0.11.0)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
babosa (1.0.2)
|
||||
base32 (0.3.2)
|
||||
bcrypt (3.1.10)
|
||||
benchmark-ips (2.3.0)
|
||||
better_errors (1.0.1)
|
||||
@@ -893,6 +894,7 @@ DEPENDENCIES
|
||||
attr_encrypted (~> 1.3.4)
|
||||
awesome_print (~> 1.2.0)
|
||||
babosa (~> 1.0.2)
|
||||
base32 (~> 0.3.0)
|
||||
benchmark-ips
|
||||
better_errors (~> 1.0.1)
|
||||
binding_of_caller (~> 0.7.2)
|
||||
@@ -954,6 +956,7 @@ DEPENDENCIES
|
||||
jquery-rails (~> 4.1.0)
|
||||
jquery-turbolinks (~> 2.1.0)
|
||||
jquery-ui-rails (~> 5.0.0)
|
||||
jwt
|
||||
kaminari (~> 0.16.3)
|
||||
letter_opener_web (~> 1.3.0)
|
||||
licensee (~> 8.0.0)
|
||||
|
||||
@@ -285,6 +285,7 @@ class @Notes
|
||||
form.addClass "js-main-target-form"
|
||||
|
||||
form.find("#note_line_code").remove()
|
||||
form.find("#note_type").remove()
|
||||
|
||||
###
|
||||
General note form setup.
|
||||
@@ -472,6 +473,7 @@ class @Notes
|
||||
setupDiscussionNoteForm: (dataHolder, form) =>
|
||||
# setup note target
|
||||
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("#note_commit_id").val dataHolder.data("commitId")
|
||||
form.find("#note_line_code").val dataHolder.data("lineCode")
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
@mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) {
|
||||
.page-with-sidebar {
|
||||
.header-logo {
|
||||
background: $color-darker;
|
||||
|
||||
a {
|
||||
color: $color-light;
|
||||
|
||||
@@ -88,8 +90,8 @@
|
||||
}
|
||||
|
||||
$theme-blue: #2980b9;
|
||||
$theme-charcoal: #333c47;
|
||||
$theme-graphite: #888;
|
||||
$theme-charcoal: #3d454d;
|
||||
$theme-graphite: #666;
|
||||
$theme-gray: #373737;
|
||||
$theme-green: #019875;
|
||||
$theme-violet: #548;
|
||||
@@ -100,11 +102,11 @@ body {
|
||||
}
|
||||
|
||||
&.ui_charcoal {
|
||||
@include gitlab-theme(#c5d0de, $theme-charcoal, #2b333d, #24272d);
|
||||
@include gitlab-theme(#d6d7d9, #485157, $theme-charcoal, #353b41);
|
||||
}
|
||||
|
||||
&.ui_graphite {
|
||||
@include gitlab-theme(#ccc, $theme-graphite, #777, #666);
|
||||
@include gitlab-theme(#ccc, #777, $theme-graphite, #555);
|
||||
}
|
||||
|
||||
&.ui_gray {
|
||||
|
||||
@@ -226,8 +226,7 @@ ul.notes {
|
||||
}
|
||||
}
|
||||
|
||||
.note-action-button,
|
||||
.discussion-action-button {
|
||||
.note-action-button {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
line-height: 24px;
|
||||
|
||||
@@ -106,6 +106,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
|
||||
:email_author_in_body,
|
||||
:repository_checks_enabled,
|
||||
:metrics_packet_size,
|
||||
:send_user_confirmation_email,
|
||||
restricted_visibility_levels: [],
|
||||
import_sources: [],
|
||||
disabled_oauth_sign_in_sources: []
|
||||
|
||||
@@ -119,6 +119,7 @@ class Admin::UsersController < Admin::ApplicationController
|
||||
user_params_with_pass.merge!(
|
||||
password: params[:user][:password],
|
||||
password_confirmation: params[:user][:password_confirmation],
|
||||
password_expires_at: Time.now
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
class JwtController < ApplicationController
|
||||
skip_before_action :authenticate_user!
|
||||
skip_before_action :verify_authenticity_token
|
||||
before_action :authenticate_project_or_user
|
||||
|
||||
SERVICES = {
|
||||
Auth::ContainerRegistryAuthenticationService::AUDIENCE => Auth::ContainerRegistryAuthenticationService,
|
||||
}
|
||||
|
||||
def auth
|
||||
service = SERVICES[params[:service]]
|
||||
return head :not_found unless service
|
||||
|
||||
result = service.new(@project, @user, auth_params).execute
|
||||
|
||||
render json: result, status: result[:http_status]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authenticate_project_or_user
|
||||
authenticate_with_http_basic do |login, password|
|
||||
# if it's possible we first try to authenticate project with login and password
|
||||
@project = authenticate_project(login, password)
|
||||
return if @project
|
||||
|
||||
@user = authenticate_user(login, password)
|
||||
return if @user
|
||||
|
||||
render_403
|
||||
end
|
||||
end
|
||||
|
||||
def auth_params
|
||||
params.permit(:service, :scope, :offline_token, :account, :client_id)
|
||||
end
|
||||
|
||||
def authenticate_project(login, password)
|
||||
if login == 'gitlab_ci_token'
|
||||
Project.find_by(builds_enabled: true, runners_token: password)
|
||||
end
|
||||
end
|
||||
|
||||
def authenticate_user(login, password)
|
||||
# TODO: this is a copy and paste from grack_auth,
|
||||
# it should be refactored in the future
|
||||
|
||||
user = Gitlab::Auth.new.find(login, password)
|
||||
|
||||
# If the user authenticated successfully, we reset the auth failure count
|
||||
# from Rack::Attack for that IP. A client may attempt to authenticate
|
||||
# with a username and blank password first, and only after it receives
|
||||
# a 401 error does it present a password. Resetting the count prevents
|
||||
# false positives from occurring.
|
||||
#
|
||||
# Otherwise, we let Rack::Attack know there was a failed authentication
|
||||
# attempt from this IP. This information is stored in the Rails cache
|
||||
# (Redis) and will be used by the Rack::Attack middleware to decide
|
||||
# whether to block requests from this IP.
|
||||
config = Gitlab.config.rack_attack.git_basic_auth
|
||||
|
||||
if config.enabled
|
||||
if user
|
||||
# A successful login will reset the auth failure count from this IP
|
||||
Rack::Attack::Allow2Ban.reset(request.ip, config)
|
||||
else
|
||||
banned = Rack::Attack::Allow2Ban.filter(request.ip, config) do
|
||||
# Unless the IP is whitelisted, return true so that Allow2Ban
|
||||
# increments the counter (stored in Rails.cache) for the IP
|
||||
if config.ip_whitelist.include?(request.ip)
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
if banned
|
||||
Rails.logger.info "IP #{request.ip} failed to login " \
|
||||
"as #{login} but has been temporarily banned from Git auth"
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
user
|
||||
end
|
||||
end
|
||||
@@ -17,12 +17,12 @@ class Projects::CommitController < Projects::ApplicationController
|
||||
def show
|
||||
apply_diff_view_cookie!
|
||||
|
||||
@line_notes = commit.notes.inline
|
||||
@grouped_diff_notes = commit.notes.grouped_diff_notes
|
||||
|
||||
@note = @project.build_commit_note(commit)
|
||||
@notes = commit.notes.not_inline.fresh
|
||||
@notes = commit.notes.non_diff_notes.fresh
|
||||
@noteable = @commit
|
||||
@comments_allowed = @reply_allowed = true
|
||||
@comments_target = {
|
||||
@comments_target = {
|
||||
noteable_type: 'Commit',
|
||||
commit_id: @commit.id
|
||||
}
|
||||
@@ -67,10 +67,10 @@ class Projects::CommitController < Projects::ApplicationController
|
||||
create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title} has been successfully reverted.",
|
||||
success_path: successful_change_path, failure_path: failed_change_path)
|
||||
end
|
||||
|
||||
|
||||
def cherry_pick
|
||||
assign_change_commit_vars(@commit.cherry_pick_branch_name)
|
||||
|
||||
|
||||
return render_404 if @target_branch.blank?
|
||||
|
||||
create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title} has been successfully cherry-picked.",
|
||||
|
||||
@@ -22,7 +22,8 @@ class Projects::CompareController < Projects::ApplicationController
|
||||
@base_commit = @project.merge_base_commit(@base_ref, @head_ref)
|
||||
@diffs = compare.diffs(diff_options)
|
||||
@diff_refs = [@base_commit, @commit]
|
||||
@line_notes = []
|
||||
@diff_notes_disabled = true
|
||||
@grouped_diff_notes = {}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -73,12 +73,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||
# but we need it for the "View file @ ..." link by deleted files
|
||||
@base_commit ||= @merge_request.first_commit.parent || @merge_request.first_commit
|
||||
|
||||
@comments_allowed = @reply_allowed = true
|
||||
@comments_target = {
|
||||
noteable_type: 'MergeRequest',
|
||||
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|
|
||||
format.html
|
||||
@@ -117,6 +117,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||
@commit = @merge_request.last_commit
|
||||
@base_commit = @merge_request.diff_base_commit
|
||||
@diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare
|
||||
@diff_notes_disabled = true
|
||||
|
||||
@ci_commit = @merge_request.ci_commit
|
||||
@statuses = @ci_commit.statuses if @ci_commit
|
||||
@@ -300,7 +301,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||
# Build a note object for comment form
|
||||
@note = @project.notes.new(noteable: @merge_request)
|
||||
@notes = @merge_request.mr_and_commit_notes.nonawards.inc_author.fresh
|
||||
@discussions = Note.discussions_from_notes(@notes)
|
||||
@discussions = @notes.discussions
|
||||
@noteable = @merge_request
|
||||
|
||||
# Get commits from repository
|
||||
|
||||
@@ -96,7 +96,7 @@ class Projects::NotesController < Projects::ApplicationController
|
||||
end
|
||||
|
||||
def note_to_discussion_html(note)
|
||||
return unless note.for_diff_line?
|
||||
return unless note.diff_note?
|
||||
|
||||
if params[:view] == 'parallel'
|
||||
template = "projects/notes/_diff_notes_with_reply_parallel"
|
||||
@@ -120,7 +120,7 @@ class Projects::NotesController < Projects::ApplicationController
|
||||
end
|
||||
|
||||
def note_to_discussion_with_diff_html(note)
|
||||
return unless note.for_diff_line?
|
||||
return unless note.diff_note?
|
||||
|
||||
render_to_string(
|
||||
"projects/notes/_discussion",
|
||||
@@ -158,7 +158,7 @@ class Projects::NotesController < Projects::ApplicationController
|
||||
def note_params
|
||||
params.require(:note).permit(
|
||||
:note, :noteable, :noteable_id, :noteable_type, :project_id,
|
||||
:attachment, :line_code, :commit_id
|
||||
:attachment, :line_code, :commit_id, :type
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -235,7 +235,8 @@ class ProjectsController < Projects::ApplicationController
|
||||
def project_params
|
||||
params.require(:project).permit(
|
||||
:name, :path, :description, :issues_tracker, :tag_list, :runners_token,
|
||||
:issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch,
|
||||
:issues_enabled, :merge_requests_enabled, :snippets_enabled, :container_registry_enabled,
|
||||
:issues_tracker_id, :default_branch,
|
||||
:wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
|
||||
:builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
|
||||
:public_builds,
|
||||
|
||||
@@ -37,8 +37,8 @@ class RegistrationsController < Devise::RegistrationsController
|
||||
super
|
||||
end
|
||||
|
||||
def after_sign_up_path_for(_resource)
|
||||
users_almost_there_path
|
||||
def after_sign_up_path_for(user)
|
||||
user.confirmed_at.present? ? dashboard_projects_path : users_almost_there_path
|
||||
end
|
||||
|
||||
def after_inactive_sign_up_path_for(_resource)
|
||||
|
||||
@@ -252,8 +252,8 @@ class IssuableFinder
|
||||
if filter_by_no_milestone?
|
||||
items = items.where(milestone_id: [-1, nil])
|
||||
elsif filter_by_upcoming_milestone?
|
||||
upcoming = Milestone.where(project_id: projects).upcoming
|
||||
items = items.joins(:milestone).where(milestones: { title: upcoming.try(:title) })
|
||||
upcoming_ids = Milestone.upcoming_ids_by_projects(projects)
|
||||
items = items.joins(:milestone).where(milestone_id: upcoming_ids)
|
||||
else
|
||||
items = items.joins(:milestone).where(milestones: { title: params[:milestone_title] })
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ class NotesFinder
|
||||
notes =
|
||||
case target_type
|
||||
when "commit"
|
||||
project.notes.for_commit_id(target_id).not_inline
|
||||
project.notes.for_commit_id(target_id).non_diff_notes
|
||||
when "issue"
|
||||
project.issues.find(target_id).notes.nonawards.inc_author
|
||||
when "merge_request"
|
||||
|
||||
@@ -55,22 +55,18 @@ module DiffHelper
|
||||
end
|
||||
end
|
||||
|
||||
def line_comments
|
||||
@line_comments ||= @line_notes.select(&:active?).sort_by(&:created_at).group_by(&:line_code)
|
||||
end
|
||||
def organize_comments(left, right)
|
||||
notes_left = notes_right = nil
|
||||
|
||||
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'
|
||||
comments_left = line_comments[line_code_left]
|
||||
unless left[:type].nil? && right[:type] == 'new'
|
||||
notes_left = @grouped_diff_notes[left[:line_code]]
|
||||
end
|
||||
|
||||
unless type_left.nil? && type_right.nil?
|
||||
comments_right = line_comments[line_code_right]
|
||||
unless left[:type].nil? && right[:type].nil?
|
||||
notes_right = @grouped_diff_notes[right[:line_code]]
|
||||
end
|
||||
|
||||
[comments_left, comments_right]
|
||||
[notes_left, notes_right]
|
||||
end
|
||||
|
||||
def inline_diff_btn
|
||||
@@ -96,8 +92,8 @@ module DiffHelper
|
||||
].join(' ').html_safe
|
||||
end
|
||||
|
||||
def commit_for_diff(diff)
|
||||
if diff.deleted_file
|
||||
def commit_for_diff(diff_file)
|
||||
if diff_file.deleted_file
|
||||
@base_commit || @commit.parent || @commit
|
||||
else
|
||||
@commit
|
||||
|
||||
@@ -3,7 +3,7 @@ module EventsHelper
|
||||
author = event.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
|
||||
event.author_name
|
||||
end
|
||||
@@ -57,11 +57,7 @@ module EventsHelper
|
||||
words << event.ref_name
|
||||
words << "at"
|
||||
elsif event.commented?
|
||||
if event.note_commit?
|
||||
words << event.note_short_commit_id
|
||||
else
|
||||
words << "##{truncate event.note_target_iid}"
|
||||
end
|
||||
words << event.note_target_reference
|
||||
words << "at"
|
||||
elsif event.milestone?
|
||||
words << "##{event.target_iid}" if event.target_iid
|
||||
@@ -84,21 +80,12 @@ module EventsHelper
|
||||
elsif event.merge_request?
|
||||
namespace_project_merge_request_url(event.project.namespace,
|
||||
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,
|
||||
event.note_target)
|
||||
elsif event.note?
|
||||
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)
|
||||
end
|
||||
event_note_target_path(event)
|
||||
end
|
||||
elsif event.push?
|
||||
push_event_feed_url(event)
|
||||
@@ -134,42 +121,30 @@ module EventsHelper
|
||||
end
|
||||
|
||||
def event_note_target_path(event)
|
||||
if event.note? && event.note_commit?
|
||||
namespace_project_commit_path(event.project.namespace, event.project,
|
||||
event.note_target)
|
||||
if event.note? && event.commit_note?
|
||||
namespace_project_commit_path(event.project.namespace,
|
||||
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
|
||||
polymorphic_path([event.project.namespace.becomes(Namespace),
|
||||
event.project, event.note_target],
|
||||
anchor: dom_id(event.target))
|
||||
anchor: dom_id(event.target))
|
||||
end
|
||||
end
|
||||
|
||||
def event_note_title_html(event)
|
||||
if event.note_target
|
||||
if event.note_commit?
|
||||
link_to(
|
||||
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
|
||||
else
|
||||
link_to event_note_target_path(event) do
|
||||
"#{event.note_target_type} #{truncate event.note_target.to_reference}"
|
||||
end
|
||||
link_to(event_note_target_path(event), title: event.target_title, class: 'has-tooltip') do
|
||||
"#{event.note_target_type} #{event.note_target_reference}"
|
||||
end
|
||||
else
|
||||
content_tag :strong do
|
||||
"(deleted)"
|
||||
end
|
||||
content_tag(:strong, '(deleted)')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+13
-16
@@ -1,7 +1,7 @@
|
||||
module NotesHelper
|
||||
# Helps to distinguish e.g. commit notes in mr notes list
|
||||
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
|
||||
|
||||
def note_target_fields(note)
|
||||
@@ -15,16 +15,6 @@ module NotesHelper
|
||||
note.editable? && can?(current_user, :admin_note, note)
|
||||
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)
|
||||
{
|
||||
id: noteable.id,
|
||||
@@ -35,7 +25,7 @@ module NotesHelper
|
||||
end
|
||||
|
||||
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_id] || @comments_target[:commit_id],
|
||||
line_code
|
||||
@@ -45,9 +35,10 @@ module NotesHelper
|
||||
noteable_type: @comments_target[:noteable_type],
|
||||
noteable_id: @comments_target[:noteable_id],
|
||||
commit_id: @comments_target[:commit_id],
|
||||
line_type: line_type,
|
||||
line_code: line_code,
|
||||
discussion_id: discussion_id,
|
||||
line_type: line_type
|
||||
note_type: LegacyDiffNote.name,
|
||||
discussion_id: discussion_id
|
||||
}
|
||||
|
||||
button_tag(class: 'btn add-diff-note js-add-diff-note-button',
|
||||
@@ -57,18 +48,24 @@ module NotesHelper
|
||||
end
|
||||
end
|
||||
|
||||
def link_to_reply_diff(note, line_type = nil)
|
||||
def link_to_reply_discussion(note, line_type = nil)
|
||||
return unless current_user
|
||||
|
||||
data = {
|
||||
noteable_type: note.noteable_type,
|
||||
noteable_id: note.noteable_id,
|
||||
commit_id: note.commit_id,
|
||||
line_code: note.line_code,
|
||||
discussion_id: note.discussion_id,
|
||||
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',
|
||||
data: data, title: 'Add a reply'
|
||||
end
|
||||
|
||||
+10
-1
@@ -61,6 +61,7 @@ class Ability
|
||||
:read_merge_request,
|
||||
:read_note,
|
||||
:read_commit_status,
|
||||
:read_container_image,
|
||||
:download_code
|
||||
]
|
||||
|
||||
@@ -203,6 +204,7 @@ class Ability
|
||||
:admin_label,
|
||||
:read_commit_status,
|
||||
:read_build,
|
||||
:read_container_image,
|
||||
:read_pipeline,
|
||||
]
|
||||
end
|
||||
@@ -219,7 +221,9 @@ class Ability
|
||||
:update_pipeline,
|
||||
:create_merge_request,
|
||||
:create_wiki,
|
||||
:push_code
|
||||
:push_code,
|
||||
:create_container_image,
|
||||
:update_container_image,
|
||||
]
|
||||
end
|
||||
|
||||
@@ -246,6 +250,7 @@ class Ability
|
||||
:admin_project,
|
||||
:admin_commit_status,
|
||||
:admin_build,
|
||||
:admin_container_image,
|
||||
:admin_pipeline
|
||||
]
|
||||
end
|
||||
@@ -292,6 +297,10 @@ class Ability
|
||||
rules += named_abilities('pipeline')
|
||||
end
|
||||
|
||||
unless project.container_registry_enabled
|
||||
rules += named_abilities('container_image')
|
||||
end
|
||||
|
||||
rules
|
||||
end
|
||||
|
||||
|
||||
@@ -120,7 +120,8 @@ class ApplicationSetting < ActiveRecord::Base
|
||||
recaptcha_enabled: false,
|
||||
akismet_enabled: false,
|
||||
repository_checks_enabled: true,
|
||||
disabled_oauth_sign_in_sources: []
|
||||
disabled_oauth_sign_in_sources: [],
|
||||
send_user_confirmation_email: false
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
+15
-20
@@ -80,7 +80,7 @@ class Event < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def target_title
|
||||
target.title if target && target.respond_to?(:title)
|
||||
target.try(:title)
|
||||
end
|
||||
|
||||
def created?
|
||||
@@ -266,28 +266,20 @@ class Event < ActiveRecord::Base
|
||||
branch? && project.default_branch != branch_name
|
||||
end
|
||||
|
||||
def note_commit_id
|
||||
target.commit_id
|
||||
end
|
||||
|
||||
def target_iid
|
||||
target.respond_to?(:iid) ? target.iid : target_id
|
||||
end
|
||||
|
||||
def note_short_commit_id
|
||||
Commit.truncate_sha(note_commit_id)
|
||||
end
|
||||
|
||||
def note_commit?
|
||||
target.noteable_type == "Commit"
|
||||
def commit_note?
|
||||
target.for_commit?
|
||||
end
|
||||
|
||||
def issue_note?
|
||||
note? && target && target.noteable_type == "Issue"
|
||||
note? && target && target.for_issue?
|
||||
end
|
||||
|
||||
def note_project_snippet?
|
||||
target.noteable_type == "Snippet"
|
||||
def project_snippet_note?
|
||||
target.for_snippet?
|
||||
end
|
||||
|
||||
def note_target
|
||||
@@ -295,19 +287,22 @@ class Event < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def note_target_id
|
||||
if note_commit?
|
||||
if commit_note?
|
||||
target.commit_id
|
||||
else
|
||||
target.noteable_id.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def note_target_iid
|
||||
if note_target.respond_to?(:iid)
|
||||
note_target.iid
|
||||
def note_target_reference
|
||||
return unless note_target
|
||||
|
||||
# Commit#to_reference returns the full SHA, but we want the short one here
|
||||
if commit_note?
|
||||
note_target.short_id
|
||||
else
|
||||
note_target_id
|
||||
end.to_s
|
||||
note_target.to_reference
|
||||
end
|
||||
end
|
||||
|
||||
def note_target_type
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
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
|
||||
# when creating new merge request
|
||||
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
|
||||
event :close do
|
||||
transition [:reopened, :opened] => :closed
|
||||
@@ -490,10 +494,14 @@ class MergeRequest < ActiveRecord::Base
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def source_sha
|
||||
return @head_source_sha if defined?(@head_source_sha)
|
||||
|
||||
last_commit.try(:sha) || source_tip.try(:sha)
|
||||
end
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ class MergeRequestDiff < ActiveRecord::Base
|
||||
|
||||
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 :collected
|
||||
@@ -38,8 +38,8 @@ class MergeRequestDiff < ActiveRecord::Base
|
||||
@diffs_no_whitespace ||= begin
|
||||
compare = Gitlab::Git::Compare.new(
|
||||
self.repository.raw_repository,
|
||||
self.target_branch,
|
||||
self.source_sha,
|
||||
self.base,
|
||||
self.head,
|
||||
)
|
||||
compare.diffs(options)
|
||||
end
|
||||
@@ -144,7 +144,7 @@ class MergeRequestDiff < ActiveRecord::Base
|
||||
|
||||
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
|
||||
end
|
||||
@@ -160,10 +160,24 @@ class MergeRequestDiff < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def source_sha
|
||||
return head_source_sha if head_source_sha.present?
|
||||
|
||||
source_commit = merge_request.source_project.commit(source_branch)
|
||||
source_commit.try(:sha)
|
||||
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
|
||||
@compare ||=
|
||||
begin
|
||||
@@ -172,8 +186,8 @@ class MergeRequestDiff < ActiveRecord::Base
|
||||
|
||||
Gitlab::Git::Compare.new(
|
||||
self.repository.raw_repository,
|
||||
self.target_branch,
|
||||
self.source_sha
|
||||
self.base,
|
||||
self.head
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
+12
-2
@@ -67,8 +67,18 @@ class Milestone < ActiveRecord::Base
|
||||
@link_reference_pattern ||= super("milestones", /(?<milestone>\d+)/)
|
||||
end
|
||||
|
||||
def self.upcoming
|
||||
self.where('due_date > ?', Time.now).reorder(due_date: :asc).first
|
||||
def self.upcoming_ids_by_projects(projects)
|
||||
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
|
||||
|
||||
def to_reference(from_project = nil)
|
||||
|
||||
+39
-188
@@ -1,6 +1,5 @@
|
||||
require 'carrierwave/orm/activerecord'
|
||||
|
||||
class Note < ActiveRecord::Base
|
||||
extend ActiveModel::Naming
|
||||
include Gitlab::CurrentSettings
|
||||
include Participable
|
||||
include Mentionable
|
||||
@@ -20,14 +19,13 @@ class Note < ActiveRecord::Base
|
||||
delegate :gfm_reference, :local_reference, to: :noteable
|
||||
delegate :name, to: :project, prefix: true
|
||||
delegate :name, :email, to: :author, prefix: true
|
||||
delegate :title, to: :noteable, allow_nil: true
|
||||
|
||||
before_validation :set_award!
|
||||
before_validation :clear_blank_line_code!
|
||||
|
||||
validates :note, :project, presence: true
|
||||
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 :line_code, line_code: true, allow_blank: true
|
||||
# Attachments are deprecated and are handled by Markdown uploader
|
||||
validates :attachment, file_size: { maximum: :max_attachment_size }
|
||||
|
||||
@@ -41,8 +39,6 @@ class Note < ActiveRecord::Base
|
||||
scope :awards, ->{ where(is_award: true) }
|
||||
scope :nonawards, ->{ where(is_award: false) }
|
||||
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 :user, ->{ where(system: false) }
|
||||
scope :common, ->{ where(noteable_type: ["", nil]) }
|
||||
@@ -50,38 +46,31 @@ class Note < ActiveRecord::Base
|
||||
scope :inc_author_project, ->{ includes(:project, :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
|
||||
includes(:author, :noteable, :updated_by,
|
||||
project: [:project_members, { group: [:group_members] }])
|
||||
end
|
||||
|
||||
serialize :st_diff
|
||||
before_create :set_diff, if: ->(n) { n.line_code.present? }
|
||||
before_validation :clear_blank_line_code!
|
||||
|
||||
class << self
|
||||
def discussions_from_notes(notes)
|
||||
discussion_ids = []
|
||||
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
|
||||
|
||||
discussions
|
||||
def model_name
|
||||
ActiveModel::Name.new(self, nil, 'note')
|
||||
end
|
||||
|
||||
def build_discussion_id(type, id, line_code)
|
||||
[:discussion, type.try(:underscore), id, line_code].join("-").to_sym
|
||||
def build_discussion_id(noteable_type, noteable_id)
|
||||
[:discussion, noteable_type.try(:underscore), noteable_id].join("-")
|
||||
end
|
||||
|
||||
def discussions
|
||||
all.group_by(&:discussion_id).values
|
||||
end
|
||||
|
||||
def grouped_diff_notes
|
||||
legacy_diff_notes.select(&:active?).sort_by(&:created_at).group_by(&:line_code)
|
||||
end
|
||||
|
||||
# Searches for notes matching the given query.
|
||||
@@ -116,167 +105,39 @@ class Note < ActiveRecord::Base
|
||||
system && SystemNoteService.cross_reference?(note)
|
||||
end
|
||||
|
||||
def max_attachment_size
|
||||
current_application_settings.max_attachment_size.megabytes.to_i
|
||||
def diff_note?
|
||||
false
|
||||
end
|
||||
|
||||
def find_diff
|
||||
return nil unless noteable
|
||||
return @diff if defined?(@diff)
|
||||
def legacy_diff_note?
|
||||
false
|
||||
end
|
||||
|
||||
# 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
|
||||
def active?
|
||||
true
|
||||
end
|
||||
|
||||
def discussion_id
|
||||
@discussion_id ||=
|
||||
if for_merge_request?
|
||||
[:discussion, :note, id].join("-")
|
||||
else
|
||||
self.class.build_discussion_id(noteable_type, noteable_id || commit_id)
|
||||
end
|
||||
end
|
||||
|
||||
def max_attachment_size
|
||||
current_application_settings.max_attachment_size.megabytes.to_i
|
||||
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
|
||||
|
||||
def diff
|
||||
@diff ||= Gitlab::Git::Diff.new(st_diff) if st_diff.respond_to?(:map)
|
||||
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?
|
||||
return true unless self.diff
|
||||
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
|
||||
|
||||
@diff_line
|
||||
end
|
||||
|
||||
def diff_line_type
|
||||
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
|
||||
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
|
||||
|
||||
def diff_lines
|
||||
@diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
|
||||
end
|
||||
|
||||
def highlighted_diff_lines
|
||||
Gitlab::Diff::Highlight.new(diff_lines).highlight
|
||||
end
|
||||
|
||||
def discussion_id
|
||||
@discussion_id ||= Note.build_discussion_id(noteable_type, noteable_id || commit_id, line_code)
|
||||
end
|
||||
|
||||
def for_commit?
|
||||
noteable_type == "Commit"
|
||||
end
|
||||
|
||||
def for_commit_diff_line?
|
||||
for_commit? && for_diff_line?
|
||||
end
|
||||
|
||||
def for_diff_line?
|
||||
line_code.present?
|
||||
end
|
||||
|
||||
def for_issue?
|
||||
noteable_type == "Issue"
|
||||
end
|
||||
@@ -285,10 +146,6 @@ class Note < ActiveRecord::Base
|
||||
noteable_type == "MergeRequest"
|
||||
end
|
||||
|
||||
def for_merge_request_diff_line?
|
||||
for_merge_request? && for_diff_line?
|
||||
end
|
||||
|
||||
def for_snippet?
|
||||
noteable_type == "Snippet"
|
||||
end
|
||||
@@ -361,14 +218,8 @@ class Note < ActiveRecord::Base
|
||||
self.line_code = nil if self.line_code.blank?
|
||||
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?
|
||||
(for_issue? || for_merge_request?) && !for_diff_line?
|
||||
(for_issue? || for_merge_request?) && !diff_note?
|
||||
end
|
||||
|
||||
def contains_emoji_only?
|
||||
|
||||
@@ -22,6 +22,7 @@ class Project < ActiveRecord::Base
|
||||
default_value_for :builds_enabled, gitlab_config_features.builds
|
||||
default_value_for :wiki_enabled, gitlab_config_features.wiki
|
||||
default_value_for :snippets_enabled, gitlab_config_features.snippets
|
||||
default_value_for :container_registry_enabled, gitlab_config_features.container_registry
|
||||
default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
|
||||
|
||||
# set last_activity_at to the same as created_at
|
||||
@@ -49,6 +50,8 @@ class Project < ActiveRecord::Base
|
||||
attr_accessor :new_default_branch
|
||||
attr_accessor :old_path_with_namespace
|
||||
|
||||
alias_attribute :title, :name
|
||||
|
||||
# Relations
|
||||
belongs_to :creator, foreign_key: 'creator_id', class_name: 'User'
|
||||
belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id'
|
||||
@@ -327,6 +330,12 @@ class Project < ActiveRecord::Base
|
||||
@repository ||= Repository.new(path_with_namespace, self)
|
||||
end
|
||||
|
||||
def container_registry_url
|
||||
if container_registry_enabled? && Gitlab.config.registry.enabled
|
||||
"#{Gitlab.config.registry.host_with_port}/#{path_with_namespace}"
|
||||
end
|
||||
end
|
||||
|
||||
def commit(id = 'HEAD')
|
||||
repository.commit(id)
|
||||
end
|
||||
|
||||
@@ -40,7 +40,7 @@ class ProjectWiki
|
||||
end
|
||||
|
||||
def wiki_base_path
|
||||
["/", @project.path_with_namespace, "/wikis"].join('')
|
||||
[Gitlab.config.gitlab.relative_url_root, "/", @project.path_with_namespace, "/wikis"].join('')
|
||||
end
|
||||
|
||||
# Returns the Gollum::Wiki object.
|
||||
|
||||
@@ -195,6 +195,10 @@ class Repository
|
||||
cache.fetch(:branch_names) { branches.map(&:name) }
|
||||
end
|
||||
|
||||
def branch_exists?(branch_name)
|
||||
branch_names.include?(branch_name)
|
||||
end
|
||||
|
||||
def tag_names
|
||||
cache.fetch(:tag_names) { raw_repository.tag_names }
|
||||
end
|
||||
|
||||
@@ -112,6 +112,7 @@ class User < ActiveRecord::Base
|
||||
before_save :ensure_external_user_rights
|
||||
after_save :ensure_namespace_correct
|
||||
after_initialize :set_projects_limit
|
||||
before_create :check_confirmation_email
|
||||
after_create :post_create_hook
|
||||
after_destroy :post_destroy_hook
|
||||
|
||||
@@ -307,6 +308,10 @@ class User < ActiveRecord::Base
|
||||
@reset_token
|
||||
end
|
||||
|
||||
def check_confirmation_email
|
||||
skip_confirmation! unless current_application_settings.send_user_confirmation_email
|
||||
end
|
||||
|
||||
def recently_sent_password_reset?
|
||||
reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
|
||||
end
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
module Auth
|
||||
class ContainerRegistryAuthenticationService < BaseService
|
||||
AUDIENCE = 'container_registry'
|
||||
|
||||
def execute
|
||||
return error('not found', 404) unless registry.enabled
|
||||
|
||||
if params[:offline_token]
|
||||
return error('forbidden', 403) unless current_user
|
||||
else
|
||||
return error('forbidden', 403) unless scope
|
||||
end
|
||||
|
||||
{ token: authorized_token(scope).encoded }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authorized_token(*accesses)
|
||||
token = JSONWebToken::RSAToken.new(registry.key)
|
||||
token.issuer = registry.issuer
|
||||
token.audience = params[:service]
|
||||
token.subject = current_user.try(:username)
|
||||
token[:access] = accesses.compact
|
||||
token
|
||||
end
|
||||
|
||||
def scope
|
||||
return unless params[:scope]
|
||||
|
||||
@scope ||= process_scope(params[:scope])
|
||||
end
|
||||
|
||||
def process_scope(scope)
|
||||
type, name, actions = scope.split(':', 3)
|
||||
actions = actions.split(',')
|
||||
return unless type == 'repository'
|
||||
|
||||
process_repository_access(type, name, actions)
|
||||
end
|
||||
|
||||
def process_repository_access(type, name, actions)
|
||||
requested_project = Project.find_with_namespace(name)
|
||||
return unless requested_project
|
||||
|
||||
actions = actions.select do |action|
|
||||
can_access?(requested_project, action)
|
||||
end
|
||||
|
||||
{ type: type, name: name, actions: actions } if actions.present?
|
||||
end
|
||||
|
||||
def can_access?(requested_project, requested_action)
|
||||
return false unless requested_project.container_registry_enabled?
|
||||
|
||||
case requested_action
|
||||
when 'pull'
|
||||
requested_project == project || can?(current_user, :read_container_image, requested_project)
|
||||
when 'push'
|
||||
requested_project == project || can?(current_user, :create_container_image, requested_project)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def registry
|
||||
Gitlab.config.registry
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -103,6 +103,12 @@
|
||||
= f.label :signup_enabled do
|
||||
= f.check_box :signup_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
|
||||
.col-sm-offset-2.col-sm-10
|
||||
.checkbox
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
= event_action_name(event)
|
||||
|
||||
- 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)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- if @note.diff_file_name
|
||||
- if @note.legacy_diff_note?
|
||||
%p.details
|
||||
New comment on diff for
|
||||
= link_to @note.diff_file_name, @target_url
|
||||
= link_to @note.diff_file_path, @target_url
|
||||
\:
|
||||
|
||||
= render 'note_message'
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
= link_text
|
||||
- else
|
||||
= 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)
|
||||
%td.new_line.diff-line-num{ class: type, data: { linenumber: line.new_pos } }
|
||||
- link_text = type == "old" ? " ".html_safe : line.new_pos
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
- else
|
||||
%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]
|
||||
- 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')
|
||||
%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 @@
|
||||
|
||||
%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
|
||||
- if @comments_allowed && can?(current_user, :create_note, @project)
|
||||
= link_to_new_diff_note(right[:line_code], 'new')
|
||||
- if !@diff_notes_disabled && can?(current_user, :create_note, @project)
|
||||
= 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])
|
||||
|
||||
- if @reply_allowed
|
||||
- comments_left, comments_right = organize_comments(left[:type], right[:type], left[:line_code], right[:line_code])
|
||||
- if comments_left.present? || comments_right.present?
|
||||
= render "projects/notes/diff_notes_with_reply_parallel", notes_left: comments_left, notes_right: comments_right
|
||||
- unless @diff_notes_disabled
|
||||
- notes_left, notes_right = organize_comments(left, right)
|
||||
- if notes_left.present? || notes_right.present?
|
||||
= 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?
|
||||
.file-mode-changed
|
||||
|
||||
@@ -6,16 +6,15 @@
|
||||
%table.text-file.code.js-syntax-highlight{ class: too_big ? 'hide' : '' }
|
||||
|
||||
- last_line = 0
|
||||
- raw_diff_lines = diff_file.diff_lines.to_a
|
||||
- diff_file.highlighted_diff_lines.each_with_index do |line, index|
|
||||
- line_code = generate_line_code(diff_file.file_path, line)
|
||||
- last_line = line.new_pos
|
||||
= render "projects/diffs/line", {line: line, diff_file: diff_file, line_code: line_code}
|
||||
|
||||
- if @reply_allowed
|
||||
- comments = @line_notes.select { |n| n.line_code == line_code && n.active? }.sort_by(&:created_at)
|
||||
- unless comments.empty?
|
||||
= render "projects/notes/diff_notes_with_reply", notes: comments, line: raw_diff_lines[index].text
|
||||
- unless @diff_notes_disabled
|
||||
- diff_notes = @grouped_diff_notes[line_code]
|
||||
- if diff_notes
|
||||
= render "projects/notes/diff_notes_with_reply", notes: diff_notes
|
||||
|
||||
- if last_line > 0
|
||||
= render "projects/diffs/match_line", { line: "",
|
||||
|
||||
@@ -84,6 +84,16 @@
|
||||
%br
|
||||
%span.descr Share code pastes with others out of git repository
|
||||
|
||||
- if Gitlab.config.registry.enabled
|
||||
.form-group
|
||||
.col-sm-offset-2.col-sm-10
|
||||
.checkbox
|
||||
= f.label :container_registry_enabled do
|
||||
= f.check_box :container_registry_enabled
|
||||
%strong Container Registry
|
||||
%br
|
||||
%span.descr Enable Container Registry for this repository
|
||||
|
||||
= render 'builds_settings', f: f
|
||||
|
||||
%fieldset.features
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
- note = notes.first # example note
|
||||
-# Check if line want not changed since comment was left
|
||||
- if !defined?(line) || line == note.diff_line
|
||||
%tr.notes_holder
|
||||
%td.notes_line{ colspan: 2 }
|
||||
%td.notes_content
|
||||
%ul.notes{ data: { discussion_id: note.discussion_id } }
|
||||
= render notes
|
||||
.discussion-reply-holder
|
||||
= link_to_reply_diff(note)
|
||||
- note = notes.first
|
||||
%tr.notes_holder
|
||||
%td.notes_line{ colspan: 2 }
|
||||
%td.notes_content
|
||||
%ul.notes{ data: { discussion_id: note.discussion_id } }
|
||||
= render partial: "projects/notes/note", collection: notes, as: :note
|
||||
.discussion-reply-holder
|
||||
= link_to_reply_discussion(note)
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
- note1 = notes_left.present? ? notes_left.first : nil
|
||||
- note2 = notes_right.present? ? notes_right.first : nil
|
||||
- note_left = notes_left.present? ? notes_left.first : nil
|
||||
- note_right = notes_right.present? ? notes_right.first : nil
|
||||
|
||||
%tr.notes_holder
|
||||
- if note1
|
||||
- if note_left
|
||||
%td.notes_line.old
|
||||
%td.notes_content.parallel.old
|
||||
%ul.notes{ data: { discussion_id: note1.discussion_id } }
|
||||
= render notes_left
|
||||
%ul.notes{ data: { discussion_id: note_left.discussion_id } }
|
||||
= render partial: "projects/notes/note", collection: notes_left, as: :note
|
||||
|
||||
.discussion-reply-holder
|
||||
= link_to_reply_diff(note1, 'old')
|
||||
= link_to_reply_discussion(note_left, 'old')
|
||||
- else
|
||||
%td.notes_line.old= ""
|
||||
%td.notes_content.parallel.old= ""
|
||||
|
||||
- if note2
|
||||
- if note_right
|
||||
%td.notes_line.new
|
||||
%td.notes_content.parallel.new
|
||||
%ul.notes{ data: { discussion_id: note2.discussion_id } }
|
||||
= render notes_right
|
||||
%ul.notes{ data: { discussion_id: note_right.discussion_id } }
|
||||
= render partial: "projects/notes/note", collection: notes_right, as: :note
|
||||
|
||||
.discussion-reply-holder
|
||||
= link_to_reply_diff(note2, 'new')
|
||||
= link_to_reply_discussion(note_right, 'new')
|
||||
- else
|
||||
%td.notes_line.new= ""
|
||||
%td.notes_content.parallel.new= ""
|
||||
|
||||
@@ -1,13 +1,46 @@
|
||||
- note = discussion_notes.first
|
||||
- expanded = !note.diff_note? || note.active?
|
||||
%li.note.note-discussion.timeline-entry
|
||||
.timeline-entry-inner
|
||||
.timeline-icon
|
||||
= link_to user_path(note.author) do
|
||||
= image_tag avatar_icon(note.author_email), class: "avatar s40"
|
||||
= image_tag avatar_icon(note.author), class: "avatar s40"
|
||||
.timeline-content
|
||||
- if note.for_merge_request?
|
||||
- (active_notes, outdated_notes) = discussion_notes.partition(&:active?)
|
||||
= render "projects/notes/discussions/active", discussion_notes: active_notes if active_notes.length > 0
|
||||
= render "projects/notes/discussions/outdated", discussion_notes: outdated_notes if outdated_notes.length > 0
|
||||
- else
|
||||
= render "projects/notes/discussions/commit", discussion_notes: discussion_notes
|
||||
.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
|
||||
|
||||
- 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
|
||||
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 @@
|
||||
= f.hidden_field :line_code
|
||||
= f.hidden_field :noteable_id
|
||||
= f.hidden_field :noteable_type
|
||||
= f.hidden_field :type
|
||||
|
||||
= 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..."
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
- return unless note.author
|
||||
- return if note.cross_reference_not_visible_for?(current_user)
|
||||
|
||||
- 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-icon
|
||||
%a{href: user_path(note.author)}
|
||||
@@ -8,8 +11,8 @@
|
||||
.note-header
|
||||
= link_to_member(note.project, note.author, avatar: false)
|
||||
.inline.note-headline-light
|
||||
= "#{note.author.to_reference}"
|
||||
- if !note.system
|
||||
= note.author.to_reference
|
||||
- unless note.system
|
||||
commented
|
||||
%a{ href: "##{dom_id(note)}" }
|
||||
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
|
||||
|
||||
@@ -2,14 +2,9 @@
|
||||
- @discussions.each do |discussion_notes|
|
||||
- note = discussion_notes.first
|
||||
- if note_for_main_target?(note)
|
||||
- next if note.cross_reference_not_visible_for?(current_user)
|
||||
|
||||
= render discussion_notes
|
||||
= render partial: "projects/notes/note", object: note, as: :note
|
||||
- else
|
||||
= render 'projects/notes/discussion', discussion_notes: discussion_notes
|
||||
- else
|
||||
- @notes.each do |note|
|
||||
- next unless note.author
|
||||
- next if note.cross_reference_not_visible_for?(current_user)
|
||||
|
||||
= render note
|
||||
= render partial: "projects/notes/note", object: note, as: :note
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
- 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
|
||||
@@ -1,25 +0,0 @@
|
||||
- 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)
|
||||
@@ -1,28 +0,0 @@
|
||||
- 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" ? " ".html_safe : line.old_pos } }
|
||||
%td.new_line.diff-line-num{ data: { linenumber: type == "old" ? " ".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
|
||||
@@ -0,0 +1,30 @@
|
||||
- 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" ? " ".html_safe : line.old_pos } }
|
||||
%td.new_line.diff-line-num{ data: { linenumber: type == "old" ? " ".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
|
||||
@@ -0,0 +1,7 @@
|
||||
- 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)
|
||||
@@ -1,14 +0,0 @@
|
||||
- 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
|
||||
@@ -98,6 +98,7 @@ production: &base
|
||||
wiki: true
|
||||
snippets: false
|
||||
builds: true
|
||||
container_registry: true
|
||||
|
||||
## Webhook settings
|
||||
# Number of seconds to wait for HTTP response after sending webhook HTTP POST request (default: 10)
|
||||
@@ -175,6 +176,14 @@ production: &base
|
||||
repository_archive_cache_worker:
|
||||
cron: "0 * * * *"
|
||||
|
||||
registry:
|
||||
# enabled: true
|
||||
# host: registry.example.com
|
||||
# port: 5000
|
||||
# api_url: http://localhost:5000/
|
||||
# key: config/registry.key
|
||||
# issuer: omnibus-certificate
|
||||
|
||||
#
|
||||
# 2. GitLab CI settings
|
||||
# ==========================
|
||||
|
||||
@@ -206,12 +206,13 @@ Settings.gitlab['default_projects_features'] ||= {}
|
||||
Settings.gitlab['webhook_timeout'] ||= 10
|
||||
Settings.gitlab['max_attachment_size'] ||= 10
|
||||
Settings.gitlab['session_expire_delay'] ||= 10080
|
||||
Settings.gitlab.default_projects_features['issues'] = true if Settings.gitlab.default_projects_features['issues'].nil?
|
||||
Settings.gitlab.default_projects_features['merge_requests'] = true if Settings.gitlab.default_projects_features['merge_requests'].nil?
|
||||
Settings.gitlab.default_projects_features['wiki'] = true if Settings.gitlab.default_projects_features['wiki'].nil?
|
||||
Settings.gitlab.default_projects_features['snippets'] = false if Settings.gitlab.default_projects_features['snippets'].nil?
|
||||
Settings.gitlab.default_projects_features['builds'] = true if Settings.gitlab.default_projects_features['builds'].nil?
|
||||
Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
|
||||
Settings.gitlab.default_projects_features['issues'] = true if Settings.gitlab.default_projects_features['issues'].nil?
|
||||
Settings.gitlab.default_projects_features['merge_requests'] = true if Settings.gitlab.default_projects_features['merge_requests'].nil?
|
||||
Settings.gitlab.default_projects_features['wiki'] = true if Settings.gitlab.default_projects_features['wiki'].nil?
|
||||
Settings.gitlab.default_projects_features['snippets'] = false if Settings.gitlab.default_projects_features['snippets'].nil?
|
||||
Settings.gitlab.default_projects_features['builds'] = true if Settings.gitlab.default_projects_features['builds'].nil?
|
||||
Settings.gitlab.default_projects_features['container_registry'] = true if Settings.gitlab.default_projects_features['container_registry'].nil?
|
||||
Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
|
||||
Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive') if Settings.gitlab['repository_downloads_path'].nil?
|
||||
Settings.gitlab['restricted_signup_domains'] ||= []
|
||||
Settings.gitlab['import_sources'] ||= ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git']
|
||||
@@ -242,6 +243,16 @@ Settings.artifacts['enabled'] = true if Settings.artifacts['enabled'].nil?
|
||||
Settings.artifacts['path'] = File.expand_path(Settings.artifacts['path'] || File.join(Settings.shared['path'], "artifacts"), Rails.root)
|
||||
Settings.artifacts['max_size'] ||= 100 # in megabytes
|
||||
|
||||
#
|
||||
# Registry
|
||||
#
|
||||
Settings['registry'] ||= Settingslogic.new({})
|
||||
Settings.registry['enabled'] ||= false
|
||||
Settings.registry['host'] ||= "example.com"
|
||||
Settings.registry['api_url'] ||= "http://localhost:5000/"
|
||||
Settings.registry['key'] ||= nil
|
||||
Settings.registry['issuer'] ||= nil
|
||||
|
||||
#
|
||||
# Git LFS
|
||||
#
|
||||
|
||||
@@ -64,6 +64,9 @@ Rails.application.routes.draw do
|
||||
get 'search' => 'search#show'
|
||||
get 'search/autocomplete' => 'search#autocomplete', as: :search_autocomplete
|
||||
|
||||
# JSON Web Token
|
||||
get 'jwt/auth' => 'jwt#auth'
|
||||
|
||||
# API
|
||||
API::API.logger Rails.logger
|
||||
mount API::API => '/api'
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddImagesEnabledForProject < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :projects, :container_registry_enabled, :boolean
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddTypeToNotes < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :notes, :type, :string
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class SetTypeOnLegacyDiffNotes < ActiveRecord::Migration
|
||||
def change
|
||||
execute "UPDATE notes SET type = 'LegacyDiffNote' WHERE line_code IS NOT NULL"
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,12 @@
|
||||
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
|
||||
+4
-2
@@ -632,10 +632,11 @@ ActiveRecord::Schema.define(version: 20160509201028) do
|
||||
t.string "line_code"
|
||||
t.string "commit_id"
|
||||
t.integer "noteable_id"
|
||||
t.boolean "system", default: false, null: false
|
||||
t.boolean "system", default: false, null: false
|
||||
t.text "st_diff"
|
||||
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
|
||||
|
||||
add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree
|
||||
@@ -761,6 +762,7 @@ ActiveRecord::Schema.define(version: 20160509201028) do
|
||||
t.integer "pushes_since_gc", default: 0
|
||||
t.boolean "last_repository_check_failed"
|
||||
t.datetime "last_repository_check_at"
|
||||
t.boolean "container_registry_enabled"
|
||||
end
|
||||
|
||||
add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree
|
||||
|
||||
@@ -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
|
||||
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:
|
||||
configure custom domains with custom SSL, which would not be possible
|
||||
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:
|
||||
1. [Configure the GitLab application servers](gitlab.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"
|
||||
|
||||
@@ -424,6 +424,7 @@ Parameters:
|
||||
- `builds_enabled` (optional)
|
||||
- `wiki_enabled` (optional)
|
||||
- `snippets_enabled` (optional)
|
||||
- `container_registry_enabled` (optional)
|
||||
- `public` (optional) - if `true` same as setting visibility_level = 20
|
||||
- `visibility_level` (optional)
|
||||
- `import_url` (optional)
|
||||
@@ -447,6 +448,7 @@ Parameters:
|
||||
- `builds_enabled` (optional)
|
||||
- `wiki_enabled` (optional)
|
||||
- `snippets_enabled` (optional)
|
||||
- `container_registry_enabled` (optional)
|
||||
- `public` (optional) - if `true` same as setting visibility_level = 20
|
||||
- `visibility_level` (optional)
|
||||
- `import_url` (optional)
|
||||
@@ -472,6 +474,7 @@ Parameters:
|
||||
- `builds_enabled` (optional)
|
||||
- `wiki_enabled` (optional)
|
||||
- `snippets_enabled` (optional)
|
||||
- `container_registry_enabled` (optional)
|
||||
- `public` (optional) - if `true` same as setting visibility_level = 20
|
||||
- `visibility_level` (optional)
|
||||
- `public_builds` (optional)
|
||||
|
||||
+1
-1
@@ -491,7 +491,7 @@ Jira issue tracker
|
||||
|
||||
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
|
||||
|
||||
@@ -127,7 +127,7 @@ Inside the document:
|
||||
```
|
||||
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:
|
||||
`http://doc.gitlab.com/ce/administration/restart_gitlab.html`.
|
||||
`http://docs.gitlab.com/ce/administration/restart_gitlab.html`.
|
||||
Replace `reconfigure` with `restart` where appropriate.
|
||||
|
||||
## Installation guide
|
||||
@@ -266,5 +266,5 @@ curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -d "restricted_signup_domai
|
||||
|
||||
[cURL]: http://curl.haxx.se/ "cURL website"
|
||||
[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"
|
||||
|
||||
@@ -24,4 +24,4 @@ You may assign the Issue to a user, add a milestone and add labels (they are all
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||
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"
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
**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.
|
||||
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.
|
||||
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
|
||||
|
||||
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.
|
||||
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.
|
||||
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:
|
||||
1. Follow the same as above starting from 2. and set up the
|
||||
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"
|
||||
|
||||
@@ -19,7 +19,7 @@ See the documentation below for details on how to configure these services.
|
||||
|
||||
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
|
||||
|
||||
+1
-1
@@ -39,4 +39,4 @@ Install and update your GitLab installation.
|
||||
|
||||
- [Install GitLab](https://about.gitlab.com/installation/)
|
||||
- [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)
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
## Log system
|
||||
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.
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ documentation](../workflow/add-user/add-user.md).
|
||||
| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
|
||||
| Manage labels | | ✓ | ✓ | ✓ | ✓ |
|
||||
| See a commit status | | ✓ | ✓ | ✓ | ✓ |
|
||||
| See a container registry | | ✓ | ✓ | ✓ | ✓ |
|
||||
| Manage merge requests | | | ✓ | ✓ | ✓ |
|
||||
| Create new merge request | | | ✓ | ✓ | ✓ |
|
||||
| Create new branches | | | ✓ | ✓ | ✓ |
|
||||
@@ -37,6 +38,7 @@ documentation](../workflow/add-user/add-user.md).
|
||||
| Write a wiki | | | ✓ | ✓ | ✓ |
|
||||
| Cancel and retry builds | | | ✓ | ✓ | ✓ |
|
||||
| Create or update commit status | | | ✓ | ✓ | ✓ |
|
||||
| Update a container registry | | | ✓ | ✓ | ✓ |
|
||||
| Create new milestones | | | | ✓ | ✓ |
|
||||
| Add new team members | | | | ✓ | ✓ |
|
||||
| Push to protected branches | | | | ✓ | ✓ |
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
- [User management](user_management.md)
|
||||
- [Webhooks](web_hooks.md)
|
||||
- [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 @@
|
||||
- [User File Uploads](user_file_uploads.md)
|
||||
- [How we manage the CRIME vulnerability](crime_vulnerability.md)
|
||||
- [Enforce Two-factor authentication](two_factor_authentication.md)
|
||||
- [Send email confirmation on sign-up](user_email_confirmation.md)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# 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.
|
||||
|
||||
## 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.
|
||||
|
||||
## Installation from source
|
||||
@@ -86,10 +86,10 @@ possible.
|
||||
information about configuring GitLab to work with a MySQL database.
|
||||
- [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-ce]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update
|
||||
[ee-ce]: ../downgrade_ee_to_ce/README.md
|
||||
[ce]: https://about.gitlab.com/features/#community
|
||||
[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
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Issues with GitLab flow
|
||||
@@ -187,7 +187,7 @@ If you have an issue that spans across multiple repositories, the best thing is
|
||||

|
||||
|
||||
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.
|
||||
However you should never rebase commits you have pushed to a remote server.
|
||||
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
|
||||
## Managing group memberships via LDAP
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -44,5 +44,5 @@ case the namespace is taken, the project will be imported on the user's
|
||||
namespace.
|
||||
|
||||
[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"
|
||||
|
||||
@@ -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
|
||||
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.
|
||||
|
||||

|
||||
|
||||
@@ -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
|
||||
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
|
||||
different commands to differentiate between the large files and regular ones.
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ module SharedDiffNote
|
||||
page.within(diff_file_selector) do
|
||||
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"
|
||||
find(".js-comment-button").trigger("click")
|
||||
sleep 0.05
|
||||
@@ -33,7 +33,7 @@ module SharedDiffNote
|
||||
|
||||
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')
|
||||
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"
|
||||
find(".js-comment-button").trigger("click")
|
||||
end
|
||||
@@ -41,7 +41,7 @@ module SharedDiffNote
|
||||
|
||||
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')
|
||||
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"
|
||||
find(".js-comment-button").trigger("click")
|
||||
end
|
||||
@@ -51,7 +51,7 @@ module SharedDiffNote
|
||||
page.within(diff_file_selector) do
|
||||
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:"
|
||||
find('.js-md-preview-button').click
|
||||
end
|
||||
@@ -62,7 +62,7 @@ module SharedDiffNote
|
||||
page.within(diff_file_selector) do
|
||||
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"
|
||||
find('.js-md-preview-button').click
|
||||
end
|
||||
@@ -91,7 +91,7 @@ module SharedDiffNote
|
||||
page.within(diff_file_selector) do
|
||||
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:'
|
||||
click_button('Comment')
|
||||
end
|
||||
|
||||
@@ -107,6 +107,8 @@ module API
|
||||
|
||||
break if opts[:line_code]
|
||||
end
|
||||
|
||||
opts[:type] = LegacyDiffNote.name if opts[:line_code]
|
||||
end
|
||||
|
||||
note = ::Notes::CreateService.new(user_project, current_user, opts).execute
|
||||
|
||||
+5
-4
@@ -66,7 +66,8 @@ module API
|
||||
expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
|
||||
expose :name, :name_with_namespace
|
||||
expose :path, :path_with_namespace
|
||||
expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :builds_enabled, :snippets_enabled, :created_at, :last_activity_at
|
||||
expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :builds_enabled, :snippets_enabled, :container_registry_enabled
|
||||
expose :created_at, :last_activity_at
|
||||
expose :shared_runners_enabled
|
||||
expose :creator_id
|
||||
expose :namespace
|
||||
@@ -227,9 +228,9 @@ module API
|
||||
|
||||
class CommitNote < Grape::Entity
|
||||
expose :note
|
||||
expose(:path) { |note| note.diff_file_name }
|
||||
expose(:line) { |note| note.diff_new_line }
|
||||
expose(:line_type) { |note| note.diff_line_type }
|
||||
expose(:path) { |note| note.diff_file_path if note.legacy_diff_note? }
|
||||
expose(:line) { |note| note.diff_new_line if note.legacy_diff_note? }
|
||||
expose(:line_type) { |note| note.diff_line_type if note.legacy_diff_note? }
|
||||
expose :author, using: Entities::UserBasic
|
||||
expose :created_at
|
||||
end
|
||||
|
||||
@@ -94,6 +94,7 @@ module API
|
||||
# builds_enabled (optional)
|
||||
# wiki_enabled (optional)
|
||||
# snippets_enabled (optional)
|
||||
# container_registry_enabled (optional)
|
||||
# shared_runners_enabled (optional)
|
||||
# namespace_id (optional) - defaults to user namespace
|
||||
# public (optional) - if true same as setting visibility_level = 20
|
||||
@@ -112,6 +113,7 @@ module API
|
||||
:builds_enabled,
|
||||
:wiki_enabled,
|
||||
:snippets_enabled,
|
||||
:container_registry_enabled,
|
||||
:shared_runners_enabled,
|
||||
:namespace_id,
|
||||
:public,
|
||||
@@ -143,6 +145,7 @@ module API
|
||||
# builds_enabled (optional)
|
||||
# wiki_enabled (optional)
|
||||
# snippets_enabled (optional)
|
||||
# container_registry_enabled (optional)
|
||||
# shared_runners_enabled (optional)
|
||||
# public (optional) - if true same as setting visibility_level = 20
|
||||
# visibility_level (optional)
|
||||
@@ -206,6 +209,7 @@ module API
|
||||
# builds_enabled (optional)
|
||||
# wiki_enabled (optional)
|
||||
# snippets_enabled (optional)
|
||||
# container_registry_enabled (optional)
|
||||
# shared_runners_enabled (optional)
|
||||
# public (optional) - if true same as setting visibility_level = 20
|
||||
# visibility_level (optional) - visibility level of a project
|
||||
@@ -222,6 +226,7 @@ module API
|
||||
:builds_enabled,
|
||||
:wiki_enabled,
|
||||
:snippets_enabled,
|
||||
:container_registry_enabled,
|
||||
:shared_runners_enabled,
|
||||
:public,
|
||||
:visibility_level,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
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
|
||||
class Importer
|
||||
include Gitlab::ShellAdapter
|
||||
|
||||
attr_reader :project, :client
|
||||
attr_reader :client, :project, :repo, :repo_url
|
||||
|
||||
def initialize(project)
|
||||
@project = project
|
||||
if import_data_credentials
|
||||
@client = Client.new(import_data_credentials[:user])
|
||||
@project = project
|
||||
@repo = project.import_source
|
||||
@repo_url = project.import_url
|
||||
|
||||
if credentials
|
||||
@client = Client.new(credentials[:user])
|
||||
@formatter = Gitlab::ImportFormatter.new
|
||||
else
|
||||
raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}"
|
||||
@@ -22,12 +25,12 @@ module Gitlab
|
||||
|
||||
private
|
||||
|
||||
def import_data_credentials
|
||||
@import_data_credentials ||= project.import_data.credentials if project.import_data
|
||||
def credentials
|
||||
@credentials ||= project.import_data.credentials if project.import_data
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
@@ -37,7 +40,7 @@ module Gitlab
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
@@ -47,9 +50,7 @@ module Gitlab
|
||||
end
|
||||
|
||||
def import_issues
|
||||
client.list_issues(project.import_source, state: :all,
|
||||
sort: :created,
|
||||
direction: :asc).each do |raw_data|
|
||||
client.list_issues(repo, state: :all, sort: :created, direction: :asc).each do |raw_data|
|
||||
gh_issue = IssueFormatter.new(project, raw_data)
|
||||
|
||||
if gh_issue.valid?
|
||||
@@ -68,29 +69,50 @@ module Gitlab
|
||||
end
|
||||
|
||||
def import_pull_requests
|
||||
client.pull_requests(project.import_source, state: :all,
|
||||
sort: :created,
|
||||
direction: :asc).each do |raw_data|
|
||||
pull_request = PullRequestFormatter.new(project, raw_data)
|
||||
pull_requests = client.pull_requests(repo, state: :all, sort: :created, direction: :asc)
|
||||
.map { |raw| PullRequestFormatter.new(project, raw) }
|
||||
.select(&:valid?)
|
||||
|
||||
if pull_request.valid?
|
||||
merge_request = MergeRequest.new(pull_request.attributes)
|
||||
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
|
||||
|
||||
if merge_request.save
|
||||
apply_labels(pull_request.number, merge_request)
|
||||
import_comments(pull_request.number, merge_request)
|
||||
import_comments_on_diff(pull_request.number, merge_request)
|
||||
end
|
||||
create_refs(branches_removed)
|
||||
|
||||
pull_requests.each do |pull_request|
|
||||
merge_request = MergeRequest.new(pull_request.attributes)
|
||||
|
||||
if merge_request.save
|
||||
apply_labels(pull_request.number, merge_request)
|
||||
import_comments(pull_request.number, merge_request)
|
||||
import_comments_on_diff(pull_request.number, merge_request)
|
||||
end
|
||||
end
|
||||
|
||||
delete_refs(branches_removed)
|
||||
|
||||
true
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
raise Projects::ImportService::Error, e.message
|
||||
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)
|
||||
issue = client.issue(project.import_source, number)
|
||||
issue = client.issue(repo, number)
|
||||
|
||||
if issue.labels.count > 0
|
||||
label_ids = issue.labels.map do |raw|
|
||||
@@ -102,12 +124,12 @@ module Gitlab
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
module Gitlab
|
||||
module GithubImport
|
||||
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
|
||||
{
|
||||
iid: number,
|
||||
title: raw_data.title,
|
||||
description: description,
|
||||
source_project: source_project,
|
||||
source_branch: source_branch.name,
|
||||
target_project: target_project,
|
||||
target_branch: target_branch.name,
|
||||
source_project: source_branch_project,
|
||||
source_branch: source_branch_name,
|
||||
head_source_sha: source_branch_sha,
|
||||
target_project: target_branch_project,
|
||||
target_branch: target_branch_name,
|
||||
base_target_sha: target_branch_sha,
|
||||
state: state,
|
||||
milestone: milestone,
|
||||
author_id: author_id,
|
||||
@@ -24,7 +29,15 @@ module Gitlab
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
private
|
||||
@@ -52,7 +65,7 @@ module Gitlab
|
||||
end
|
||||
|
||||
def cross_project?
|
||||
source_repo.present? && target_repo.present? && source_repo.id != target_repo.id
|
||||
source_branch_repo.id != target_branch_repo.id
|
||||
end
|
||||
|
||||
def description
|
||||
@@ -65,30 +78,6 @@ module Gitlab
|
||||
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
|
||||
@state ||= case true
|
||||
when raw_data.state == 'closed' && raw_data.merged_at.present?
|
||||
|
||||
@@ -6,26 +6,28 @@ module Gitlab
|
||||
attach_to :active_support
|
||||
|
||||
def cache_read(event)
|
||||
increment(:cache_read_duration, event.duration)
|
||||
increment(:cache_read, event.duration)
|
||||
end
|
||||
|
||||
def cache_write(event)
|
||||
increment(:cache_write_duration, event.duration)
|
||||
increment(:cache_write, event.duration)
|
||||
end
|
||||
|
||||
def cache_delete(event)
|
||||
increment(:cache_delete_duration, event.duration)
|
||||
increment(:cache_delete, event.duration)
|
||||
end
|
||||
|
||||
def cache_exist?(event)
|
||||
increment(:cache_exists_duration, event.duration)
|
||||
increment(:cache_exists, event.duration)
|
||||
end
|
||||
|
||||
def increment(key, duration)
|
||||
return unless current_transaction
|
||||
|
||||
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
|
||||
|
||||
private
|
||||
|
||||
@@ -62,7 +62,7 @@ module Gitlab
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
module JSONWebToken
|
||||
class RSAToken < Token
|
||||
attr_reader :key_file
|
||||
|
||||
def initialize(key_file)
|
||||
super()
|
||||
@key_file = key_file
|
||||
end
|
||||
|
||||
def encoded
|
||||
headers = {
|
||||
kid: kid
|
||||
}
|
||||
JWT.encode(payload, key, 'RS256', headers)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def key_data
|
||||
@key_data ||= File.read(key_file)
|
||||
end
|
||||
|
||||
def key
|
||||
@key ||= OpenSSL::PKey::RSA.new(key_data)
|
||||
end
|
||||
|
||||
def public_key
|
||||
key.public_key
|
||||
end
|
||||
|
||||
def kid
|
||||
# calculate sha256 from DER encoded ASN1
|
||||
kid = Digest::SHA256.digest(public_key.to_der)
|
||||
|
||||
# we encode only 30 bytes with base32
|
||||
kid = Base32.encode(kid[0..29])
|
||||
|
||||
# insert colon every 4 characters
|
||||
kid.scan(/.{4}/).join(':')
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,46 @@
|
||||
module JSONWebToken
|
||||
class Token
|
||||
attr_accessor :issuer, :subject, :audience, :id
|
||||
attr_accessor :issued_at, :not_before, :expire_time
|
||||
|
||||
def initialize
|
||||
@id = SecureRandom.uuid
|
||||
@issued_at = Time.now
|
||||
# we give a few seconds for time shift
|
||||
@not_before = issued_at - 5.seconds
|
||||
# default 60 seconds should be more than enough for this authentication token
|
||||
@expire_time = issued_at + 1.minute
|
||||
@custom_payload = {}
|
||||
end
|
||||
|
||||
def [](key)
|
||||
@custom_payload[key]
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
@custom_payload[key] = value
|
||||
end
|
||||
|
||||
def encoded
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def payload
|
||||
@custom_payload.merge(default_payload)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_payload
|
||||
{
|
||||
jti: id,
|
||||
aud: audience,
|
||||
sub: subject,
|
||||
iss: issuer,
|
||||
iat: issued_at.to_i,
|
||||
nbf: not_before.to_i,
|
||||
exp: expire_time.to_i
|
||||
}.compact
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -65,3 +65,4 @@ Disallow: /*/*/deploy_keys
|
||||
Disallow: /*/*/hooks
|
||||
Disallow: /*/*/services
|
||||
Disallow: /*/*/protected_branches
|
||||
Disallow: /*/*/uploads/
|
||||
|
||||
@@ -114,6 +114,82 @@ describe Admin::UsersController do
|
||||
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
|
||||
context "when the user is blocked" do
|
||||
before do
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
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
|
||||
author
|
||||
|
||||
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_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 :system_note, traits: [:system]
|
||||
factory :downvote_note, traits: [:award, :downvote]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user