Merge remote-tracking branch 'origin/master' into balsalmiq-support
|
@ -303,12 +303,12 @@ ee_compat_check:
|
|||
script:
|
||||
- bundle exec rake db:migrate:reset
|
||||
|
||||
db:migrate:reset pg:
|
||||
rake pg db:migrate:reset:
|
||||
<<: *db-migrate-reset
|
||||
<<: *use-pg
|
||||
<<: *except-docs
|
||||
|
||||
db:migrate:reset mysql:
|
||||
rake mysql db:migrate:reset:
|
||||
<<: *db-migrate-reset
|
||||
<<: *use-mysql
|
||||
<<: *except-docs
|
||||
|
@ -320,12 +320,12 @@ db:migrate:reset mysql:
|
|||
- bundle exec rake db:rollback STEP=120
|
||||
- bundle exec rake db:migrate
|
||||
|
||||
db:rollback pg:
|
||||
rake pg db:rollback:
|
||||
<<: *db-rollback
|
||||
<<: *use-pg
|
||||
<<: *except-docs
|
||||
|
||||
db:rollback mysql:
|
||||
rake mysql db:rollback:
|
||||
<<: *db-rollback
|
||||
<<: *use-mysql
|
||||
<<: *except-docs
|
||||
|
@ -347,17 +347,17 @@ db:rollback mysql:
|
|||
paths:
|
||||
- log/development.log
|
||||
|
||||
db:seed_fu pg:
|
||||
rake pg db:seed_fu:
|
||||
<<: *db-seed_fu
|
||||
<<: *use-pg
|
||||
<<: *except-docs
|
||||
|
||||
db:seed_fu mysql:
|
||||
rake mysql db:seed_fu:
|
||||
<<: *db-seed_fu
|
||||
<<: *use-mysql
|
||||
<<: *except-docs
|
||||
|
||||
gitlab:assets:compile:
|
||||
rake gitlab:assets:compile:
|
||||
stage: test
|
||||
<<: *dedicated-runner
|
||||
<<: *except-docs
|
||||
|
@ -377,7 +377,7 @@ gitlab:assets:compile:
|
|||
paths:
|
||||
- webpack-report/
|
||||
|
||||
karma:
|
||||
rake karma:
|
||||
cache:
|
||||
paths:
|
||||
- vendor/ruby
|
||||
|
@ -443,11 +443,11 @@ bundler:audit:
|
|||
- . scripts/prepare_build.sh
|
||||
- bundle exec rake db:migrate
|
||||
|
||||
migration path pg:
|
||||
migration pg paths:
|
||||
<<: *migration-paths
|
||||
<<: *use-pg
|
||||
|
||||
migration path mysql:
|
||||
migration mysql paths:
|
||||
<<: *migration-paths
|
||||
<<: *use-mysql
|
||||
|
||||
|
@ -502,30 +502,14 @@ trigger_docs:
|
|||
- master@gitlab-org/gitlab-ce
|
||||
- master@gitlab-org/gitlab-ee
|
||||
|
||||
# Notify slack in the end
|
||||
notify:slack:
|
||||
stage: post-test
|
||||
<<: *dedicated-runner
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
USE_BUNDLE_INSTALL: "false"
|
||||
script:
|
||||
- ./scripts/notify_slack.sh "#development" "Build on \`$CI_COMMIT_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_COMMIT_SHA"/pipelines>"
|
||||
when: on_failure
|
||||
only:
|
||||
- master@gitlab-org/gitlab-ce
|
||||
- tags@gitlab-org/gitlab-ce
|
||||
- master@gitlab-org/gitlab-ee
|
||||
- tags@gitlab-org/gitlab-ee
|
||||
|
||||
pages:
|
||||
before_script: []
|
||||
stage: pages
|
||||
<<: *dedicated-runner
|
||||
dependencies:
|
||||
- coverage
|
||||
- karma
|
||||
- gitlab:assets:compile
|
||||
- rake karma
|
||||
- rake gitlab:assets:compile
|
||||
- lint:javascript:report
|
||||
script:
|
||||
- mv public/ .public/
|
||||
|
|
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 4.2 KiB |
|
@ -1,4 +1,6 @@
|
|||
class Admin::HooksController < Admin::ApplicationController
|
||||
before_action :hook, only: :edit
|
||||
|
||||
def index
|
||||
@hooks = SystemHook.all
|
||||
@hook = SystemHook.new
|
||||
|
@ -15,15 +17,25 @@ class Admin::HooksController < Admin::ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
if hook.update_attributes(hook_params)
|
||||
flash[:notice] = 'System hook was successfully updated.'
|
||||
redirect_to admin_hooks_path
|
||||
else
|
||||
render 'edit'
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@hook = SystemHook.find(params[:id])
|
||||
@hook.destroy
|
||||
hook.destroy
|
||||
|
||||
redirect_to admin_hooks_path
|
||||
end
|
||||
|
||||
def test
|
||||
@hook = SystemHook.find(params[:hook_id])
|
||||
data = {
|
||||
event_name: "project_create",
|
||||
name: "Ruby",
|
||||
|
@ -32,11 +44,17 @@ class Admin::HooksController < Admin::ApplicationController
|
|||
owner_name: "Someone",
|
||||
owner_email: "example@gitlabhq.com"
|
||||
}
|
||||
@hook.execute(data, 'system_hooks')
|
||||
hook.execute(data, 'system_hooks')
|
||||
|
||||
redirect_back_or_default
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def hook
|
||||
@hook ||= SystemHook.find(params[:id])
|
||||
end
|
||||
|
||||
def hook_params
|
||||
params.require(:hook).permit(
|
||||
:enable_ssl_verification,
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
module NotesActions
|
||||
include RendersNotes
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :authorize_admin_note!, only: [:update, :destroy]
|
||||
end
|
||||
|
||||
def index
|
||||
current_fetched_at = Time.now.to_i
|
||||
|
||||
notes_json = { notes: [], last_fetched_at: current_fetched_at }
|
||||
|
||||
@notes = notes_finder.execute.inc_relations_for_view
|
||||
@notes = prepare_notes_for_rendering(@notes)
|
||||
|
||||
@notes.each do |note|
|
||||
next if note.cross_reference_not_visible_for?(current_user)
|
||||
|
||||
notes_json[:notes] << note_json(note)
|
||||
end
|
||||
|
||||
render json: notes_json
|
||||
end
|
||||
|
||||
def create
|
||||
create_params = note_params.merge(
|
||||
merge_request_diff_head_sha: params[:merge_request_diff_head_sha],
|
||||
in_reply_to_discussion_id: params[:in_reply_to_discussion_id]
|
||||
)
|
||||
@note = Notes::CreateService.new(project, current_user, create_params).execute
|
||||
|
||||
if @note.is_a?(Note)
|
||||
Banzai::NoteRenderer.render([@note], @project, current_user)
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.json { render json: note_json(@note) }
|
||||
format.html { redirect_back_or_default }
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
|
||||
|
||||
if @note.is_a?(Note)
|
||||
Banzai::NoteRenderer.render([@note], @project, current_user)
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.json { render json: note_json(@note) }
|
||||
format.html { redirect_back_or_default }
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
if note.editable?
|
||||
Notes::DestroyService.new(project, current_user).execute(note)
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.js { head :ok }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def note_json(note)
|
||||
attrs = {
|
||||
commands_changes: note.commands_changes
|
||||
}
|
||||
|
||||
if note.persisted?
|
||||
attrs.merge!(
|
||||
valid: true,
|
||||
id: note.id,
|
||||
discussion_id: note.discussion_id(noteable),
|
||||
html: note_html(note),
|
||||
note: note.note
|
||||
)
|
||||
|
||||
discussion = note.to_discussion(noteable)
|
||||
unless discussion.individual_note?
|
||||
attrs.merge!(
|
||||
discussion_resolvable: discussion.resolvable?,
|
||||
|
||||
diff_discussion_html: diff_discussion_html(discussion),
|
||||
discussion_html: discussion_html(discussion)
|
||||
)
|
||||
end
|
||||
else
|
||||
attrs.merge!(
|
||||
valid: false,
|
||||
errors: note.errors
|
||||
)
|
||||
end
|
||||
|
||||
attrs
|
||||
end
|
||||
|
||||
def authorize_admin_note!
|
||||
return access_denied! unless can?(current_user, :admin_note, note)
|
||||
end
|
||||
|
||||
def note_params
|
||||
params.require(:note).permit(
|
||||
:project_id,
|
||||
:noteable_type,
|
||||
:noteable_id,
|
||||
:commit_id,
|
||||
:noteable,
|
||||
:type,
|
||||
|
||||
:note,
|
||||
:attachment,
|
||||
|
||||
# LegacyDiffNote
|
||||
:line_code,
|
||||
|
||||
# DiffNote
|
||||
:position
|
||||
)
|
||||
end
|
||||
|
||||
def noteable
|
||||
@noteable ||= notes_finder.target
|
||||
end
|
||||
|
||||
def last_fetched_at
|
||||
request.headers['X-Last-Fetched-At']
|
||||
end
|
||||
|
||||
def notes_finder
|
||||
@notes_finder ||= NotesFinder.new(project, current_user, finder_params)
|
||||
end
|
||||
end
|
|
@ -10,6 +10,8 @@ module RendersNotes
|
|||
private
|
||||
|
||||
def preload_max_access_for_authors(notes, project)
|
||||
return nil unless project
|
||||
|
||||
user_ids = notes.map(&:author_id)
|
||||
project.team.max_member_access_for_user_ids(user_ids)
|
||||
end
|
||||
|
|
|
@ -5,10 +5,12 @@ module SnippetsActions
|
|||
end
|
||||
|
||||
def raw
|
||||
disposition = params[:inline] == 'false' ? 'attachment' : 'inline'
|
||||
|
||||
send_data(
|
||||
convert_line_endings(@snippet.content),
|
||||
type: 'text/plain; charset=utf-8',
|
||||
disposition: 'inline',
|
||||
disposition: disposition,
|
||||
filename: @snippet.sanitized_file_name
|
||||
)
|
||||
end
|
||||
|
|
|
@ -22,7 +22,8 @@ module ToggleAwardEmoji
|
|||
def to_todoable(awardable)
|
||||
case awardable
|
||||
when Note
|
||||
awardable.noteable
|
||||
# we don't create todos for personal snippet comments for now
|
||||
awardable.for_personal_snippet? ? nil : awardable.noteable
|
||||
when MergeRequest, Issue
|
||||
awardable
|
||||
when Snippet
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
class Projects::HooksController < Projects::ApplicationController
|
||||
# Authorize
|
||||
before_action :authorize_admin_project!
|
||||
before_action :hook, only: :edit
|
||||
|
||||
respond_to :html
|
||||
|
||||
|
@ -17,6 +18,18 @@ class Projects::HooksController < Projects::ApplicationController
|
|||
redirect_to namespace_project_settings_integrations_path(@project.namespace, @project)
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
if hook.update_attributes(hook_params)
|
||||
flash[:notice] = 'Hook was successfully updated.'
|
||||
redirect_to namespace_project_settings_integrations_path(@project.namespace, @project)
|
||||
else
|
||||
render 'edit'
|
||||
end
|
||||
end
|
||||
|
||||
def test
|
||||
if !@project.empty_repo?
|
||||
status, message = TestHookService.new.execute(hook, current_user)
|
||||
|
|
|
@ -1,68 +1,22 @@
|
|||
class Projects::NotesController < Projects::ApplicationController
|
||||
include RendersNotes
|
||||
include NotesActions
|
||||
include ToggleAwardEmoji
|
||||
|
||||
# Authorize
|
||||
before_action :authorize_read_note!
|
||||
before_action :authorize_create_note!, only: [:create]
|
||||
before_action :authorize_admin_note!, only: [:update, :destroy]
|
||||
before_action :authorize_resolve_note!, only: [:resolve, :unresolve]
|
||||
|
||||
def index
|
||||
current_fetched_at = Time.now.to_i
|
||||
|
||||
notes_json = { notes: [], last_fetched_at: current_fetched_at }
|
||||
|
||||
@notes = notes_finder.execute.inc_relations_for_view
|
||||
@notes = prepare_notes_for_rendering(@notes)
|
||||
|
||||
@notes.each do |note|
|
||||
next if note.cross_reference_not_visible_for?(current_user)
|
||||
|
||||
notes_json[:notes] << note_json(note)
|
||||
end
|
||||
|
||||
render json: notes_json
|
||||
end
|
||||
|
||||
#
|
||||
# This is a fix to make spinach feature tests passing:
|
||||
# Controller actions are returned from AbstractController::Base and methods of parent classes are
|
||||
# excluded in order to return only specific controller related methods.
|
||||
# That is ok for the app (no :create method in ancestors)
|
||||
# but fails for tests because there is a :create method on FactoryGirl (one of the ancestors)
|
||||
#
|
||||
# see https://github.com/rails/rails/blob/v4.2.7/actionpack/lib/abstract_controller/base.rb#L78
|
||||
#
|
||||
def create
|
||||
create_params = note_params.merge(
|
||||
merge_request_diff_head_sha: params[:merge_request_diff_head_sha],
|
||||
in_reply_to_discussion_id: params[:in_reply_to_discussion_id]
|
||||
)
|
||||
@note = Notes::CreateService.new(project, current_user, create_params).execute
|
||||
|
||||
if @note.is_a?(Note)
|
||||
Banzai::NoteRenderer.render([@note], @project, current_user)
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.json { render json: note_json(@note) }
|
||||
format.html { redirect_back_or_default }
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
|
||||
|
||||
if @note.is_a?(Note)
|
||||
Banzai::NoteRenderer.render([@note], @project, current_user)
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.json { render json: note_json(@note) }
|
||||
format.html { redirect_back_or_default }
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
if note.editable?
|
||||
Notes::DestroyService.new(project, current_user).execute(note)
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.js { head :ok }
|
||||
end
|
||||
super
|
||||
end
|
||||
|
||||
def delete_attachment
|
||||
|
@ -110,7 +64,7 @@ class Projects::NotesController < Projects::ApplicationController
|
|||
|
||||
def note_html(note)
|
||||
render_to_string(
|
||||
"projects/notes/_note",
|
||||
"shared/notes/_note",
|
||||
layout: false,
|
||||
formats: [:html],
|
||||
locals: { note: note }
|
||||
|
@ -152,76 +106,11 @@ class Projects::NotesController < Projects::ApplicationController
|
|||
)
|
||||
end
|
||||
|
||||
def note_json(note)
|
||||
attrs = {
|
||||
commands_changes: note.commands_changes
|
||||
}
|
||||
|
||||
if note.persisted?
|
||||
attrs.merge!(
|
||||
valid: true,
|
||||
id: note.id,
|
||||
discussion_id: note.discussion_id(noteable),
|
||||
html: note_html(note),
|
||||
note: note.note
|
||||
)
|
||||
|
||||
discussion = note.to_discussion(noteable)
|
||||
unless discussion.individual_note?
|
||||
attrs.merge!(
|
||||
discussion_resolvable: discussion.resolvable?,
|
||||
|
||||
diff_discussion_html: diff_discussion_html(discussion),
|
||||
discussion_html: discussion_html(discussion)
|
||||
)
|
||||
end
|
||||
else
|
||||
attrs.merge!(
|
||||
valid: false,
|
||||
errors: note.errors
|
||||
)
|
||||
end
|
||||
|
||||
attrs
|
||||
end
|
||||
|
||||
def authorize_admin_note!
|
||||
return access_denied! unless can?(current_user, :admin_note, note)
|
||||
def finder_params
|
||||
params.merge(last_fetched_at: last_fetched_at)
|
||||
end
|
||||
|
||||
def authorize_resolve_note!
|
||||
return access_denied! unless can?(current_user, :resolve_note, note)
|
||||
end
|
||||
|
||||
def note_params
|
||||
params.require(:note).permit(
|
||||
:project_id,
|
||||
:noteable_type,
|
||||
:noteable_id,
|
||||
:commit_id,
|
||||
:noteable,
|
||||
:type,
|
||||
|
||||
:note,
|
||||
:attachment,
|
||||
|
||||
# LegacyDiffNote
|
||||
:line_code,
|
||||
|
||||
# DiffNote
|
||||
:position
|
||||
)
|
||||
end
|
||||
|
||||
def notes_finder
|
||||
@notes_finder ||= NotesFinder.new(project, current_user, params.merge(last_fetched_at: last_fetched_at))
|
||||
end
|
||||
|
||||
def noteable
|
||||
@noteable ||= notes_finder.target
|
||||
end
|
||||
|
||||
def last_fetched_at
|
||||
request.headers['X-Last-Fetched-At']
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
class Snippets::NotesController < ApplicationController
|
||||
include NotesActions
|
||||
include ToggleAwardEmoji
|
||||
|
||||
skip_before_action :authenticate_user!, only: [:index]
|
||||
before_action :snippet
|
||||
before_action :authorize_read_snippet!, only: [:show, :index, :create]
|
||||
|
||||
private
|
||||
|
||||
def note
|
||||
@note ||= snippet.notes.find(params[:id])
|
||||
end
|
||||
alias_method :awardable, :note
|
||||
|
||||
def note_html(note)
|
||||
render_to_string(
|
||||
"shared/notes/_note",
|
||||
layout: false,
|
||||
formats: [:html],
|
||||
locals: { note: note }
|
||||
)
|
||||
end
|
||||
|
||||
def project
|
||||
nil
|
||||
end
|
||||
|
||||
def snippet
|
||||
PersonalSnippet.find_by(id: params[:snippet_id])
|
||||
end
|
||||
|
||||
def note_params
|
||||
super.merge(noteable_id: params[:snippet_id])
|
||||
end
|
||||
|
||||
def finder_params
|
||||
params.merge(last_fetched_at: last_fetched_at, target_id: snippet.id, target_type: 'personal_snippet')
|
||||
end
|
||||
|
||||
def authorize_read_snippet!
|
||||
return render_404 unless can?(current_user, :read_personal_snippet, snippet)
|
||||
end
|
||||
end
|
|
@ -1,14 +1,15 @@
|
|||
class SnippetsController < ApplicationController
|
||||
include RendersNotes
|
||||
include ToggleAwardEmoji
|
||||
include SpammableActions
|
||||
include SnippetsActions
|
||||
include MarkdownPreview
|
||||
include RendersBlob
|
||||
|
||||
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download]
|
||||
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
|
||||
|
||||
# Allow read snippet
|
||||
before_action :authorize_read_snippet!, only: [:show, :raw, :download]
|
||||
before_action :authorize_read_snippet!, only: [:show, :raw]
|
||||
|
||||
# Allow modify snippet
|
||||
before_action :authorize_update_snippet!, only: [:edit, :update]
|
||||
|
@ -16,7 +17,7 @@ class SnippetsController < ApplicationController
|
|||
# Allow destroy snippet
|
||||
before_action :authorize_admin_snippet!, only: [:destroy]
|
||||
|
||||
skip_before_action :authenticate_user!, only: [:index, :show, :raw, :download]
|
||||
skip_before_action :authenticate_user!, only: [:index, :show, :raw]
|
||||
|
||||
layout 'snippets'
|
||||
respond_to :html
|
||||
|
@ -64,6 +65,11 @@ class SnippetsController < ApplicationController
|
|||
blob = @snippet.blob
|
||||
override_max_blob_size(blob)
|
||||
|
||||
@noteable = @snippet
|
||||
|
||||
@discussions = @snippet.discussions
|
||||
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes))
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
render 'show'
|
||||
|
@ -83,14 +89,6 @@ class SnippetsController < ApplicationController
|
|||
redirect_to snippets_path
|
||||
end
|
||||
|
||||
def download
|
||||
send_data(
|
||||
convert_line_endings(@snippet.content),
|
||||
type: 'text/plain; charset=utf-8',
|
||||
filename: @snippet.sanitized_file_name
|
||||
)
|
||||
end
|
||||
|
||||
def preview_markdown
|
||||
render_markdown_preview(params[:text], skip_project_check: true)
|
||||
end
|
||||
|
|
|
@ -68,6 +68,8 @@ class NotesFinder
|
|||
MergeRequestsFinder.new(@current_user, project_id: @project.id).execute
|
||||
when "snippet", "project_snippet"
|
||||
SnippetsFinder.new.execute(@current_user, filter: :by_project, project: @project)
|
||||
when "personal_snippet"
|
||||
PersonalSnippet.all
|
||||
else
|
||||
raise 'invalid target_type'
|
||||
end
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
module AwardEmojiHelper
|
||||
def toggle_award_url(awardable)
|
||||
return url_for([:toggle_award_emoji, awardable]) unless @project
|
||||
return url_for([:toggle_award_emoji, awardable]) unless @project || awardable.is_a?(Note)
|
||||
|
||||
if awardable.is_a?(Note)
|
||||
# We render a list of notes very frequently and calling the specific method is a lot faster than the generic one (4.5x)
|
||||
toggle_award_emoji_namespace_project_note_url(@project.namespace, @project, awardable.id)
|
||||
if awardable.for_personal_snippet?
|
||||
toggle_award_emoji_snippet_note_path(awardable.noteable, awardable)
|
||||
else
|
||||
toggle_award_emoji_namespace_project_note_path(@project.namespace, @project, awardable.id)
|
||||
end
|
||||
else
|
||||
url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable])
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module MergeRequestsHelper
|
||||
def new_mr_path_from_push_event(event)
|
||||
target_project = event.project.forked_from_project || event.project
|
||||
target_project = event.project.default_merge_request_target
|
||||
new_namespace_project_merge_request_path(
|
||||
event.project.namespace,
|
||||
event.project,
|
||||
|
@ -127,6 +127,10 @@ module MergeRequestsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def target_projects(project)
|
||||
[project, project.default_merge_request_target].uniq
|
||||
end
|
||||
|
||||
def merge_request_button_visibility(merge_request, closed)
|
||||
return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) || merge_request.closed_without_fork?
|
||||
end
|
||||
|
|
|
@ -8,6 +8,14 @@ module SnippetsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def download_snippet_path(snippet)
|
||||
if snippet.project_id
|
||||
raw_namespace_project_snippet_path(@project.namespace, @project, snippet, inline: false)
|
||||
else
|
||||
raw_snippet_path(snippet, inline: false)
|
||||
end
|
||||
end
|
||||
|
||||
# Return the path of a snippets index for a user or for a project
|
||||
#
|
||||
# @returns String, path to snippet index
|
||||
|
|
|
@ -100,6 +100,7 @@ class MergeRequest < ActiveRecord::Base
|
|||
validates :merge_user, presence: true, if: :merge_when_pipeline_succeeds?, unless: :importing?
|
||||
validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?]
|
||||
validate :validate_fork, unless: :closed_without_fork?
|
||||
validate :validate_target_project, on: :create
|
||||
|
||||
scope :by_source_or_target_branch, ->(branch_name) do
|
||||
where("source_branch = :branch OR target_branch = :branch", branch: branch_name)
|
||||
|
@ -330,6 +331,12 @@ class MergeRequest < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def validate_target_project
|
||||
return true if target_project.merge_requests_enabled?
|
||||
|
||||
errors.add :base, 'Target project has disabled merge requests'
|
||||
end
|
||||
|
||||
def validate_fork
|
||||
return true unless target_project && source_project
|
||||
return true if target_project == source_project
|
||||
|
|
|
@ -1314,6 +1314,14 @@ class Project < ActiveRecord::Base
|
|||
namespace_id_changed?
|
||||
end
|
||||
|
||||
def default_merge_request_target
|
||||
if forked_from_project&.merge_requests_enabled?
|
||||
forked_from_project
|
||||
else
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :name_with_namespace, :full_name
|
||||
alias_method :human_name, :full_name
|
||||
alias_method :path_with_namespace, :full_path
|
||||
|
|
|
@ -505,14 +505,8 @@ class Repository
|
|||
delegate :tag_names, to: :raw_repository
|
||||
cache_method :tag_names, fallback: []
|
||||
|
||||
def branch_count
|
||||
branches.size
|
||||
end
|
||||
delegate :branch_count, :tag_count, to: :raw_repository
|
||||
cache_method :branch_count, fallback: 0
|
||||
|
||||
def tag_count
|
||||
raw_repository.rugged.tags.count
|
||||
end
|
||||
cache_method :tag_count, fallback: 0
|
||||
|
||||
def avatar
|
||||
|
|
|
@ -7,6 +7,9 @@ class StatusEntity < Grape::Entity
|
|||
expose :details_path
|
||||
|
||||
expose :favicon do |status|
|
||||
ActionController::Base.helpers.image_path(File.join('ci_favicons', "#{status.favicon}.ico"))
|
||||
dir = 'ci_favicons'
|
||||
dir = File.join(dir, 'dev') if Rails.env.development?
|
||||
|
||||
ActionController::Base.helpers.image_path(File.join(dir, "#{status.favicon}.ico"))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,7 +28,7 @@ module MergeRequests
|
|||
|
||||
def find_target_project
|
||||
return target_project if target_project.present? && can?(current_user, :read_project, target_project)
|
||||
project.forked_from_project || project
|
||||
project.default_merge_request_target
|
||||
end
|
||||
|
||||
def find_target_branch
|
||||
|
|
|
@ -281,7 +281,7 @@ class TodoService
|
|||
|
||||
def attributes_for_target(target)
|
||||
attributes = {
|
||||
project_id: target.project.id,
|
||||
project_id: target&.project&.id,
|
||||
target_id: target.id,
|
||||
target_type: target.class.name,
|
||||
commit_id: nil
|
||||
|
|
|
@ -73,6 +73,12 @@
|
|||
= container_reg
|
||||
%span.light.pull-right
|
||||
= boolean_to_icon Gitlab.config.registry.enabled
|
||||
- gitlab_pages = 'GitLab Pages'
|
||||
- gitlab_pages_enabled = Gitlab.config.pages.enabled
|
||||
%p{ "aria-label" => "#{gitlab_pages}: status " + (gitlab_pages_enabled ? "on" : "off") }
|
||||
= gitlab_pages
|
||||
%span.light.pull-right
|
||||
= boolean_to_icon gitlab_pages_enabled
|
||||
|
||||
.col-md-4
|
||||
%h4
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
= form_errors(hook)
|
||||
|
||||
.form-group
|
||||
= form.label :url, 'URL', class: 'control-label'
|
||||
.col-sm-10
|
||||
= form.text_field :url, class: 'form-control'
|
||||
.form-group
|
||||
= form.label :token, 'Secret Token', class: 'control-label'
|
||||
.col-sm-10
|
||||
= form.text_field :token, class: 'form-control'
|
||||
%p.help-block
|
||||
Use this token to validate received payloads
|
||||
.form-group
|
||||
= form.label :url, 'Trigger', class: 'control-label'
|
||||
.col-sm-10.prepend-top-10
|
||||
%div
|
||||
System hook will be triggered on set of events like creating project
|
||||
or adding ssh key. But you can also enable extra triggers like Push events.
|
||||
|
||||
.prepend-top-default
|
||||
= form.check_box :push_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= form.label :push_events, class: 'list-label' do
|
||||
%strong Push events
|
||||
%p.light
|
||||
This url will be triggered by a push to the repository
|
||||
%div
|
||||
= form.check_box :tag_push_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= form.label :tag_push_events, class: 'list-label' do
|
||||
%strong Tag push events
|
||||
%p.light
|
||||
This url will be triggered when a new tag is pushed to the repository
|
||||
.form-group
|
||||
= form.label :enable_ssl_verification, 'SSL verification', class: 'control-label checkbox'
|
||||
.col-sm-10
|
||||
.checkbox
|
||||
= form.label :enable_ssl_verification do
|
||||
= form.check_box :enable_ssl_verification
|
||||
%strong Enable SSL verification
|
|
@ -0,0 +1,14 @@
|
|||
- page_title 'Edit System Hook'
|
||||
%h3.page-title
|
||||
Edit System Hook
|
||||
|
||||
%p.light
|
||||
#{link_to 'System hooks ', help_page_path('system_hooks/system_hooks'), class: 'vlink'} can be
|
||||
used for binding events when GitLab creates a User or Project.
|
||||
|
||||
%hr
|
||||
|
||||
= form_for @hook, as: :hook, url: admin_hook_path, html: { class: 'form-horizontal' } do |f|
|
||||
= render partial: 'form', locals: { form: f, hook: @hook }
|
||||
.form-actions
|
||||
= f.submit 'Save changes', class: 'btn btn-create'
|
|
@ -1,57 +1,17 @@
|
|||
- page_title "System Hooks"
|
||||
- page_title 'System Hooks'
|
||||
%h3.page-title
|
||||
System hooks
|
||||
|
||||
%p.light
|
||||
#{link_to "System hooks ", help_page_path("system_hooks/system_hooks"), class: "vlink"} can be
|
||||
#{link_to 'System hooks ', help_page_path('system_hooks/system_hooks'), class: 'vlink'} can be
|
||||
used for binding events when GitLab creates a User or Project.
|
||||
|
||||
%hr
|
||||
|
||||
|
||||
= form_for @hook, as: :hook, url: admin_hooks_path, html: { class: 'form-horizontal' } do |f|
|
||||
= form_errors(@hook)
|
||||
|
||||
.form-group
|
||||
= f.label :url, 'URL', class: 'control-label'
|
||||
.col-sm-10
|
||||
= f.text_field :url, class: 'form-control'
|
||||
.form-group
|
||||
= f.label :token, 'Secret Token', class: 'control-label'
|
||||
.col-sm-10
|
||||
= f.text_field :token, class: 'form-control'
|
||||
%p.help-block
|
||||
Use this token to validate received payloads
|
||||
.form-group
|
||||
= f.label :url, "Trigger", class: 'control-label'
|
||||
.col-sm-10.prepend-top-10
|
||||
%div
|
||||
System hook will be triggered on set of events like creating project
|
||||
or adding ssh key. But you can also enable extra triggers like Push events.
|
||||
|
||||
.prepend-top-default
|
||||
= f.check_box :push_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= f.label :push_events, class: 'list-label' do
|
||||
%strong Push events
|
||||
%p.light
|
||||
This url will be triggered by a push to the repository
|
||||
%div
|
||||
= f.check_box :tag_push_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= f.label :tag_push_events, class: 'list-label' do
|
||||
%strong Tag push events
|
||||
%p.light
|
||||
This url will be triggered when a new tag is pushed to the repository
|
||||
.form-group
|
||||
= f.label :enable_ssl_verification, "SSL verification", class: 'control-label checkbox'
|
||||
.col-sm-10
|
||||
.checkbox
|
||||
= f.label :enable_ssl_verification do
|
||||
= f.check_box :enable_ssl_verification
|
||||
%strong Enable SSL verification
|
||||
= render partial: 'form', locals: { form: f, hook: @hook }
|
||||
.form-actions
|
||||
= f.submit "Add system hook", class: "btn btn-create"
|
||||
= f.submit 'Add system hook', class: 'btn btn-create'
|
||||
%hr
|
||||
|
||||
- if @hooks.any?
|
||||
|
@ -62,11 +22,12 @@
|
|||
- @hooks.each do |hook|
|
||||
%li
|
||||
.controls
|
||||
= link_to 'Test hook', admin_hook_test_path(hook), class: "btn btn-sm"
|
||||
= link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm"
|
||||
= link_to 'Test hook', test_admin_hook_path(hook), class: 'btn btn-sm'
|
||||
= link_to 'Edit', edit_admin_hook_path(hook), class: 'btn btn-sm'
|
||||
= link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: 'btn btn-remove btn-sm'
|
||||
.monospace= hook.url
|
||||
%div
|
||||
- %w(push_events tag_push_events issues_events note_events merge_requests_events build_events).each do |trigger|
|
||||
- if hook.send(trigger)
|
||||
%span.label.label-gray= trigger.titleize
|
||||
%span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
|
||||
%span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.discussion-notes
|
||||
%ul.notes{ data: { discussion_id: discussion.id } }
|
||||
= render partial: "projects/notes/note", collection: discussion.notes, as: :note
|
||||
= render partial: "shared/notes/note", collection: discussion.notes, as: :note
|
||||
|
||||
- if current_user
|
||||
.discussion-reply-holder
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
git init
|
||||
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
|
||||
git add .
|
||||
git commit
|
||||
git commit -m "Initial commit"
|
||||
git push -u origin master
|
||||
|
||||
%fieldset
|
||||
|
|
|
@ -1 +1,23 @@
|
|||
= render 'shared/web_hooks/form', hook: @hook, hooks: @hooks, url_components: [@project.namespace.becomes(Namespace), @project]
|
||||
.row.prepend-top-default
|
||||
.col-lg-3
|
||||
%h4.prepend-top-0
|
||||
= page_title
|
||||
%p
|
||||
#{link_to 'Webhooks', help_page_path('user/project/integrations/webhooks')} can be
|
||||
used for binding events when something is happening within the project.
|
||||
|
||||
.col-lg-9.append-bottom-default
|
||||
= form_for @hook, as: :hook, url: polymorphic_path([@project.namespace.becomes(Namespace), @project, :hooks]) do |f|
|
||||
= render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook }
|
||||
= f.submit 'Add webhook', class: 'btn btn-create'
|
||||
|
||||
%hr
|
||||
%h5.prepend-top-default
|
||||
Webhooks (#{@hooks.count})
|
||||
- if @hooks.any?
|
||||
%ul.well-list
|
||||
- @hooks.each do |hook|
|
||||
= render 'project_hook', hook: hook
|
||||
- else
|
||||
%p.settings-message.text-center.append-bottom-0
|
||||
No webhooks found, add one in the form above.
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
= render 'projects/settings/head'
|
||||
|
||||
.row.prepend-top-default
|
||||
.col-lg-3
|
||||
%h4.prepend-top-0
|
||||
= page_title
|
||||
%p
|
||||
#{link_to 'Webhooks', help_page_path('user/project/integrations/webhooks')} can be
|
||||
used for binding events when something is happening within the project.
|
||||
.col-lg-9.append-bottom-default
|
||||
= form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: namespace_project_hook_path do |f|
|
||||
= render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook }
|
||||
= f.submit 'Save changes', class: 'btn btn-create'
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
.panel-heading
|
||||
Target branch
|
||||
.panel-body.clearfix
|
||||
- projects = @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project]
|
||||
- projects = target_projects(@project)
|
||||
.merge-request-select.dropdown
|
||||
= f.hidden_field :target_project_id
|
||||
= dropdown_toggle f.object.target_project.path_with_namespace, { toggle: "dropdown", field_name: "#{f.object_name}[target_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-target-project" }
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
- access = note_max_access_for_user(note)
|
||||
- if access
|
||||
%span.note-role= access
|
||||
|
||||
- if note.resolvable?
|
||||
- can_resolve = can?(current_user, :resolve_note, note)
|
||||
%resolve-btn{ "project-path" => project_path(note.project),
|
||||
"discussion-id" => note.discussion_id(@noteable),
|
||||
":note-id" => note.id,
|
||||
":resolved" => note.resolved?,
|
||||
":can-resolve" => can_resolve,
|
||||
":author-name" => "'#{j(note.author.name)}'",
|
||||
"author-avatar" => note.author.avatar_url,
|
||||
":note-truncated" => "'#{j(truncate(note.note, length: 17))}'",
|
||||
":resolved-by" => "'#{j(note.resolved_by.try(:name))}'",
|
||||
"v-show" => "#{can_resolve || note.resolved?}",
|
||||
"inline-template" => true,
|
||||
"ref" => "note_#{note.id}" }
|
||||
|
||||
%button.note-action-button.line-resolve-btn{ type: "button",
|
||||
class: ("is-disabled" unless can_resolve),
|
||||
":class" => "{ 'is-active': isResolved }",
|
||||
":aria-label" => "buttonText",
|
||||
"@click" => "resolve",
|
||||
":title" => "buttonText",
|
||||
":ref" => "'button'" }
|
||||
|
||||
= icon('spin spinner', 'v-show' => 'loading', class: 'loading', 'aria-hidden' => 'true', 'aria-label' => 'Loading')
|
||||
%div{ 'v-show' => '!loading' }= render 'shared/icons/icon_status_success.svg'
|
||||
|
||||
- if current_user
|
||||
- if note.emoji_awardable?
|
||||
- user_authored = note.user_authored?(current_user)
|
||||
= link_to '#', title: 'Award Emoji', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored}", data: { position: 'right' } do
|
||||
= icon('spinner spin')
|
||||
%span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
|
||||
%span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
|
||||
%span{ class: 'link-highlight award-control-icon-super-positive' }= custom_icon('emoji_smile')
|
||||
|
||||
- if note_editable
|
||||
= link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
|
||||
= icon('pencil', class: 'link-highlight')
|
||||
= link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do
|
||||
= icon('trash-o', class: 'danger-highlight')
|
|
@ -0,0 +1,3 @@
|
|||
.original-note-content.hidden{ data: { post_url: namespace_project_note_path(@project.namespace, @project, note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } }
|
||||
#{note.note}
|
||||
%textarea.hidden.js-task-list-field.original-task-list{ data: {update_url: namespace_project_note_path(@project.namespace, @project, note) } }= note.note
|
|
@ -1,101 +0,0 @@
|
|||
- 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: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable, note_id: note.id} }
|
||||
.timeline-entry-inner
|
||||
.timeline-icon
|
||||
- if note.system
|
||||
= icon_for_system_note(note)
|
||||
- else
|
||||
%a{ href: user_path(note.author) }
|
||||
= image_tag avatar_icon(note.author), alt: '', class: 'avatar s40'
|
||||
.timeline-content
|
||||
.note-header
|
||||
.note-header-info
|
||||
%a{ href: user_path(note.author) }
|
||||
%span.hidden-xs
|
||||
= sanitize(note.author.name)
|
||||
%span.note-headline-light
|
||||
= note.author.to_reference
|
||||
%span.note-headline-light
|
||||
%span.note-headline-meta
|
||||
- unless note.system
|
||||
commented
|
||||
- if note.system
|
||||
%span.system-note-message
|
||||
= note.redacted_note_html
|
||||
%a{ href: "##{dom_id(note)}" }
|
||||
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
|
||||
- unless note.system?
|
||||
.note-actions
|
||||
- access = note_max_access_for_user(note)
|
||||
- if access
|
||||
%span.note-role= access
|
||||
|
||||
- if note.resolvable?
|
||||
- can_resolve = can?(current_user, :resolve_note, note)
|
||||
%resolve-btn{ "project-path" => project_path(note.project),
|
||||
"discussion-id" => note.discussion_id(@noteable),
|
||||
":note-id" => note.id,
|
||||
":resolved" => note.resolved?,
|
||||
":can-resolve" => can_resolve,
|
||||
":author-name" => "'#{j(note.author.name)}'",
|
||||
"author-avatar" => note.author.avatar_url,
|
||||
":note-truncated" => "'#{j(truncate(note.note, length: 17))}'",
|
||||
":resolved-by" => "'#{j(note.resolved_by.try(:name))}'",
|
||||
"v-show" => "#{can_resolve || note.resolved?}",
|
||||
"inline-template" => true,
|
||||
"ref" => "note_#{note.id}" }
|
||||
|
||||
%button.note-action-button.line-resolve-btn{ type: "button",
|
||||
class: ("is-disabled" unless can_resolve),
|
||||
":class" => "{ 'is-active': isResolved }",
|
||||
":aria-label" => "buttonText",
|
||||
"@click" => "resolve",
|
||||
":title" => "buttonText",
|
||||
":ref" => "'button'" }
|
||||
|
||||
= icon("spin spinner", "v-show" => "loading", class: 'loading')
|
||||
%div{ 'v-show' => '!loading' }= render "shared/icons/icon_status_success.svg"
|
||||
|
||||
- if current_user
|
||||
- if note.emoji_awardable?
|
||||
- user_authored = note.user_authored?(current_user)
|
||||
= link_to '#', title: 'Award Emoji', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored}", data: { position: 'right' } do
|
||||
= icon('spinner spin')
|
||||
%span{ class: "link-highlight award-control-icon-neutral" }= custom_icon('emoji_slightly_smiling_face')
|
||||
%span{ class: "link-highlight award-control-icon-positive" }= custom_icon('emoji_smiley')
|
||||
%span{ class: "link-highlight award-control-icon-super-positive" }= custom_icon('emoji_smile')
|
||||
|
||||
- if note_editable
|
||||
= link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
|
||||
= icon('pencil', class: 'link-highlight')
|
||||
= link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do
|
||||
= icon('trash-o', class: 'danger-highlight')
|
||||
.note-body{ class: note_editable ? 'js-task-list-container' : '' }
|
||||
.note-text.md
|
||||
= note.redacted_note_html
|
||||
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
|
||||
- if note_editable
|
||||
.original-note-content.hidden{ data: { post_url: namespace_project_note_path(@project.namespace, @project, note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } }
|
||||
#{note.note}
|
||||
%textarea.hidden.js-task-list-field.original-task-list{ data: {update_url: namespace_project_note_path(@project.namespace, @project, note) } }= note.note
|
||||
.note-awards
|
||||
= render 'award_emoji/awards_block', awardable: note, inline: false
|
||||
- if note.system
|
||||
.system-note-commit-list-toggler
|
||||
Toggle commit list
|
||||
%i.fa.fa-angle-down
|
||||
- if note.attachment.url
|
||||
.note-attachment
|
||||
- if note.attachment.image?
|
||||
= link_to note.attachment.url, target: '_blank' do
|
||||
= image_tag note.attachment.url, class: 'note-image-attach'
|
||||
.attachment
|
||||
= link_to note.attachment.url, target: '_blank' do
|
||||
= icon('paperclip')
|
||||
= note.attachment_identifier
|
||||
= link_to delete_attachment_namespace_project_note_path(note.project.namespace, note.project, note),
|
||||
title: 'Delete this attachment', method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: 'danger js-note-attachment-delete' do
|
||||
= icon('trash-o', class: 'cred')
|
|
@ -1,5 +1,5 @@
|
|||
%ul#notes-list.notes.main-notes-list.timeline
|
||||
= render "projects/notes/notes"
|
||||
= render "shared/notes/notes"
|
||||
|
||||
= render 'projects/notes/edit_form'
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
%span
|
||||
Members
|
||||
- if can_edit
|
||||
= nav_link(controller: [:integrations, :services]) do
|
||||
= nav_link(controller: [:integrations, :services, :hooks]) do
|
||||
= link_to project_settings_integrations_path(@project), title: 'Integrations' do
|
||||
%span
|
||||
Integrations
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
.col-md-4.col-lg-5.text-right-lg.prepend-top-5
|
||||
%span.append-right-10.inline
|
||||
SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
|
||||
= link_to "Edit", edit_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm"
|
||||
= link_to "Test", test_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm"
|
||||
= link_to namespace_project_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent" do
|
||||
%span.sr-only Remove
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
- 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: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable, note_id: note.id} }
|
||||
.timeline-entry-inner
|
||||
.timeline-icon
|
||||
- if note.system
|
||||
= icon_for_system_note(note)
|
||||
- else
|
||||
%a{ href: user_path(note.author) }
|
||||
= image_tag avatar_icon(note.author), alt: '', class: 'avatar s40'
|
||||
.timeline-content
|
||||
.note-header
|
||||
.note-header-info
|
||||
%a{ href: user_path(note.author) }
|
||||
%span.hidden-xs
|
||||
= sanitize(note.author.name)
|
||||
%span.note-headline-light
|
||||
= note.author.to_reference
|
||||
%span.note-headline-light
|
||||
%span.note-headline-meta
|
||||
- unless note.system
|
||||
commented
|
||||
- if note.system
|
||||
%span.system-note-message
|
||||
= note.redacted_note_html
|
||||
%a{ href: "##{dom_id(note)}" }
|
||||
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
|
||||
- unless note.system?
|
||||
.note-actions
|
||||
- if note.for_personal_snippet?
|
||||
= render 'snippets/notes/actions', note: note, note_editable: note_editable
|
||||
- else
|
||||
= render 'projects/notes/actions', note: note, note_editable: note_editable
|
||||
.note-body{ class: note_editable ? 'js-task-list-container' : '' }
|
||||
.note-text.md
|
||||
= note.redacted_note_html
|
||||
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
|
||||
- if note_editable
|
||||
- if note.for_personal_snippet?
|
||||
= render 'snippets/notes/edit', note: note
|
||||
- else
|
||||
= render 'projects/notes/edit', note: note
|
||||
.note-awards
|
||||
= render 'award_emoji/awards_block', awardable: note, inline: false
|
||||
- if note.system
|
||||
.system-note-commit-list-toggler
|
||||
Toggle commit list
|
||||
%i.fa.fa-angle-down
|
||||
- if note.attachment.url
|
||||
.note-attachment
|
||||
- if note.attachment.image?
|
||||
= link_to note.attachment.url, target: '_blank' do
|
||||
= image_tag note.attachment.url, class: 'note-image-attach'
|
||||
.attachment
|
||||
= link_to note.attachment.url, target: '_blank' do
|
||||
= icon('paperclip')
|
||||
= note.attachment_identifier
|
||||
= link_to delete_attachment_namespace_project_note_path(note.project.namespace, note.project, note),
|
||||
title: 'Delete this attachment', method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: 'danger js-note-attachment-delete' do
|
||||
= icon('trash-o', class: 'cred')
|
|
@ -1,8 +1,8 @@
|
|||
- if defined?(@discussions)
|
||||
- @discussions.each do |discussion|
|
||||
- if discussion.individual_note?
|
||||
= render partial: "projects/notes/note", collection: discussion.notes, as: :note
|
||||
= render partial: "shared/notes/note", collection: discussion.notes, as: :note
|
||||
- else
|
||||
= render 'discussions/discussion', discussion: discussion
|
||||
- else
|
||||
= render partial: "projects/notes/note", collection: @notes, as: :note
|
||||
= render partial: "shared/notes/note", collection: @notes, as: :note
|
|
@ -18,7 +18,6 @@
|
|||
= copy_blob_source_button(blob)
|
||||
= open_raw_blob_button(blob)
|
||||
|
||||
- if defined?(download_path) && download_path
|
||||
= link_to icon('download'), download_path, class: "btn btn-sm has-tooltip", title: 'Download', data: { container: 'body' }
|
||||
= link_to icon('download'), download_snippet_path(@snippet), target: '_blank', class: "btn btn-sm has-tooltip", title: 'Download', data: { container: 'body' }
|
||||
|
||||
= render 'projects/blob/content', blob: blob
|
||||
|
|
|
@ -1,102 +1,82 @@
|
|||
.row.prepend-top-default
|
||||
.col-lg-3
|
||||
%h4.prepend-top-0
|
||||
= page_title
|
||||
%p
|
||||
#{link_to "Webhooks", help_page_path("user/project/integrations/webhooks")} can be
|
||||
used for binding events when something is happening within the project.
|
||||
.col-lg-9.append-bottom-default
|
||||
= form_for hook, as: :hook, url: polymorphic_path(url_components + [:hooks]) do |f|
|
||||
= form_errors(hook)
|
||||
= form_errors(hook)
|
||||
|
||||
.form-group
|
||||
= f.label :url, "URL", class: 'label-light'
|
||||
= f.text_field :url, class: "form-control", placeholder: 'http://example.com/trigger-ci.json'
|
||||
.form-group
|
||||
= f.label :token, "Secret Token", class: 'label-light'
|
||||
= f.text_field :token, class: "form-control", placeholder: ''
|
||||
%p.help-block
|
||||
Use this token to validate received payloads. It will be sent with the request in the X-Gitlab-Token HTTP header.
|
||||
.form-group
|
||||
= f.label :url, "Trigger", class: 'label-light'
|
||||
%ul.list-unstyled
|
||||
%li
|
||||
= f.check_box :push_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= f.label :push_events, class: 'list-label' do
|
||||
%strong Push events
|
||||
%p.light
|
||||
This URL will be triggered by a push to the repository
|
||||
%li
|
||||
= f.check_box :tag_push_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= f.label :tag_push_events, class: 'list-label' do
|
||||
%strong Tag push events
|
||||
%p.light
|
||||
This URL will be triggered when a new tag is pushed to the repository
|
||||
%li
|
||||
= f.check_box :note_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= f.label :note_events, class: 'list-label' do
|
||||
%strong Comments
|
||||
%p.light
|
||||
This URL will be triggered when someone adds a comment
|
||||
%li
|
||||
= f.check_box :issues_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= f.label :issues_events, class: 'list-label' do
|
||||
%strong Issues events
|
||||
%p.light
|
||||
This URL will be triggered when an issue is created/updated/merged
|
||||
%li
|
||||
= f.check_box :confidential_issues_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= f.label :confidential_issues_events, class: 'list-label' do
|
||||
%strong Confidential Issues events
|
||||
%p.light
|
||||
This URL will be triggered when a confidential issue is created/updated/merged
|
||||
%li
|
||||
= f.check_box :merge_requests_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= f.label :merge_requests_events, class: 'list-label' do
|
||||
%strong Merge Request events
|
||||
%p.light
|
||||
This URL will be triggered when a merge request is created/updated/merged
|
||||
%li
|
||||
= f.check_box :build_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= f.label :build_events, class: 'list-label' do
|
||||
%strong Jobs events
|
||||
%p.light
|
||||
This URL will be triggered when the job status changes
|
||||
%li
|
||||
= f.check_box :pipeline_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= f.label :pipeline_events, class: 'list-label' do
|
||||
%strong Pipeline events
|
||||
%p.light
|
||||
This URL will be triggered when the pipeline status changes
|
||||
%li
|
||||
= f.check_box :wiki_page_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= f.label :wiki_page_events, class: 'list-label' do
|
||||
%strong Wiki Page events
|
||||
%p.light
|
||||
This URL will be triggered when a wiki page is created/updated
|
||||
.form-group
|
||||
= f.label :enable_ssl_verification, "SSL verification", class: 'label-light checkbox'
|
||||
.checkbox
|
||||
= f.label :enable_ssl_verification do
|
||||
= f.check_box :enable_ssl_verification
|
||||
%strong Enable SSL verification
|
||||
= f.submit "Add webhook", class: "btn btn-create"
|
||||
%hr
|
||||
%h5.prepend-top-default
|
||||
Webhooks (#{hooks.count})
|
||||
- if hooks.any?
|
||||
%ul.well-list
|
||||
- hooks.each do |hook|
|
||||
= render "project_hook", hook: hook
|
||||
- else
|
||||
%p.settings-message.text-center.append-bottom-0
|
||||
No webhooks found, add one in the form above.
|
||||
.form-group
|
||||
= form.label :url, 'URL', class: 'label-light'
|
||||
= form.text_field :url, class: 'form-control', placeholder: 'http://example.com/trigger-ci.json'
|
||||
.form-group
|
||||
= form.label :token, 'Secret Token', class: 'label-light'
|
||||
= form.text_field :token, class: 'form-control', placeholder: ''
|
||||
%p.help-block
|
||||
Use this token to validate received payloads. It will be sent with the request in the X-Gitlab-Token HTTP header.
|
||||
.form-group
|
||||
= form.label :url, 'Trigger', class: 'label-light'
|
||||
%ul.list-unstyled
|
||||
%li
|
||||
= form.check_box :push_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= form.label :push_events, class: 'list-label' do
|
||||
%strong Push events
|
||||
%p.light
|
||||
This URL will be triggered by a push to the repository
|
||||
%li
|
||||
= form.check_box :tag_push_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= form.label :tag_push_events, class: 'list-label' do
|
||||
%strong Tag push events
|
||||
%p.light
|
||||
This URL will be triggered when a new tag is pushed to the repository
|
||||
%li
|
||||
= form.check_box :note_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= form.label :note_events, class: 'list-label' do
|
||||
%strong Comments
|
||||
%p.light
|
||||
This URL will be triggered when someone adds a comment
|
||||
%li
|
||||
= form.check_box :issues_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= form.label :issues_events, class: 'list-label' do
|
||||
%strong Issues events
|
||||
%p.light
|
||||
This URL will be triggered when an issue is created/updated/merged
|
||||
%li
|
||||
= form.check_box :confidential_issues_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= form.label :confidential_issues_events, class: 'list-label' do
|
||||
%strong Confidential Issues events
|
||||
%p.light
|
||||
This URL will be triggered when a confidential issue is created/updated/merged
|
||||
%li
|
||||
= form.check_box :merge_requests_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= form.label :merge_requests_events, class: 'list-label' do
|
||||
%strong Merge Request events
|
||||
%p.light
|
||||
This URL will be triggered when a merge request is created/updated/merged
|
||||
%li
|
||||
= form.check_box :build_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= form.label :build_events, class: 'list-label' do
|
||||
%strong Jobs events
|
||||
%p.light
|
||||
This URL will be triggered when the job status changes
|
||||
%li
|
||||
= form.check_box :pipeline_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= form.label :pipeline_events, class: 'list-label' do
|
||||
%strong Pipeline events
|
||||
%p.light
|
||||
This URL will be triggered when the pipeline status changes
|
||||
%li
|
||||
= form.check_box :wiki_page_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= form.label :wiki_page_events, class: 'list-label' do
|
||||
%strong Wiki Page events
|
||||
%p.light
|
||||
This URL will be triggered when a wiki page is created/updated
|
||||
.form-group
|
||||
= form.label :enable_ssl_verification, 'SSL verification', class: 'label-light checkbox'
|
||||
.checkbox
|
||||
= form.label :enable_ssl_verification do
|
||||
= form.check_box :enable_ssl_verification
|
||||
%strong Enable SSL verification
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
- if current_user
|
||||
- if note.emoji_awardable?
|
||||
- user_authored = note.user_authored?(current_user)
|
||||
= link_to '#', title: 'Award Emoji', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored}", data: { position: 'right' } do
|
||||
= icon('spinner spin')
|
||||
%span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
|
||||
%span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
|
||||
%span{ class: 'link-highlight award-control-icon-super-positive' }= custom_icon('emoji_smile')
|
||||
- if note_editable
|
||||
= link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
|
||||
= icon('pencil', class: 'link-highlight')
|
||||
= link_to snippet_note_path(note.noteable, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do
|
||||
= icon('trash-o', class: 'danger-highlight')
|
|
@ -0,0 +1,2 @@
|
|||
%ul#notes-list.notes.main-notes-list.timeline
|
||||
= render "projects/notes/notes"
|
|
@ -3,7 +3,10 @@
|
|||
= render 'shared/snippets/header'
|
||||
|
||||
%article.file-holder.snippet-file-content
|
||||
= render 'shared/snippets/blob', download_path: download_snippet_path(@snippet)
|
||||
= render 'shared/snippets/blob'
|
||||
|
||||
.row-content-block.top-block.content-component-block
|
||||
= render 'award_emoji/awards_block', awardable: @snippet, inline: true
|
||||
|
||||
%ul#notes-list.notes.main-notes-list.timeline
|
||||
#notes= render 'shared/notes/notes'
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Display comments for personal snippets
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Implement ability to edit hooks
|
||||
merge_request: 10816
|
||||
author: Alexander Randa
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Disallow merge requests from fork when source project have disabled merge requests
|
||||
merge_request:
|
||||
author: mhasbini
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Display GitLab Pages status in Admin Dashboard
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Change Git commit command in Existing folder to git commit -m
|
||||
merge_request: 10900
|
||||
author: TM Lee
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Updated CI status favicons to include the tanuki
|
||||
merge_request: 10923
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Add download button to project snippets
|
||||
merge_request:
|
||||
author:
|
|
@ -50,8 +50,10 @@ namespace :admin do
|
|||
|
||||
resources :deploy_keys, only: [:index, :new, :create, :destroy]
|
||||
|
||||
resources :hooks, only: [:index, :create, :destroy] do
|
||||
get :test
|
||||
resources :hooks, only: [:index, :create, :edit, :update, :destroy] do
|
||||
member do
|
||||
get :test
|
||||
end
|
||||
end
|
||||
|
||||
resources :broadcast_messages, only: [:index, :edit, :create, :update, :destroy] do
|
||||
|
|
|
@ -44,7 +44,7 @@ constraints(ProjectUrlConstrainer.new) do
|
|||
|
||||
resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do
|
||||
member do
|
||||
get 'raw'
|
||||
get :raw
|
||||
post :mark_as_spam
|
||||
end
|
||||
end
|
||||
|
@ -185,7 +185,7 @@ constraints(ProjectUrlConstrainer.new) do
|
|||
end
|
||||
end
|
||||
|
||||
resources :hooks, only: [:index, :create, :destroy], constraints: { id: /\d+/ } do
|
||||
resources :hooks, only: [:index, :create, :edit, :update, :destroy], constraints: { id: /\d+/ } do
|
||||
member do
|
||||
get :test
|
||||
end
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
resources :snippets, concerns: :awardable do
|
||||
member do
|
||||
get 'raw'
|
||||
get 'download'
|
||||
get :raw
|
||||
post :mark_as_spam
|
||||
post :preview_markdown
|
||||
end
|
||||
|
||||
scope module: :snippets do
|
||||
resources :notes, only: [:index, :create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do
|
||||
member do
|
||||
delete :delete_attachment
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
get '/s/:username', to: redirect('/u/%{username}/snippets'),
|
||||
|
|
|
@ -13,7 +13,7 @@ you need to use with GitLab.
|
|||
| LB Port | Backend Port | Protocol |
|
||||
| ------- | ------------ | --------------- |
|
||||
| 80 | 80 | HTTP [^1] |
|
||||
| 443 | 443 | HTTPS [^1] [^2] |
|
||||
| 443 | 443 | TCP or HTTPS [^1] [^2] |
|
||||
| 22 | 22 | TCP |
|
||||
|
||||
## GitLab Pages Ports
|
||||
|
|
|
@ -32,7 +32,7 @@ In brief:
|
|||
|
||||
As web terminals use WebSockets, every HTTP/HTTPS reverse proxy in front of
|
||||
Workhorse needs to be configured to pass the `Connection` and `Upgrade` headers
|
||||
through to the next one in the chain. If you installed Gitlab using Omnibus, or
|
||||
through to the next one in the chain. If you installed GitLab using Omnibus, or
|
||||
from source, starting with GitLab 8.15, this should be done by the default
|
||||
configuration, so there's no need for you to do anything.
|
||||
|
||||
|
@ -58,7 +58,7 @@ document for more details.
|
|||
If you'd like to disable web terminal support in GitLab, just stop passing
|
||||
the `Connection` and `Upgrade` hop-by-hop headers in the *first* HTTP reverse
|
||||
proxy in the chain. For most users, this will be the NGINX server bundled with
|
||||
Omnibus Gitlab, in which case, you need to:
|
||||
Omnibus GitLab, in which case, you need to:
|
||||
|
||||
* Find the `nginx['proxy_set_headers']` section of your `gitlab.rb` file
|
||||
* Ensure the whole block is uncommented, and then comment out or remove the
|
||||
|
|
|
@ -1,24 +1,28 @@
|
|||
# How to create a project in GitLab
|
||||
|
||||
There are two ways to create a new project in GitLab.
|
||||
|
||||
1. While in your dashboard, you can create a new project using the **New project**
|
||||
green button or you can use the cross icon in the upper right corner next to
|
||||
your avatar which is always visible.
|
||||
1. In your dashboard, click the green **New project** button or use the plus
|
||||
icon in the upper right corner of the navigation bar.
|
||||
|
||||
![Create a project](img/create_new_project_button.png)
|
||||
|
||||
1. From there you can see several options.
|
||||
1. This opens the **New project** page.
|
||||
|
||||
![Project information](img/create_new_project_info.png)
|
||||
|
||||
1. Fill out the information:
|
||||
1. Provide the following information:
|
||||
- Enter the name of your project in the **Project name** field. You can't use
|
||||
special characters, but you can use spaces, hyphens, underscores or even
|
||||
emoji.
|
||||
- If you have a project in a different repository, you can [import it] by
|
||||
clicking an **Import project from** button provided this is enabled in
|
||||
your GitLab instance. Ask your administrator if not.
|
||||
- The **Project description (optional)** field enables you to enter a
|
||||
description for your project's dashboard, which will help others
|
||||
understand what your project is about. Though it's not required, it's a good
|
||||
idea to fill this in.
|
||||
- Changing the **Visibility Level** modifies the project's
|
||||
[viewing and access rights](../public_access/public_access.md) for users.
|
||||
|
||||
1. "Project name" is the name of your project (you can't use special characters,
|
||||
but you can use spaces, hyphens, underscores or even emojis).
|
||||
1. The "Project description" is optional and will be shown in your project's
|
||||
dashboard so others can briefly understand what your project is about.
|
||||
1. Select a [visibility level](../public_access/public_access.md).
|
||||
1. You can also [import your existing projects](../workflow/importing/README.md).
|
||||
1. Click **Create project**.
|
||||
|
||||
1. Finally, click **Create project**.
|
||||
[import it]: ../workflow/importing/README.md
|
||||
|
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 3.6 KiB |
|
@ -49,6 +49,8 @@ sudo gem install bundler --no-ri --no-rdoc
|
|||
### 4. Get latest code
|
||||
|
||||
```bash
|
||||
cd /home/git/gitlab
|
||||
|
||||
sudo -u git -H git fetch --all
|
||||
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
|
||||
```
|
||||
|
|
|
@ -49,6 +49,8 @@ sudo gem install bundler --no-ri --no-rdoc
|
|||
### 4. Get latest code
|
||||
|
||||
```bash
|
||||
cd /home/git/gitlab
|
||||
|
||||
sudo -u git -H git fetch --all
|
||||
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
|
||||
```
|
||||
|
|
|
@ -49,6 +49,8 @@ sudo gem install bundler --no-ri --no-rdoc
|
|||
### 4. Get latest code
|
||||
|
||||
```bash
|
||||
cd /home/git/gitlab
|
||||
|
||||
sudo -u git -H git fetch --all
|
||||
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
|
||||
```
|
||||
|
|
|
@ -49,6 +49,8 @@ sudo gem install bundler --no-ri --no-rdoc
|
|||
### 4. Get latest code
|
||||
|
||||
```bash
|
||||
cd /home/git/gitlab
|
||||
|
||||
sudo -u git -H git fetch --all
|
||||
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
|
||||
```
|
||||
|
|
|
@ -20,6 +20,8 @@ module API
|
|||
error!(errors[:validate_fork], 422)
|
||||
elsif errors[:validate_branches].any?
|
||||
conflict!(errors[:validate_branches])
|
||||
elsif errors[:base].any?
|
||||
error!(errors[:base], 422)
|
||||
end
|
||||
|
||||
render_api_error!(errors, 400)
|
||||
|
|
|
@ -23,6 +23,8 @@ module API
|
|||
error!(errors[:validate_fork], 422)
|
||||
elsif errors[:validate_branches].any?
|
||||
conflict!(errors[:validate_branches])
|
||||
elsif errors[:base].any?
|
||||
error!(errors[:base], 422)
|
||||
end
|
||||
|
||||
render_api_error!(errors, 400)
|
||||
|
|
|
@ -122,13 +122,30 @@ module Gitlab
|
|||
|
||||
# Returns the number of valid branches
|
||||
def branch_count
|
||||
rugged.branches.count do |ref|
|
||||
begin
|
||||
ref.name && ref.target # ensures the branch is valid
|
||||
Gitlab::GitalyClient.migrate(:branch_names) do |is_enabled|
|
||||
if is_enabled
|
||||
gitaly_ref_client.count_branch_names
|
||||
else
|
||||
rugged.branches.count do |ref|
|
||||
begin
|
||||
ref.name && ref.target # ensures the branch is valid
|
||||
|
||||
true
|
||||
rescue Rugged::ReferenceError
|
||||
false
|
||||
true
|
||||
rescue Rugged::ReferenceError
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the number of valid tags
|
||||
def tag_count
|
||||
Gitlab::GitalyClient.migrate(:tag_names) do |is_enabled|
|
||||
if is_enabled
|
||||
gitaly_ref_client.count_tag_names
|
||||
else
|
||||
rugged.tags.count
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -34,6 +34,14 @@ module Gitlab
|
|||
stub.find_ref_name(request).name
|
||||
end
|
||||
|
||||
def count_tag_names
|
||||
tag_names.count
|
||||
end
|
||||
|
||||
def count_branch_names
|
||||
branch_names.count
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def consume_refs_response(response, prefix:)
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
#!/bin/sh
|
||||
# Sends Slack notification ERROR_MSG to CHANNEL
|
||||
# An env. variable CI_SLACK_WEBHOOK_URL needs to be set.
|
||||
|
||||
CHANNEL=$1
|
||||
ERROR_MSG=$2
|
||||
|
||||
if [ -z "$CHANNEL" ] || [ -z "$ERROR_MSG" ] || [ -z "$CI_SLACK_WEBHOOK_URL" ]; then
|
||||
echo "Missing argument(s) - Use: $0 channel message"
|
||||
echo "and set CI_SLACK_WEBHOOK_URL environment variable."
|
||||
else
|
||||
curl -X POST --data-urlencode 'payload={"channel": "'"$CHANNEL"'", "username": "gitlab-ci", "text": "'"$ERROR_MSG"'", "icon_emoji": ":gitlab:"}' "$CI_SLACK_WEBHOOK_URL"
|
||||
fi
|
|
@ -167,6 +167,47 @@ describe Projects::NotesController do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'DELETE destroy' do
|
||||
let(:request_params) do
|
||||
{
|
||||
namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
id: note,
|
||||
format: :js
|
||||
}
|
||||
end
|
||||
|
||||
context 'user is the author of a note' do
|
||||
before do
|
||||
sign_in(note.author)
|
||||
project.team << [note.author, :developer]
|
||||
end
|
||||
|
||||
it "returns status 200 for html" do
|
||||
delete :destroy, request_params
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it "deletes the note" do
|
||||
expect { delete :destroy, request_params }.to change { Note.count }.from(1).to(0)
|
||||
end
|
||||
end
|
||||
|
||||
context 'user is not the author of a note' do
|
||||
before do
|
||||
sign_in(user)
|
||||
project.team << [user, :developer]
|
||||
end
|
||||
|
||||
it "returns status 404" do
|
||||
delete :destroy, request_params
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST toggle_award_emoji' do
|
||||
before do
|
||||
sign_in(user)
|
||||
|
|
|
@ -0,0 +1,196 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Snippets::NotesController do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
let(:private_snippet) { create(:personal_snippet, :private) }
|
||||
let(:internal_snippet) { create(:personal_snippet, :internal) }
|
||||
let(:public_snippet) { create(:personal_snippet, :public) }
|
||||
|
||||
let(:note_on_private) { create(:note_on_personal_snippet, noteable: private_snippet) }
|
||||
let(:note_on_internal) { create(:note_on_personal_snippet, noteable: internal_snippet) }
|
||||
let(:note_on_public) { create(:note_on_personal_snippet, noteable: public_snippet) }
|
||||
|
||||
describe 'GET index' do
|
||||
context 'when a snippet is public' do
|
||||
before do
|
||||
note_on_public
|
||||
|
||||
get :index, { snippet_id: public_snippet }
|
||||
end
|
||||
|
||||
it "returns status 200" do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it "returns not empty array of notes" do
|
||||
expect(JSON.parse(response.body)["notes"].empty?).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a snippet is internal' do
|
||||
before do
|
||||
note_on_internal
|
||||
end
|
||||
|
||||
context 'when user not logged in' do
|
||||
it "returns status 404" do
|
||||
get :index, { snippet_id: internal_snippet }
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user logged in' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it "returns status 200" do
|
||||
get :index, { snippet_id: internal_snippet }
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a snippet is private' do
|
||||
before do
|
||||
note_on_private
|
||||
end
|
||||
|
||||
context 'when user not logged in' do
|
||||
it "returns status 404" do
|
||||
get :index, { snippet_id: private_snippet }
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user other than author logged in' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it "returns status 404" do
|
||||
get :index, { snippet_id: private_snippet }
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when author logged in' do
|
||||
before do
|
||||
note_on_private
|
||||
|
||||
sign_in(private_snippet.author)
|
||||
end
|
||||
|
||||
it "returns status 200" do
|
||||
get :index, { snippet_id: private_snippet }
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it "returns 1 note" do
|
||||
get :index, { snippet_id: private_snippet }
|
||||
|
||||
expect(JSON.parse(response.body)['notes'].count).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'dont show non visible notes' do
|
||||
before do
|
||||
note_on_public
|
||||
|
||||
sign_in(user)
|
||||
|
||||
expect_any_instance_of(Note).to receive(:cross_reference_not_visible_for?).and_return(true)
|
||||
end
|
||||
|
||||
it "does not return any note" do
|
||||
get :index, { snippet_id: public_snippet }
|
||||
|
||||
expect(JSON.parse(response.body)['notes'].count).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE destroy' do
|
||||
let(:request_params) do
|
||||
{
|
||||
snippet_id: public_snippet,
|
||||
id: note_on_public,
|
||||
format: :js
|
||||
}
|
||||
end
|
||||
|
||||
context 'when user is the author of a note' do
|
||||
before do
|
||||
sign_in(note_on_public.author)
|
||||
end
|
||||
|
||||
it "returns status 200" do
|
||||
delete :destroy, request_params
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it "deletes the note" do
|
||||
expect{ delete :destroy, request_params }.to change{ Note.count }.from(1).to(0)
|
||||
end
|
||||
|
||||
context 'system note' do
|
||||
before do
|
||||
expect_any_instance_of(Note).to receive(:system?).and_return(true)
|
||||
end
|
||||
|
||||
it "does not delete the note" do
|
||||
expect{ delete :destroy, request_params }.not_to change{ Note.count }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not the author of a note' do
|
||||
before do
|
||||
sign_in(user)
|
||||
|
||||
note_on_public
|
||||
end
|
||||
|
||||
it "returns status 404" do
|
||||
delete :destroy, request_params
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
|
||||
it "does not update the note" do
|
||||
expect{ delete :destroy, request_params }.not_to change{ Note.count }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST toggle_award_emoji' do
|
||||
let(:note) { create(:note_on_personal_snippet, noteable: public_snippet) }
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
subject { post(:toggle_award_emoji, snippet_id: public_snippet, id: note.id, name: "thumbsup") }
|
||||
|
||||
it "toggles the award emoji" do
|
||||
expect { subject }.to change { note.award_emoji.count }.by(1)
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it "removes the already awarded emoji when it exists" do
|
||||
note.toggle_award_emoji('thumbsup', user) # create award emoji before
|
||||
|
||||
expect { subject }.to change { AwardEmoji.count }.by(-1)
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -350,144 +350,138 @@ describe SnippetsController do
|
|||
end
|
||||
end
|
||||
|
||||
%w(raw download).each do |action|
|
||||
describe "GET #{action}" do
|
||||
context 'when the personal snippet is private' do
|
||||
let(:personal_snippet) { create(:personal_snippet, :private, author: user) }
|
||||
describe "GET #raw" do
|
||||
context 'when the personal snippet is private' do
|
||||
let(:personal_snippet) { create(:personal_snippet, :private, author: user) }
|
||||
|
||||
context 'when signed in' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'when signed in user is not the author' do
|
||||
let(:other_author) { create(:author) }
|
||||
let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) }
|
||||
|
||||
it 'responds with status 404' do
|
||||
get action, id: other_personal_snippet.to_param
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when signed in user is the author' do
|
||||
before { get action, id: personal_snippet.to_param }
|
||||
|
||||
it 'responds with status 200' do
|
||||
expect(assigns(:snippet)).to eq(personal_snippet)
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'has expected headers' do
|
||||
expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
|
||||
|
||||
if action == :download
|
||||
expect(response.header['Content-Disposition']).to match(/attachment/)
|
||||
elsif action == :raw
|
||||
expect(response.header['Content-Disposition']).to match(/inline/)
|
||||
end
|
||||
end
|
||||
end
|
||||
context 'when signed in' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'when not signed in' do
|
||||
it 'redirects to the sign in page' do
|
||||
get action, id: personal_snippet.to_param
|
||||
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the personal snippet is internal' do
|
||||
let(:personal_snippet) { create(:personal_snippet, :internal, author: user) }
|
||||
|
||||
context 'when signed in' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'responds with status 200' do
|
||||
get action, id: personal_snippet.to_param
|
||||
|
||||
expect(assigns(:snippet)).to eq(personal_snippet)
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not signed in' do
|
||||
it 'redirects to the sign in page' do
|
||||
get action, id: personal_snippet.to_param
|
||||
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the personal snippet is public' do
|
||||
let(:personal_snippet) { create(:personal_snippet, :public, author: user) }
|
||||
|
||||
context 'when signed in' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'responds with status 200' do
|
||||
get action, id: personal_snippet.to_param
|
||||
|
||||
expect(assigns(:snippet)).to eq(personal_snippet)
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
context 'CRLF line ending' do
|
||||
let(:personal_snippet) do
|
||||
create(:personal_snippet, :public, author: user, content: "first line\r\nsecond line\r\nthird line")
|
||||
end
|
||||
|
||||
it 'returns LF line endings by default' do
|
||||
get action, id: personal_snippet.to_param
|
||||
|
||||
expect(response.body).to eq("first line\nsecond line\nthird line")
|
||||
end
|
||||
|
||||
it 'does not convert line endings when parameter present' do
|
||||
get action, id: personal_snippet.to_param, line_ending: :raw
|
||||
|
||||
expect(response.body).to eq("first line\r\nsecond line\r\nthird line")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not signed in' do
|
||||
it 'responds with status 200' do
|
||||
get action, id: personal_snippet.to_param
|
||||
|
||||
expect(assigns(:snippet)).to eq(personal_snippet)
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the personal snippet does not exist' do
|
||||
context 'when signed in' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
context 'when signed in user is not the author' do
|
||||
let(:other_author) { create(:author) }
|
||||
let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) }
|
||||
|
||||
it 'responds with status 404' do
|
||||
get action, id: 'doesntexist'
|
||||
get :raw, id: other_personal_snippet.to_param
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not signed in' do
|
||||
it 'responds with status 404' do
|
||||
get action, id: 'doesntexist'
|
||||
context 'when signed in user is the author' do
|
||||
before { get :raw, id: personal_snippet.to_param }
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
it 'responds with status 200' do
|
||||
expect(assigns(:snippet)).to eq(personal_snippet)
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'has expected headers' do
|
||||
expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
|
||||
|
||||
expect(response.header['Content-Disposition']).to match(/inline/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not signed in' do
|
||||
it 'redirects to the sign in page' do
|
||||
get :raw, id: personal_snippet.to_param
|
||||
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the personal snippet is internal' do
|
||||
let(:personal_snippet) { create(:personal_snippet, :internal, author: user) }
|
||||
|
||||
context 'when signed in' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'responds with status 200' do
|
||||
get :raw, id: personal_snippet.to_param
|
||||
|
||||
expect(assigns(:snippet)).to eq(personal_snippet)
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not signed in' do
|
||||
it 'redirects to the sign in page' do
|
||||
get :raw, id: personal_snippet.to_param
|
||||
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the personal snippet is public' do
|
||||
let(:personal_snippet) { create(:personal_snippet, :public, author: user) }
|
||||
|
||||
context 'when signed in' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'responds with status 200' do
|
||||
get :raw, id: personal_snippet.to_param
|
||||
|
||||
expect(assigns(:snippet)).to eq(personal_snippet)
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
context 'CRLF line ending' do
|
||||
let(:personal_snippet) do
|
||||
create(:personal_snippet, :public, author: user, content: "first line\r\nsecond line\r\nthird line")
|
||||
end
|
||||
|
||||
it 'returns LF line endings by default' do
|
||||
get :raw, id: personal_snippet.to_param
|
||||
|
||||
expect(response.body).to eq("first line\nsecond line\nthird line")
|
||||
end
|
||||
|
||||
it 'does not convert line endings when parameter present' do
|
||||
get :raw, id: personal_snippet.to_param, line_ending: :raw
|
||||
|
||||
expect(response.body).to eq("first line\r\nsecond line\r\nthird line")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not signed in' do
|
||||
it 'responds with status 200' do
|
||||
get :raw, id: personal_snippet.to_param
|
||||
|
||||
expect(assigns(:snippet)).to eq(personal_snippet)
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the personal snippet does not exist' do
|
||||
context 'when signed in' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'responds with status 404' do
|
||||
get :raw, id: 'doesntexist'
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not signed in' do
|
||||
it 'responds with status 404' do
|
||||
get :raw, id: 'doesntexist'
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ include ActionDispatch::TestProcess
|
|||
FactoryGirl.define do
|
||||
factory :note do
|
||||
project factory: :empty_project
|
||||
note "Note"
|
||||
note { generate(:title) }
|
||||
author
|
||||
on_issue
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
FactoryGirl.define do
|
||||
factory :project_hook do
|
||||
url { generate(:url) }
|
||||
enable_ssl_verification false
|
||||
|
||||
trait :token do
|
||||
token { SecureRandom.hex(10) }
|
||||
|
@ -11,6 +12,7 @@ FactoryGirl.define do
|
|||
merge_requests_events true
|
||||
tag_push_events true
|
||||
issues_events true
|
||||
confidential_issues_events true
|
||||
note_events true
|
||||
build_events true
|
||||
pipeline_events true
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe "Admin::Hooks", feature: true do
|
||||
describe 'Admin::Hooks', feature: true do
|
||||
before do
|
||||
@project = create(:project)
|
||||
login_as :admin
|
||||
|
@ -8,24 +8,24 @@ describe "Admin::Hooks", feature: true do
|
|||
@system_hook = create(:system_hook)
|
||||
end
|
||||
|
||||
describe "GET /admin/hooks" do
|
||||
it "is ok" do
|
||||
describe 'GET /admin/hooks' do
|
||||
it 'is ok' do
|
||||
visit admin_root_path
|
||||
|
||||
page.within ".layout-nav" do
|
||||
click_on "Hooks"
|
||||
page.within '.layout-nav' do
|
||||
click_on 'Hooks'
|
||||
end
|
||||
|
||||
expect(current_path).to eq(admin_hooks_path)
|
||||
end
|
||||
|
||||
it "has hooks list" do
|
||||
it 'has hooks list' do
|
||||
visit admin_hooks_path
|
||||
expect(page).to have_content(@system_hook.url)
|
||||
end
|
||||
end
|
||||
|
||||
describe "New Hook" do
|
||||
describe 'New Hook' do
|
||||
let(:url) { generate(:url) }
|
||||
|
||||
it 'adds new hook' do
|
||||
|
@ -40,11 +40,36 @@ describe "Admin::Hooks", feature: true do
|
|||
end
|
||||
end
|
||||
|
||||
describe "Test" do
|
||||
describe 'Update existing hook' do
|
||||
let(:new_url) { generate(:url) }
|
||||
|
||||
it 'updates existing hook' do
|
||||
visit admin_hooks_path
|
||||
|
||||
click_link 'Edit'
|
||||
fill_in 'hook_url', with: new_url
|
||||
check 'Enable SSL verification'
|
||||
click_button 'Save changes'
|
||||
|
||||
expect(page).to have_content 'SSL Verification: enabled'
|
||||
expect(current_path).to eq(admin_hooks_path)
|
||||
expect(page).to have_content(new_url)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Remove existing hook' do
|
||||
it 'remove existing hook' do
|
||||
visit admin_hooks_path
|
||||
|
||||
expect { click_link 'Remove' }.to change(SystemHook, :count).by(-1)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Test' do
|
||||
before do
|
||||
WebMock.stub_request(:post, @system_hook.url)
|
||||
visit admin_hooks_path
|
||||
click_link "Test hook"
|
||||
click_link 'Test hook'
|
||||
end
|
||||
|
||||
it { expect(current_path).to eq(admin_hooks_path) }
|
||||
|
|
|
@ -231,7 +231,7 @@ feature 'File blob', :js, feature: true do
|
|||
branch_name: 'master',
|
||||
commit_message: "Add PDF",
|
||||
file_path: 'files/test.pdf',
|
||||
file_content: File.read(Rails.root.join('spec/javascripts/blob/pdf/test.pdf'))
|
||||
file_content: project.repository.blob_at('add-pdf-file', 'files/pdf/test.pdf').data
|
||||
).execute
|
||||
|
||||
visit_blob('files/test.pdf')
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'Integration settings', feature: true do
|
||||
let(:project) { create(:empty_project) }
|
||||
let(:user) { create(:user) }
|
||||
let(:role) { :developer }
|
||||
let(:integrations_path) { namespace_project_settings_integrations_path(project.namespace, project) }
|
||||
|
||||
background do
|
||||
login_as(user)
|
||||
project.team << [user, role]
|
||||
end
|
||||
|
||||
context 'for developer' do
|
||||
given(:role) { :developer }
|
||||
|
||||
scenario 'to be disallowed to view' do
|
||||
visit integrations_path
|
||||
|
||||
expect(page.status_code).to eq(404)
|
||||
end
|
||||
end
|
||||
|
||||
context 'for master' do
|
||||
given(:role) { :master }
|
||||
|
||||
context 'Webhooks' do
|
||||
let(:hook) { create(:project_hook, :all_events_enabled, enable_ssl_verification: true, project: project) }
|
||||
let(:url) { generate(:url) }
|
||||
|
||||
scenario 'show list of webhooks' do
|
||||
hook
|
||||
|
||||
visit integrations_path
|
||||
|
||||
expect(page.status_code).to eq(200)
|
||||
expect(page).to have_content(hook.url)
|
||||
expect(page).to have_content('SSL Verification: enabled')
|
||||
expect(page).to have_content('Push Events')
|
||||
expect(page).to have_content('Tag Push Events')
|
||||
expect(page).to have_content('Issues Events')
|
||||
expect(page).to have_content('Confidential Issues Events')
|
||||
expect(page).to have_content('Note Events')
|
||||
expect(page).to have_content('Merge Requests Events')
|
||||
expect(page).to have_content('Pipeline Events')
|
||||
expect(page).to have_content('Wiki Page Events')
|
||||
end
|
||||
|
||||
scenario 'create webhook' do
|
||||
visit integrations_path
|
||||
|
||||
fill_in 'hook_url', with: url
|
||||
check 'Tag push events'
|
||||
check 'Enable SSL verification'
|
||||
|
||||
click_button 'Add webhook'
|
||||
|
||||
expect(page).to have_content(url)
|
||||
expect(page).to have_content('SSL Verification: enabled')
|
||||
expect(page).to have_content('Push Events')
|
||||
expect(page).to have_content('Tag Push Events')
|
||||
end
|
||||
|
||||
scenario 'edit existing webhook' do
|
||||
hook
|
||||
visit integrations_path
|
||||
|
||||
click_link 'Edit'
|
||||
fill_in 'hook_url', with: url
|
||||
check 'Enable SSL verification'
|
||||
click_button 'Save changes'
|
||||
|
||||
expect(page).to have_content 'SSL Verification: enabled'
|
||||
expect(page).to have_content(url)
|
||||
end
|
||||
|
||||
scenario 'test existing webhook' do
|
||||
WebMock.stub_request(:post, hook.url)
|
||||
visit integrations_path
|
||||
|
||||
click_link 'Test'
|
||||
|
||||
expect(current_path).to eq(integrations_path)
|
||||
end
|
||||
|
||||
scenario 'remove existing webhook' do
|
||||
hook
|
||||
visit integrations_path
|
||||
|
||||
expect { click_link 'Remove' }.to change(ProjectHook, :count).by(-1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -30,6 +30,12 @@ feature 'Project snippet', :js, feature: true do
|
|||
|
||||
# shows an enabled copy button
|
||||
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
|
||||
|
||||
# shows a raw button
|
||||
expect(page).to have_link('Open raw')
|
||||
|
||||
# shows a download button
|
||||
expect(page).to have_link('Download')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -59,6 +65,12 @@ feature 'Project snippet', :js, feature: true do
|
|||
|
||||
# shows a disabled copy button
|
||||
expect(page).to have_selector('.js-copy-blob-source-btn.disabled')
|
||||
|
||||
# shows a raw button
|
||||
expect(page).to have_link('Open raw')
|
||||
|
||||
# shows a download button
|
||||
expect(page).to have_link('Download')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe 'Comments on personal snippets', feature: true do
|
||||
let!(:user) { create(:user) }
|
||||
let!(:snippet) { create(:personal_snippet, :public) }
|
||||
let!(:snippet_notes) do
|
||||
[
|
||||
create(:note_on_personal_snippet, noteable: snippet, author: user),
|
||||
create(:note_on_personal_snippet, noteable: snippet)
|
||||
]
|
||||
end
|
||||
let!(:other_note) { create(:note_on_personal_snippet) }
|
||||
|
||||
before do
|
||||
login_as user
|
||||
visit snippet_path(snippet)
|
||||
end
|
||||
|
||||
subject { page }
|
||||
|
||||
context 'viewing the snippet detail page' do
|
||||
it 'contains notes for a snippet with correct action icons' do
|
||||
expect(page).to have_selector('#notes-list li', count: 2)
|
||||
|
||||
# comment authored by current user
|
||||
page.within("#notes-list li#note_#{snippet_notes[0].id}") do
|
||||
expect(page).to have_content(snippet_notes[0].note)
|
||||
expect(page).to have_selector('.js-note-delete')
|
||||
expect(page).to have_selector('.note-emoji-button')
|
||||
end
|
||||
|
||||
page.within("#notes-list li#note_#{snippet_notes[1].id}") do
|
||||
expect(page).to have_content(snippet_notes[1].note)
|
||||
expect(page).not_to have_selector('.js-note-delete')
|
||||
expect(page).to have_selector('.note-emoji-button')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -24,6 +24,12 @@ feature 'Snippet', :js, feature: true do
|
|||
|
||||
# shows an enabled copy button
|
||||
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
|
||||
|
||||
# shows a raw button
|
||||
expect(page).to have_link('Open raw')
|
||||
|
||||
# shows a download button
|
||||
expect(page).to have_link('Download')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -53,6 +59,12 @@ feature 'Snippet', :js, feature: true do
|
|||
|
||||
# shows a disabled copy button
|
||||
expect(page).to have_selector('.js-copy-blob-source-btn.disabled')
|
||||
|
||||
# shows a raw button
|
||||
expect(page).to have_link('Open raw')
|
||||
|
||||
# shows a download button
|
||||
expect(page).to have_link('Download')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -110,6 +110,15 @@ describe NotesFinder do
|
|||
expect(notes.count).to eq(1)
|
||||
end
|
||||
|
||||
it 'finds notes on personal snippets' do
|
||||
note = create(:note_on_personal_snippet)
|
||||
params = { target_type: 'personal_snippet', target_id: note.noteable_id }
|
||||
|
||||
notes = described_class.new(project, user, params).execute
|
||||
|
||||
expect(notes.count).to eq(1)
|
||||
end
|
||||
|
||||
it 'raises an exception for an invalid target_type' do
|
||||
params[:target_type] = 'invalid'
|
||||
expect { described_class.new(project, user, params).execute }.to raise_error('invalid target_type')
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe AwardEmojiHelper do
|
||||
describe '.toggle_award_url' do
|
||||
context 'note on personal snippet' do
|
||||
let(:note) { create(:note_on_personal_snippet) }
|
||||
|
||||
it 'returns correct url' do
|
||||
expected_url = "/snippets/#{note.noteable.id}/notes/#{note.id}/toggle_award_emoji"
|
||||
|
||||
expect(helper.toggle_award_url(note)).to eq(expected_url)
|
||||
end
|
||||
end
|
||||
|
||||
context 'note on project item' do
|
||||
let(:note) { create(:note_on_project_snippet) }
|
||||
|
||||
it 'returns correct url' do
|
||||
@project = note.noteable.project
|
||||
|
||||
expected_url = "/#{@project.namespace.path}/#{@project.path}/notes/#{note.id}/toggle_award_emoji"
|
||||
|
||||
expect(helper.toggle_award_url(note)).to eq(expected_url)
|
||||
end
|
||||
end
|
||||
|
||||
context 'personal snippet' do
|
||||
let(:snippet) { create(:personal_snippet) }
|
||||
|
||||
it 'returns correct url' do
|
||||
expected_url = "/snippets/#{snippet.id}/toggle_award_emoji"
|
||||
|
||||
expect(helper.toggle_award_url(snippet)).to eq(expected_url)
|
||||
end
|
||||
end
|
||||
|
||||
context 'merge request' do
|
||||
let(:merge_request) { create(:merge_request) }
|
||||
|
||||
it 'returns correct url' do
|
||||
@project = merge_request.project
|
||||
|
||||
expected_url = "/#{@project.namespace.path}/#{@project.path}/merge_requests/#{merge_request.id}/toggle_award_emoji"
|
||||
|
||||
expect(helper.toggle_award_url(merge_request)).to eq(expected_url)
|
||||
end
|
||||
end
|
||||
|
||||
context 'issue' do
|
||||
let(:issue) { create(:issue) }
|
||||
|
||||
it 'returns correct url' do
|
||||
@project = issue.project
|
||||
|
||||
expected_url = "/#{@project.namespace.path}/#{@project.path}/issues/#{issue.id}/toggle_award_emoji"
|
||||
|
||||
expect(helper.toggle_award_url(issue)).to eq(expected_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -64,7 +64,7 @@ describe MergeRequestsHelper do
|
|||
|
||||
it do
|
||||
@project = project
|
||||
|
||||
|
||||
is_expected.to eq("#1, #2, and #{other_project.namespace.path}/#{other_project.path}#3")
|
||||
end
|
||||
end
|
||||
|
@ -149,6 +149,50 @@ describe MergeRequestsHelper do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#target_projects' do
|
||||
let(:project) { create(:empty_project) }
|
||||
let(:fork_project) { create(:empty_project, forked_from_project: project) }
|
||||
|
||||
context 'when target project has enabled merge requests' do
|
||||
it 'returns the forked_from project' do
|
||||
expect(target_projects(fork_project)).to contain_exactly(project, fork_project)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when target project has disabled merge requests' do
|
||||
it 'returns the forked project' do
|
||||
project.project_feature.update(merge_requests_access_level: 0)
|
||||
|
||||
expect(target_projects(fork_project)).to contain_exactly(fork_project)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#new_mr_path_from_push_event' do
|
||||
subject(:url_params) { URI.decode_www_form(new_mr_path_from_push_event(event)).to_h }
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:empty_project, creator: user) }
|
||||
let(:fork_project) { create(:project, forked_from_project: project, creator: user) }
|
||||
let(:event) do
|
||||
push_data = Gitlab::DataBuilder::Push.build_sample(fork_project, user)
|
||||
create(:event, :pushed, project: fork_project, target: fork_project, author: user, data: push_data)
|
||||
end
|
||||
|
||||
context 'when target project has enabled merge requests' do
|
||||
it 'returns link to create merge request on source project' do
|
||||
expect(url_params['merge_request[target_project_id]'].to_i).to eq(project.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when target project has disabled merge requests' do
|
||||
it 'returns link to create merge request on forked project' do
|
||||
project.project_feature.update(merge_requests_access_level: 0)
|
||||
|
||||
expect(url_params['merge_request[target_project_id]'].to_i).to eq(fork_project.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#mr_issues_mentioned_but_not_closing' do
|
||||
let(:user_1) { create(:user) }
|
||||
let(:user_2) { create(:user) }
|
||||
|
|