Commit 719b73b8 authored by James Lopez's avatar James Lopez

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into update-ruby-2.2.4

parents 3d81f1da 07ee8393
...@@ -9,7 +9,7 @@ variables: ...@@ -9,7 +9,7 @@ variables:
MYSQL_ALLOW_EMPTY_PASSWORD: "1" MYSQL_ALLOW_EMPTY_PASSWORD: "1"
before_script: before_script:
- ./scripts/prepare_build.sh - source ./scripts/prepare_build.sh
- ruby -v - ruby -v
- which ruby - which ruby
- gem install bundler --no-ri --no-rdoc - gem install bundler --no-ri --no-rdoc
......
...@@ -2,8 +2,12 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -2,8 +2,12 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.5.0 (unreleased) v 8.5.0 (unreleased)
- Add "visibility" flag to GET /projects api endpoint - Add "visibility" flag to GET /projects api endpoint
- Ignore binary files in code search to prevent Error 500 (Stan Hu)
- Upgrade gitlab_git to 7.2.23 to fix commit message mentions in first branch push - Upgrade gitlab_git to 7.2.23 to fix commit message mentions in first branch push
- New UI for pagination - New UI for pagination
- Fix diff comments loaded by AJAX to load comment with diff in discussion tab
- Whitelist raw "abbr" elements when parsing Markdown (Benedict Etzel)
- Don't vendor minified JS
v 8.4.0 v 8.4.0
- Allow LDAP users to change their email if it was not set by the LDAP server - Allow LDAP users to change their email if it was not set by the LDAP server
......
...@@ -212,6 +212,9 @@ gem 'select2-rails', '~> 3.5.9' ...@@ -212,6 +212,9 @@ gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1' gem 'virtus', '~> 1.0.1'
gem 'net-ssh', '~> 3.0.1' gem 'net-ssh', '~> 3.0.1'
# Sentry integration
gem 'sentry-raven'
# Metrics # Metrics
group :metrics do group :metrics do
gem 'allocations', '~> 1.0', require: false, platform: :mri gem 'allocations', '~> 1.0', require: false, platform: :mri
...@@ -293,9 +296,6 @@ end ...@@ -293,9 +296,6 @@ end
group :production do group :production do
gem "gitlab_meta", '7.0' gem "gitlab_meta", '7.0'
# Sentry integration
gem 'sentry-raven'
end end
gem "newrelic_rpm", '~> 3.9.4.245' gem "newrelic_rpm", '~> 3.9.4.245'
......
...@@ -21,9 +21,9 @@ ...@@ -21,9 +21,9 @@
#= require bootstrap #= require bootstrap
#= require select2 #= require select2
#= require raphael #= require raphael
#= require g.raphael-min #= require g.raphael
#= require g.bar-min #= require g.bar
#= require chart-lib.min #= require Chart
#= require branch-graph #= require branch-graph
#= require ace/ace #= require ace/ace
#= require ace/ext-searchbox #= require ace/ext-searchbox
...@@ -38,9 +38,9 @@ ...@@ -38,9 +38,9 @@
#= require shortcuts_dashboard_navigation #= require shortcuts_dashboard_navigation
#= require shortcuts_issuable #= require shortcuts_issuable
#= require shortcuts_network #= require shortcuts_network
#= require jquery.nicescroll.min #= require jquery.nicescroll
#= require_tree . #= require_tree .
#= require fuzzaldrin-plus.min #= require fuzzaldrin-plus
window.slugify = (text) -> window.slugify = (text) ->
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase() text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
......
...@@ -15,6 +15,8 @@ class @Notes ...@@ -15,6 +15,8 @@ class @Notes
@last_fetched_at = last_fetched_at @last_fetched_at = last_fetched_at
@view = view @view = view
@noteable_url = document.URL @noteable_url = document.URL
@notesCountBadge ||= $(".issuable-details").find(".notes-tab .badge")
@initRefresh() @initRefresh()
@setupMainTargetNoteForm() @setupMainTargetNoteForm()
@cleanBinding() @cleanBinding()
...@@ -89,7 +91,7 @@ class @Notes ...@@ -89,7 +91,7 @@ class @Notes
, 15000 , 15000
refresh: -> refresh: ->
unless document.hidden or (@noteable_url != document.URL) if not document.hidden and document.URL.indexOf(@noteable_url) is 0
@getContent() @getContent()
getContent: -> getContent: ->
...@@ -101,6 +103,9 @@ class @Notes ...@@ -101,6 +103,9 @@ class @Notes
notes = data.notes notes = data.notes
@last_fetched_at = data.last_fetched_at @last_fetched_at = data.last_fetched_at
$.each notes, (i, note) => $.each notes, (i, note) =>
if note.discussion_with_diff_html?
@renderDiscussionNote(note)
else
@renderNote(note) @renderNote(note)
...@@ -116,18 +121,21 @@ class @Notes ...@@ -116,18 +121,21 @@ class @Notes
flash.pinTo('.header-content') flash.pinTo('.header-content')
return return
if note.award
awards_handler.addAwardToEmojiBar(note.note)
awards_handler.scrollToAwards()
# render note if it not present in loaded list # render note if it not present in loaded list
# or skip if rendered # or skip if rendered
if @isNewNote(note) && !note.award else if @isNewNote(note)
@note_ids.push(note.id) @note_ids.push(note.id)
$('ul.main-notes-list').
append(note.html). $('ul.main-notes-list')
syntaxHighlight() .append(note.html)
.syntaxHighlight()
@initTaskList() @initTaskList()
@updateNotesCount(1)
if note.award
awards_handler.addAwardToEmojiBar(note.note)
awards_handler.scrollToAwards()
### ###
Check if note does not exists on page Check if note does not exists on page
...@@ -144,34 +152,39 @@ class @Notes ...@@ -144,34 +152,39 @@ class @Notes
Note: for rendering inline notes use renderDiscussionNote Note: for rendering inline notes use renderDiscussionNote
### ###
renderDiscussionNote: (note) -> renderDiscussionNote: (note) ->
return unless @isNewNote(note)
@note_ids.push(note.id) @note_ids.push(note.id)
form = $("form[rel='" + note.discussion_id + "']") form = $("#new-discussion-note-form-#{note.discussion_id}")
row = form.closest("tr") row = form.closest("tr")
note_html = $(note.html) note_html = $(note.html)
note_html.syntaxHighlight() note_html.syntaxHighlight()
# is this the first note of discussion? # is this the first note of discussion?
if row.is(".js-temp-notes-holder") discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']")
if discussionContainer.length is 0
# insert the note and the reply button after the temp row # insert the note and the reply button after the temp row
row.after note.discussion_html row.after note.discussion_html
# remove the note (will be added again below) # remove the note (will be added again below)
row.next().find(".note").remove() row.next().find(".note").remove()
# Before that, the container didn't exist
discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']")
# Add note to 'Changes' page discussions # Add note to 'Changes' page discussions
$(".notes[rel='" + note.discussion_id + "']").append note_html discussionContainer.append note_html
# Init discussion on 'Discussion' page if it is merge request page # Init discussion on 'Discussion' page if it is merge request page
if $('body').attr('data-page').indexOf('projects:merge_request') == 0 if $('body').attr('data-page').indexOf('projects:merge_request') is 0
discussion_html = $(note.discussion_with_diff_html) $('ul.main-notes-list')
discussion_html.syntaxHighlight() .append(note.discussion_with_diff_html)
$('ul.main-notes-list').append(discussion_html) .syntaxHighlight()
else else
# append new note to all matching discussions # append new note to all matching discussions
$(".notes[rel='" + note.discussion_id + "']").append note_html discussionContainer.append note_html
# cleanup after successfully creating a diff/discussion note @updateNotesCount(1)
@removeDiscussionNoteForm(form)
### ###
Called in response the main target form has been successfully submitted. Called in response the main target form has been successfully submitted.
...@@ -278,6 +291,9 @@ class @Notes ...@@ -278,6 +291,9 @@ class @Notes
addDiscussionNote: (xhr, note, status) => addDiscussionNote: (xhr, note, status) =>
@renderDiscussionNote(note) @renderDiscussionNote(note)
# cleanup after successfully creating a diff/discussion note
@removeDiscussionNoteForm($("#new-discussion-note-form-#{note.discussion_id}"))
### ###
Called in response to the edit note form being submitted Called in response to the edit note form being submitted
...@@ -349,30 +365,32 @@ class @Notes ...@@ -349,30 +365,32 @@ class @Notes
Removes the actual note from view. Removes the actual note from view.
Removes the whole discussion if the last note is being removed. Removes the whole discussion if the last note is being removed.
### ###
removeNote: -> removeNote: (e) =>
note = $(this).closest(".note") noteId = $(e.currentTarget)
note_id = note.attr('id') .closest(".note")
.attr("id")
$('.note[id="' + note_id + '"]').each -> # A same note appears in the "Discussion" and in the "Changes" tab, we have
note = $(this) # to remove all. Using $(".note[id='noteId']") ensure we get all the notes,
# where $("#noteId") would return only one.
$(".note[id='#{noteId}']").each (i, el) =>
note = $(el)
notes = note.closest(".notes") notes = note.closest(".notes")
count = notes.closest(".issuable-details").find(".notes-tab .badge")
# check if this is the last note for this line # check if this is the last note for this line
if notes.find(".note").length is 1 if notes.find(".note").length is 1
# for discussions # "Discussions" tab
notes.closest(".discussion").remove() notes.closest(".timeline-entry").remove()
# for diff lines # "Changes" tab / commit view
notes.closest("tr").remove() notes.closest("tr").remove()
# update notes count
oldNum = parseInt(count.text())
count.text(oldNum - 1)
note.remove() note.remove()
# Decrement the "Discussions" counter only once
@updateNotesCount(-1)
### ###
Called in response to clicking the delete attachment link Called in response to clicking the delete attachment link
...@@ -412,7 +430,7 @@ class @Notes ...@@ -412,7 +430,7 @@ class @Notes
### ###
setupDiscussionNoteForm: (dataHolder, form) => setupDiscussionNoteForm: (dataHolder, form) =>
# setup note target # setup note target
form.attr "rel", dataHolder.data("discussionId") form.attr 'id', "new-discussion-note-form-#{dataHolder.data("discussionId")}"
form.find("#line_type").val dataHolder.data("lineType") form.find("#line_type").val dataHolder.data("lineType")
form.find("#note_commit_id").val dataHolder.data("commitId") form.find("#note_commit_id").val dataHolder.data("commitId")
form.find("#note_line_code").val dataHolder.data("lineCode") form.find("#note_line_code").val dataHolder.data("lineCode")
...@@ -542,3 +560,6 @@ class @Notes ...@@ -542,3 +560,6 @@ class @Notes
updateTaskList: -> updateTaskList: ->
$('form', this).submit() $('form', this).submit()
updateNotesCount: (updateCount) ->
@notesCountBadge.text(parseInt(@notesCountBadge.text()) + updateCount)
...@@ -146,6 +146,10 @@ ...@@ -146,6 +146,10 @@
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
&.oneline-block { &.oneline-block {
line-height: 42px; line-height: 36px;
}
> .controls {
float: right;
} }
} }
...@@ -2,7 +2,13 @@ ...@@ -2,7 +2,13 @@
margin-bottom: $gl-padding; margin-bottom: $gl-padding;
.panel-heading { .panel-heading {
padding: 7px $gl-padding; padding: $gl-vert-padding $gl-padding;
line-height: 36px;
.controls {
margin-top: -2px;
float: right;
}
} }
.panel-body { .panel-body {
...@@ -14,7 +20,3 @@ ...@@ -14,7 +20,3 @@
} }
} }
} }
.container-blank .panel .panel-heading {
line-height: 42px !important;
}
...@@ -114,22 +114,9 @@ ...@@ -114,22 +114,9 @@
* *
*/ */
.container-blank .panel .panel-heading {
font-size: 17px;
line-height: 38px;
}
.panel { .panel {
box-shadow: none; box-shadow: none;
.panel-heading {
.panel-head-actions {
position: relative;
top: -5px;
float: right;
}
}
.panel-body { .panel-body {
form, pre { form, pre {
margin: 0; margin: 0;
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
} }
.diff-line-num.old, .line_content.old { .diff-line-num.old, .line_content.old {
@include diff_background(rgba(220, 50, 47, 0.2), rgba(220, 50, 47, 0.3), #808080); @include diff_background(rgba(220, 50, 47, 0.3), rgba(220, 50, 47, 0.3), #808080);
} }
.line_content.match { .line_content.match {
......
...@@ -11,11 +11,9 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -11,11 +11,9 @@ class Projects::NotesController < Projects::ApplicationController
notes_json = { notes: [], last_fetched_at: current_fetched_at } notes_json = { notes: [], last_fetched_at: current_fetched_at }
@notes.each do |note| @notes.each do |note|
notes_json[:notes] << { next if note.cross_reference_not_visible_for?(current_user)
id: note.id,
html: note_to_html(note), notes_json[:notes] << note_json(note)
valid: note.valid?
}
end end
render json: notes_json render json: notes_json
...@@ -25,7 +23,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -25,7 +23,7 @@ class Projects::NotesController < Projects::ApplicationController
@note = Notes::CreateService.new(project, current_user, note_params).execute @note = Notes::CreateService.new(project, current_user, note_params).execute
respond_to do |format| respond_to do |format|
format.json { render_note_json(@note) } format.json { render json: note_json(@note) }
format.html { redirect_back_or_default } format.html { redirect_back_or_default }
end end
end end
...@@ -34,7 +32,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -34,7 +32,7 @@ class Projects::NotesController < Projects::ApplicationController
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note) @note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
respond_to do |format| respond_to do |format|
format.json { render_note_json(@note) } format.json { render json: note_json(@note) }
format.html { redirect_back_or_default } format.html { redirect_back_or_default }
end end
end end
...@@ -99,6 +97,8 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -99,6 +97,8 @@ class Projects::NotesController < Projects::ApplicationController
end end
def note_to_discussion_html(note) def note_to_discussion_html(note)
return unless note.for_diff_line?
if params[:view] == 'parallel' if params[:view] == 'parallel'
template = "projects/notes/_diff_notes_with_reply_parallel" template = "projects/notes/_diff_notes_with_reply_parallel"
locals = locals =
...@@ -131,9 +131,9 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -131,9 +131,9 @@ class Projects::NotesController < Projects::ApplicationController
) )
end end
def render_note_json(note) def note_json(note)
if note.valid? if note.valid?
render json: { {
valid: true, valid: true,
id: note.id, id: note.id,
discussion_id: note.discussion_id, discussion_id: note.discussion_id,
...@@ -144,7 +144,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -144,7 +144,7 @@ class Projects::NotesController < Projects::ApplicationController
discussion_with_diff_html: note_to_discussion_with_diff_html(note) discussion_with_diff_html: note_to_discussion_with_diff_html(note)
} }
else else
render json: { {
valid: false, valid: false,
award: note.is_award, award: note.is_award,
errors: note.errors errors: note.errors
...@@ -163,8 +163,6 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -163,8 +163,6 @@ class Projects::NotesController < Projects::ApplicationController
) )
end end
private
def find_current_user_notes def find_current_user_notes
@notes = NotesFinder.new.execute(project, current_user, params) @notes = NotesFinder.new.execute(project, current_user, params)
end end
......
...@@ -17,4 +17,79 @@ module SnippetsHelper ...@@ -17,4 +17,79 @@ module SnippetsHelper
snippet_path(snippet) snippet_path(snippet)
end end
end end
# Get an array of line numbers surrounding a matching
# line, bounded by min/max.
#
# @returns Array of line numbers
def bounded_line_numbers(line, min, max, surrounding_lines)
lower = line - surrounding_lines > min ? line - surrounding_lines : min
upper = line + surrounding_lines < max ? line + surrounding_lines : max
(lower..upper).to_a
end
# Returns a sorted set of lines to be included in a snippet preview.
# This ensures matching adjacent lines do not display duplicated
# surrounding code.
#
# @returns Array, unique and sorted.
def matching_lines(lined_content, surrounding_lines)
used_lines = []
lined_content.each_with_index do |line, line_number|
used_lines.concat bounded_line_numbers(
line_number,
0,
lined_content.size,
surrounding_lines
) if line.include?(query)
end
used_lines.uniq.sort
end
# 'Chunkify' entire snippet. Splits the snippet data into matching lines +
# surrounding_lines() worth of unmatching lines.
#
# @returns a hash with {snippet_object, snippet_chunks:{data,start_line}}
def chunk_snippet(snippet, surrounding_lines = 3)
lined_content = snippet.content.split("\n")
used_lines = matching_lines(lined_content, surrounding_lines)
snippet_chunk = []
snippet_chunks = []
snippet_start_line = 0
last_line = -1
# Go through each used line, and add consecutive lines as a single chunk
# to the snippet chunk array.
used_lines.each do |line_number|
if last_line < 0
# Start a new chunk.
snippet_start_line = line_number
snippet_chunk << lined_content[line_number]
elsif last_line == line_number - 1
# Consecutive line, continue chunk.
snippet_chunk << lined_content[line_number]
else
# Non-consecutive line, add chunk to chunk array.
snippet_chunks << {
data: snippet_chunk.join("\n"),
start_line: snippet_start_line + 1
}
# Start a new chunk.
snippet_chunk = [lined_content[line_number]]
snippet_start_line = line_number
end
last_line = line_number
end
# Add final chunk to chunk array
snippet_chunks << {
data: snippet_chunk.join("\n"),
start_line: snippet_start_line + 1
}
# Return snippet with chunk array
{ snippet_object: snippet, snippet_chunks: snippet_chunks }
end
end end
...@@ -38,6 +38,7 @@ class Issue < ActiveRecord::Base ...@@ -38,6 +38,7 @@ class Issue < ActiveRecord::Base
scope :cared, ->(user) { where(assignee_id: user) } scope :cared, ->(user) { where(assignee_id: user) }
scope :open_for, ->(user) { opened.assigned_to(user) } scope :open_for, ->(user) { opened.assigned_to(user) }
scope :in_projects, ->(project_ids) { where(project_id: project_ids) }
state_machine :state, initial: :opened do state_machine :state, initial: :opened do
event :close do event :close do
......
...@@ -904,4 +904,8 @@ class Project < ActiveRecord::Base ...@@ -904,4 +904,8 @@ class Project < ActiveRecord::Base
def runners_token def runners_token
ensure_runners_token! ensure_runners_token!
end end
def wiki
@wiki ||= ProjectWiki.new(self, self.owner)
end
end end
...@@ -12,6 +12,7 @@ class ProjectWiki ...@@ -12,6 +12,7 @@ class ProjectWiki
# Returns a string describing what went wrong after # Returns a string describing what went wrong after
# an operation fails. # an operation fails.
attr_reader :error_message attr_reader :error_message
attr_reader :project
def initialize(project, user = nil) def initialize(project, user = nil)
@project = project @project = project
......
...@@ -598,7 +598,7 @@ class Repository ...@@ -598,7 +598,7 @@ class Repository
def search_files(query, ref) def search_files(query, ref)
offset = 2 offset = 2
args = %W(#{Gitlab.config.git.bin_path} grep -i -n --before-context #{offset} --after-context #{offset} -e #{query} #{ref || root_ref}) args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -e #{query} #{ref || root_ref})
Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/) Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
end end
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
Public deploy keys (#{@deploy_keys.count}) Public deploy keys (#{@deploy_keys.count})
.panel-head-actions .controls
= link_to 'New Deploy Key', new_admin_deploy_key_path, class: "btn btn-new btn-sm" = link_to 'New Deploy Key', new_admin_deploy_key_path, class: "btn btn-new btn-sm"
- if @deploy_keys.any? - if @deploy_keys.any?
.table-holder .table-holder
......
- page_title "Projects" - page_title "Projects"
= render 'shared/show_aside' = render 'shared/show_aside'
.row .row.prepend-top-default
%aside.col-md-3 %aside.col-md-3
.admin-filter .admin-filter
= form_tag admin_namespaces_projects_path, method: :get, class: '' do = form_tag admin_namespaces_projects_path, method: :get, class: '' do
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
Projects (#{@projects.total_count}) Projects (#{@projects.total_count})
.panel-head-actions .controls
.dropdown.inline .dropdown.inline
%button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'} %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
%span.light sort: %span.light sort:
......
- header_title group_title(@group, "Settings", edit_group_path(@group)) - header_title group_title(@group, "Settings", edit_group_path(@group))
- @blank_container = true
.panel.panel-default.prepend-top-default .panel.panel-default.prepend-top-default
.panel-heading .panel-heading
......
- page_title "Members" - page_title "Members"
- header_title group_title(@group, "Members", group_group_members_path(@group)) - header_title group_title(@group, "Members", group_group_members_path(@group))
- @blank_container = true
.group-members-page.prepend-top-default .group-members-page.prepend-top-default
- if current_user && current_user.can?(:admin_group_member, @group) - if current_user && current_user.can?(:admin_group_member, @group)
...@@ -20,7 +19,7 @@ ...@@ -20,7 +19,7 @@
group members group members
%small %small
(#{@members.total_count}) (#{@members.total_count})
.pull-right .controls
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
.form-group .form-group
= search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false } = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false }
......
...@@ -6,9 +6,9 @@ ...@@ -6,9 +6,9 @@
%strong= @group.name %strong= @group.name
projects: projects:
- if can? current_user, :admin_group, @group - if can? current_user, :admin_group, @group
.panel-head-actions .controls
= link_to new_project_path(namespace_id: @group.id), class: "btn btn-sm btn-success" do = link_to new_project_path(namespace_id: @group.id), class: "btn btn-sm btn-success" do
%i.fa.fa-plus = icon('plus')
New Project New Project
%ul.well-list %ul.well-list
- @projects.each do |project| - @projects.each do |project|
......
- page_title "Account" - page_title "Account"
- header_title page_title, profile_account_path - header_title page_title, profile_account_path
- @blank_container = true
- if current_user.ldap_user? - if current_user.ldap_user?
.alert.alert-info .alert.alert-info
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
Edit Edit
.issue-details.issuable-details .issue-details.issuable-details
.detail-page-description.gray-content-block.second-block .detail-page-description.content-block
%h2.title %h2.title
= markdown escape_once(@issue.title), pipeline: :single_line = markdown escape_once(@issue.title), pipeline: :single_line
%div %div
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
.merge-requests .merge-requests
= render 'merge_requests' = render 'merge_requests'
.gray-content-block.second-block.oneline-block .content-block
= render 'votes/votes_block', votable: @issue = render 'votes/votes_block', votable: @issue
.row .row
......
...@@ -66,7 +66,7 @@ ...@@ -66,7 +66,7 @@
.tab-content .tab-content
#notes.notes.tab-pane.voting_notes #notes.notes.tab-pane.voting_notes
.gray-content-block.second-block.oneline-block .content-block.oneline-block
= render 'votes/votes_block', votable: @merge_request = render 'votes/votes_block', votable: @merge_request
.row .row
......
.gray-content-block.middle-block.oneline-block .content-block.oneline-block
= icon("sort-amount-desc") = icon("sort-amount-desc")
Most recent commits displayed first Most recent commits displayed first
......
...@@ -45,6 +45,10 @@ ...@@ -45,6 +45,10 @@
- unless @merge_request.can_be_merged_by?(current_user) - unless @merge_request.can_be_merged_by?(current_user)
%p %p
Note that pushing to GitLab requires write access to this repository. Note that pushing to GitLab requires write access to this repository.
%p
%strong Tip:
You can also checkout merge requests locally by
%a{href: 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/workflow/merge_requests.md#checkout-merge-requests-locally', target: '_blank'} following these guidelines
:javascript :javascript
$(function(){ $(function(){
......
.detail-page-description.gray-content-block.second-block .detail-page-description.content-block
%h2.title %h2.title
= markdown escape_once(@merge_request.title), pipeline: :single_line = markdown escape_once(@merge_request.title), pipeline: :single_line
......
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
= icon('pencil-square-o') = icon('pencil-square-o')
Edit Edit
.detail-page-description.gray-content-block.second-block .detail-page-description.content-block
%h2.title %h2.title
= markdown escape_once(@milestone.title), pipeline: :single_line = markdown escape_once(@milestone.title), pipeline: :single_line
%div %div
...@@ -73,8 +73,8 @@ ...@@ -73,8 +73,8 @@
.tab-content .tab-content
.tab-pane.active#tab-issues .tab-pane.active#tab-issues
.gray-content-block.middle-block .content-block.oneline-block
.pull-right .controls
- if can?(current_user, :create_issue, @project) - if can?(current_user, :create_issue, @project)
= link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do
%i.fa.fa-plus %i.fa.fa-plus
...@@ -94,8 +94,8 @@ ...@@ -94,8 +94,8 @@
= render('issues', title: 'Completed Issues (closed)', issues: @issues.closed, id: 'closed') = render('issues', title: 'Completed Issues (closed)', issues: @issues.closed, id: 'closed')
.tab-pane#tab-merge-requests .tab-pane#tab-merge-requests
.gray-content-block.middle-block .content-block.oneline-block
.pull-right .controls
- if can?(current_user, :read_merge_request, @project) - if can?(current_user, :read_merge_request, @project)
= link_to 'Browse Merge Requests', namespace_project_merge_requests_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped" = link_to 'Browse Merge Requests', namespace_project_merge_requests_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped"
...@@ -117,8 +117,7 @@ ...@@ -117,8 +117,7 @@
= render 'merge_request', merge_request: merge_request = render 'merge_request', merge_request: merge_request
.tab-pane#tab-participants .tab-pane#tab-participants
.gray-content-block.middle-block .content-block.oneline-block
.oneline
All participants to this milestone All participants to this milestone
%ul.bordered-list %ul.bordered-list
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
%i.fa.fa-comment %i.fa.fa-comment
= notes.count = notes.count
%td.notes_content %td.notes_content
%ul.notes{ rel: note.discussion_id } %ul.notes{ data: { discussion_id: note.discussion_id } }
= render notes = render notes
.discussion-reply-holder .discussion-reply-holder
= link_to_reply_diff(note) = link_to_reply_diff(note)
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
%i.fa.fa-comment %i.fa.fa-comment
= notes_left.count = notes_left.count
%td.notes_content.parallel.old %td.notes_content.parallel.old
%ul.notes{ rel: note1.discussion_id } %ul.notes{ data: { discussion_id: note1.discussion_id } }
= render notes_left = render notes_left
.discussion-reply-holder .discussion-reply-holder
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
%i.fa.fa-comment %i.fa.fa-comment
= notes_right.count = notes_right.count
%td.notes_content.parallel.new %td.notes_content.parallel.new
%ul.notes{ rel: note2.discussion_id } %ul.notes{ data: { discussion_id: note2.discussion_id } }
= render notes_right = render notes_right
.discussion-reply-holder .discussion-reply-holder
...@@ -31,4 +31,3 @@ ...@@ -31,4 +31,3 @@
- else - else
%td.notes_line.new= "" %td.notes_line.new= ""
%td.notes_content.parallel.new= "" %td.notes_content.parallel.new= ""
%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: { discussion: note.discussion_id } } %li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)] }
.timeline-entry-inner .timeline-entry-inner
.timeline-icon .timeline-icon
%a{href: user_path(note.author)} %a{href: user_path(note.author)}
......
...@@ -20,8 +20,7 @@ ...@@ -20,8 +20,7 @@
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
- else - else
.panel.panel-default .panel.panel-default
.notes{ rel: discussion_notes.first.discussion_id } .notes{ data: { discussion_id: discussion_notes.first.discussion_id } }
= render discussion_notes = render discussion_notes
.discussion-reply-holder .discussion-reply-holder
= link_to_reply_diff(discussion_notes.first) = link_to_reply_diff(discussion_notes.first)
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
%small %small
(#{members.count}) (#{members.count})
- if can?(current_user, :admin_group_member, @group) - if can?(current_user, :admin_group_member, @group)
.pull-right .controls
= link_to group_group_members_path(@group), class: 'btn' do = link_to group_group_members_path(@group), class: 'btn' do
= icon('pencil-square-o') = icon('pencil-square-o')
Manage group members Manage group members
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
project members project members
%small %small
(#{members.count}) (#{members.count})
.pull-right .controls
= form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
.form-group .form-group
= search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false } = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false }
......
- page_title "Members" - page_title "Members"
= render "header_title" = render "header_title"
- @blank_container = true
.project-members-page.prepend-top-default .project-members-page.prepend-top-default
- if can?(current_user, :admin_project_member, @project) - if can?(current_user, :admin_project_member, @project)
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
Add new user to project Add new user to project
.pull-right .controls
= link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do = link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do
Import members Import members
.panel-body .panel-body
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
- if merge_request.description.present? - if merge_request.description.present?
.description.term .description.term
= preserve do = preserve do
= search_md_sanitize(markdown(merge_request.description)) = search_md_sanitize(markdown(merge_request.description, { project: merge_request.project }))
%span.light %span.light
#{merge_request.project.name_with_namespace} #{merge_request.project.name_with_namespace}
.pull-right .pull-right
......
...@@ -4,8 +4,8 @@ _**Note:** This feature was [introduced][ce-2371] in GitLab 8.4_ ...@@ -4,8 +4,8 @@ _**Note:** This feature was [introduced][ce-2371] in GitLab 8.4_
--- ---
The housekeeping function runs [`git gc`][man] on the current project Git The housekeeping function runs `git gc` ([man page][man]) on the current
repository. project Git repository.
`git gc` runs a number of housekeeping tasks, such as compressing file `git gc` runs a number of housekeeping tasks, such as compressing file
revisions (to reduce disk space and increase performance) and removing revisions (to reduce disk space and increase performance) and removing
......
This diff is collapsed.
...@@ -8,13 +8,21 @@ Get a list of repository branches from a project, sorted by name alphabetically. ...@@ -8,13 +8,21 @@ Get a list of repository branches from a project, sorted by name alphabetically.
GET /projects/:id/repository/branches GET /projects/:id/repository/branches
``` ```
Parameters: | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
- `id` (required) - The ID of a project ```bash
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches
```
Example response:
```json ```json
[ [
{ {
"name": "master",
"protected": true,
"commit": { "commit": {
"author_email": "john@example.com", "author_email": "john@example.com",
"author_name": "John Smith", "author_name": "John Smith",
...@@ -27,10 +35,9 @@ Parameters: ...@@ -27,10 +35,9 @@ Parameters:
"parent_ids": [ "parent_ids": [
"4ad91d3c1144c406e50c7b33bae684bd6837faf8" "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
] ]
},
"name": "master",
"protected": true
} }
},
...
] ]
``` ```
...@@ -42,13 +49,21 @@ Get a single project repository branch. ...@@ -42,13 +49,21 @@ Get a single project repository branch.
GET /projects/:id/repository/branches/:branch GET /projects/:id/repository/branches/:branch
``` ```
Parameters: | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `branch` | string | yes | The name of the branch |
```bash
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master
```
- `id` (required) - The ID of a project Example response:
- `branch` (required) - The name of the branch
```json ```json
{ {
"name": "master",
"protected": true,
"commit": { "commit": {
"author_email": "john@example.com", "author_email": "john@example.com",
"author_name": "John Smith", "author_name": "John Smith",
...@@ -61,25 +76,30 @@ Parameters: ...@@ -61,25 +76,30 @@ Parameters:
"parent_ids": [ "parent_ids": [
"4ad91d3c1144c406e50c7b33bae684bd6837faf8" "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
] ]
}, }
"name": "master",
"protected": true
} }
``` ```
## Protect repository branch ## Protect repository branch
Protects a single project repository branch. This is an idempotent function, protecting an already Protects a single project repository branch. This is an idempotent function,
protected repository branch still returns a `200 OK` status code. protecting an already protected repository branch still returns a `200 OK`
status code.
``` ```
PUT /projects/:id/repository/branches/:branch/protect PUT /projects/:id/repository/branches/:branch/protect
``` ```
Parameters: ```bash
curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master/protect
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `branch` | string | yes | The name of the branch |
- `id` (required) - The ID of a project Example response:
- `branch` (required) - The name of the branch
```json ```json
{ {
...@@ -103,17 +123,24 @@ Parameters: ...@@ -103,17 +123,24 @@ Parameters:
## Unprotect repository branch ## Unprotect repository branch
Unprotects a single project repository branch. This is an idempotent function, unprotecting an already Unprotects a single project repository branch. This is an idempotent function,
unprotected repository branch still returns a `200 OK` status code. unprotecting an already unprotected repository branch still returns a `200 OK`
status code.
``` ```
PUT /projects/:id/repository/branches/:branch/unprotect PUT /projects/:id/repository/branches/:branch/unprotect
``` ```
Parameters: ```bash
curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master/unprotect
```
- `id` (required) - The ID of a project | Attribute | Type | Required | Description |
- `branch` (required) - The name of the branch | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `branch` | string | yes | The name of the branch |
Example response:
```json ```json
{ {
...@@ -141,11 +168,17 @@ Parameters: ...@@ -141,11 +168,17 @@ Parameters:
POST /projects/:id/repository/branches POST /projects/:id/repository/branches
``` ```
Parameters: | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `branch_name` | string | yes | The name of the branch |
| `ref` | string | yes | The branch name or commit SHA to create branch from |
```bash
curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/branches?branch_name=newbranch&ref=master"
```
- `id` (required) - The ID of a project Example response:
- `branch_name` (required) - The name of the branch
- `ref` (required) - Create branch from commit SHA or existing branch
```json ```json
{ {
...@@ -162,12 +195,13 @@ Parameters: ...@@ -162,12 +195,13 @@ Parameters:
"4ad91d3c1144c406e50c7b33bae684bd6837faf8" "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
] ]
}, },
"name": "master", "name": "newbranch",
"protected": false "protected": false
} }
``` ```
It return 200 if succeed or 400 if failed with error message explaining reason. It returns `200` if it succeeds or `400` if failed with an error message
explaining the reason.
## Delete repository branch ## Delete repository branch
...@@ -175,18 +209,22 @@ It return 200 if succeed or 400 if failed with error message explaining reason. ...@@ -175,18 +209,22 @@ It return 200 if succeed or 400 if failed with error message explaining reason.
DELETE /projects/:id/repository/branches/:branch DELETE /projects/:id/repository/branches/:branch
``` ```
Parameters: | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `branch` | string | yes | The name of the branch |
- `id` (required) - The ID of a project It returns `200` if it succeeds, `404` if the branch to be deleted does not exist
- `branch` (required) - The name of the branch or `400` for other reasons. In case of an error, an explaining message is provided.
It return 200 if succeed, 404 if the branch to be deleted does not exist ```bash
or 400 for other reasons. In case of an error, an explaining message is provided. curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/branches/newbranch"
```
Success response: Example response:
```json ```json
{ {
"branch_name": "my-removed-branch" "branch_name": "newbranch"
} }
``` ```
...@@ -8,9 +8,15 @@ Get a list of a project's deploy keys. ...@@ -8,9 +8,15 @@ Get a list of a project's deploy keys.
GET /projects/:id/keys GET /projects/:id/keys
``` ```
Parameters: | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project |
```bash
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/keys"
```
- `id` (required) - The ID of the project Example response:
```json ```json
[ [
...@@ -39,8 +45,16 @@ GET /projects/:id/keys/:key_id ...@@ -39,8 +45,16 @@ GET /projects/:id/keys/:key_id
Parameters: Parameters:
- `id` (required) - The ID of the project | Attribute | Type | Required | Description |
- `key_id` (required) - The ID of the deploy key | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project |
| `key_id` | integer | yes | The ID of the deploy key |
```bash
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/keys/11"
```
Example response:
```json ```json
{ {
...@@ -54,17 +68,34 @@ Parameters: ...@@ -54,17 +68,34 @@ Parameters:
## Add deploy key ## Add deploy key
Creates a new deploy key for a project. Creates a new deploy key for a project.
If deploy key already exists in another project - it will be joined to project but only if original one was is accessible by same user
If the deploy key already exists in another project, it will be joined to current
project only if original one was is accessible by the same user.
``` ```
POST /projects/:id/keys POST /projects/:id/keys
``` ```
Parameters: | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project |
| `title` | string | yes | New deploy key's title |
| `key` | string | yes | New deploy key |
```bash
curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -H "Content-Type: application/json" --data '{"title": "My deploy key", "key": "ssh-rsa AAAA..."}' "https://gitlab.example.com/api/v3/projects/5/keys/"
```
- `id` (required) - The ID of the project Example response:
- `title` (required) - New deploy key's title
- `key` (required) - New deploy key ```json
{
"key" : "ssh-rsa AAAA...",
"id" : 12,
"title" : "My deploy key",
"created_at" : "2015-08-29T12:44:31.550Z"
}
```
## Delete deploy key ## Delete deploy key
...@@ -74,7 +105,26 @@ Delete a deploy key from a project ...@@ -74,7 +105,26 @@ Delete a deploy key from a project
DELETE /projects/:id/keys/:key_id DELETE /projects/:id/keys/:key_id
``` ```
Parameters: | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project |
| `key_id` | integer | yes | The ID of the deploy key |
```bash
curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/keys/13"
```
- `id` (required) - The ID of the project Example response:
- `key_id` (required) - The ID of the deploy key
```json
{
"updated_at" : "2015-08-29T12:50:57.259Z",
"key" : "ssh-rsa AAAA...",
"public" : false,
"title" : "My deploy key",
"user_id" : null,
"created_at" : "2015-08-29T12:50:57.259Z",
"fingerprint" : "6a:33:1f:74:51:c0:39:81:79:ec:7a:31:f8:40:20:43",
"id" : 13
}
```
This diff is collapsed.
...@@ -2,83 +2,153 @@ ...@@ -2,83 +2,153 @@
## List labels ## List labels
Get all labels for given project. Get all labels for a given project.
``` ```
GET /projects/:id/labels GET /projects/:id/labels
``` ```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project |
```bash
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/labels
```
Example response:
```json ```json
[ [
{ {
"name": "Awesome", "name" : "bug",
"color": "#DD10AA" "color" : "#d9534f"
},
{
"color" : "#d9534f",
"name" : "confirmed"
},
{
"name" : "critical",
"color" : "#d9534f"
}, },
{ {
"name": "Documentation", "color" : "#428bca",
"color": "#1E80DD" "name" : "discussion"
}, },
{ {
"name": "Feature", "name" : "documentation",
"color": "#11FF22" "color" : "#f0ad4e"
}, },
{ {
"name": "Bug", "color" : "#5cb85c",
"color": "#EE1122" "name" : "enhancement"
},
{
"color" : "#428bca",
"name" : "suggestion"
},
{
"color" : "#f0ad4e",
"name" : "support"
} }
] ]
``` ```
## Create a new label ## Create a new label
Creates a new label for given repository with given name and color. Creates a new label for the given repository with the given name and color.
It returns 200 if the label was successfully created, 400 for wrong parameters
and 409 if the label already exists.
``` ```
POST /projects/:id/labels POST /projects/:id/labels
``` ```
Parameters: | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project |
| `name` | string | yes | The name of the label |
| `color` | string | yes | The color of the label in 6-digit hex notation with leading `#` sign |
- `id` (required) - The ID of a project ```bash
- `name` (required) - The name of the label curl --data "name=feature&color=#5843AD" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels"
- `color` (required) - Color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) ```
It returns 200 and the newly created label, if the operation succeeds. Example response:
If the label already exists, 409 and an error message is returned.
If label parameters are invalid, 400 and an explaining error message is returned. ```json
{
"name" : "feature",
"color" : "#5843AD"
}
```
## Delete a label ## Delete a label
Deletes a label given by its name. Deletes a label with a given name.
It returns 200 if the label was successfully deleted, 400 for wrong parameters
and 404 if the label does not exist.
In case of an error, an additional error message is returned.
``` ```
DELETE /projects/:id/labels DELETE /projects/:id/labels
``` ```
- `id` (required) - The ID of a project | Attribute | Type | Required | Description |
- `name` (required) - The name of the label to be deleted | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project |
| `name` | string | yes | The name of the label |
It returns 200 if the label successfully was deleted, 400 for wrong parameters ```bash
and 404 if the label does not exist. curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels?name=bug"
In case of an error, additionally an error message is returned. ```
Example response:
```json
{
"title" : "feature",
"color" : "#5843AD",
"updated_at" : "2015-11-03T21:22:30.737Z",
"template" : false,
"project_id" : 1,
"created_at" : "2015-11-03T21:22:30.737Z",
"id" : 9
}
```
## Edit an existing label ## Edit an existing label
Updates an existing label with new name or now color. At least one parameter Updates an existing label with new name or new color. At least one parameter
is required, to update the label. is required, to update the label.
It returns 200 if the label was successfully deleted, 400 for wrong parameters
and 404 if the label does not exist.
In case of an error, an additional error message is returned.
``` ```
PUT /projects/:id/labels PUT /projects/:id/labels
``` ```
Parameters: | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project |
| `name` | string | yes | The name of the existing label |
| `new_name` | string | yes if `color` if not provided | The new name of the label |
| `color` | string | yes if `new_name` is not provided | The new color of the label in 6-digit hex notation with leading `#` sign |
- `id` (required) - The ID of a project ```bash
- `name` (required) - The name of the existing label curl -X PUT --data "name=documentation&new_name=docs&color=#8E44AD" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels"
- `new_name` (optional) - The new name of the label ```
- `color` (optional) - New color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB)
On success, this method returns 200 with the updated label. Example response:
If required parameters are missing or parameters are invalid, 400 is returned.
If the label to be updated is missing, 404 is returned. ```json
In case of an error, additionally an error message is returned. {
"color" : "#8E44AD",
"name" : "docs"
}
```
# Namespaces # Namespaces
Usernames and groupnames fall under a special category called namespaces.
For users and groups supported API calls see the [users](users.md) and
[groups](groups.md) documentation respectively.
[Pagination](README.md#pagination) is used.
## List namespaces ## List namespaces
Get a list of namespaces. (As user: my namespaces, as admin: all namespaces) Get a list of the namespaces of the authenticated user. If the user is an
administrator, a list of all namespaces in the GitLab instance is shown.
``` ```
GET /namespaces GET /namespaces
``` ```
Example request:
```bash
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/namespaces
```
Example response:
```json ```json
[ [
{ {
...@@ -23,22 +39,32 @@ GET /namespaces ...@@ -23,22 +39,32 @@ GET /namespaces
] ]
``` ```
You can search for namespaces by name or path, see below.
## Search for namespace ## Search for namespace
Get all namespaces that match your string in their name or path. Get all namespaces that match a string in their name or path.
``` ```
GET /namespaces?search=foobar GET /namespaces?search=foobar
``` ```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `search` | string | no | Returns a list of namespaces the user is authorized to see based on the search criteria |
Example request:
```bash
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/namespaces?search=twitter
```
Example response:
```json ```json
[ [
{ {
"id": 1, "id": 4,
"path": "user1", "path": "twitter",
"kind": "user" "kind": "group"
} }
] ]
``` ```
# Application settings # Application settings
This API allows you to read and modify GitLab instance application settings. These API calls allow you to read and modify GitLab instance application
settings as appear in `/admin/application_settings`. You have to be an
administrator in order to perform this action.
## Get current application settings
## Get current application settings: List the current application settings of the GitLab instance.
``` ```
GET /application/settings GET /application/settings
``` ```
```bash
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/application/settings
```
Example response:
```json ```json
{ {
"id": 1, "default_projects_limit" : 10,
"default_projects_limit": 10, "signup_enabled" : true,
"signup_enabled": true, "id" : 1,
"signin_enabled": true, "default_branch_protection" : 2,
"gravatar_enabled": true, "restricted_visibility_levels" : [],
"sign_in_text": "", "signin_enabled" : true,
"created_at": "2015-06-12T15:51:55.432Z", "twitter_sharing_enabled" : true,
"updated_at": "2015-06-30T13:22:42.210Z", "after_sign_out_path" : null,
"home_page_url": "", "max_attachment_size" : 10,
"default_branch_protection": 2, "user_oauth_applications" : true,
"twitter_sharing_enabled": true, "updated_at" : "2016-01-04T15:44:55.176Z",
"restricted_visibility_levels": [], "session_expire_delay" : 10080,
"max_attachment_size": 10, "home_page_url" : null,
"session_expire_delay": 10080, "default_snippet_visibility" : 0,
"default_project_visibility": 0, "restricted_signup_domains" : [],
"default_snippet_visibility": 0, "created_at" : "2016-01-04T15:44:55.176Z",
"restricted_signup_domains": [], "default_project_visibility" : 0,
"user_oauth_applications": true, "gravatar_enabled" : true,
"after_sign_out_path": "" "sign_in_text" : null
} }
``` ```
## Change application settings: ## Change application settings
``` ```
PUT /application/settings PUT /application/settings
``` ```
Parameters: | Attribute | Type | Required | Description |
| --------- | ---- | :------: | ----------- |
- `default_projects_limit` - project limit per user | `default_projects_limit` | integer | no | Project limit per user. Default is `10` |
- `signup_enabled` - enable registration | `signup_enabled` | boolean | no | Enable registration. Default is `true`. |
- `signin_enabled` - enable login via GitLab account | `signin_enabled` | boolean | no | Enable login via a GitLab account. Default is `true`. |
- `gravatar_enabled` - enable gravatar | `gravatar_enabled` | boolean | no | Enable Gravatar |
- `sign_in_text` - text on login page | `sign_in_text` | string | no | Text on login page |
- `home_page_url` - redirect to this URL when not logged in | `home_page_url` | string | no | Redirect to this URL when not logged in |
- `default_branch_protection` - determine if developers can push to master | `default_branch_protection` | integer | no | Determine if developers can push to master. Can take `0` _(not protected, both developers and masters can push new commits, force push or delete the branch)_, `1` _(partially protected, developers can push new commits, but cannot force push or delete the branch, masters can do anything)_ or `2` _(fully protected, developers cannot push new commits, force push or delete the branch, masters can do anything)_ as a parameter. Default is `1`. |
- `twitter_sharing_enabled` - allow users to share project creation in twitter | `twitter_sharing_enabled` | boolean | no | Allow users to share project creation on Twitter |
- `restricted_visibility_levels` - restrict certain visibility levels | `restricted_visibility_levels` | array of integers | no | Selected levels cannot be used by non-admin users for projects or snippets. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is null which means there is no restriction. |
- `max_attachment_size` - limit attachment size | `max_attachment_size` | integer | no | Limit attachment size in MB |
- `session_expire_delay` - session lifetime | `session_expire_delay` | integer | no | Session duration in minutes. GitLab restart is required to apply changes |
- `default_project_visibility` - what visibility level new project receives | `default_project_visibility` | integer | no | What visibility level new projects receive. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is `0`.|
- `default_snippet_visibility` - what visibility level new snippet receives | `default_snippet_visibility` | integer | no | What visibility level new snippets receive. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is `0`.|
- `restricted_signup_domains` - force people to use only corporate emails for signup | `restricted_signup_domains` | array of strings | no | Force people to use only corporate emails for sign-up. Default is null, meaning there is no restriction. |
- `user_oauth_applications` - allow users to create oauth applications | `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider |
- `after_sign_out_path` - where redirect user after logout | `after_sign_out_path` | string | no | Where to redirect users after logout |
All parameters are optional. You can send only one that you want to change. ```bash
curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/application/settings?signup_enabled=false&default_project_visibility=1
```
Example response:
```json ```json
{ {
...@@ -79,7 +89,7 @@ All parameters are optional. You can send only one that you want to change. ...@@ -79,7 +89,7 @@ All parameters are optional. You can send only one that you want to change.
"restricted_visibility_levels": [], "restricted_visibility_levels": [],
"max_attachment_size": 10, "max_attachment_size": 10,
"session_expire_delay": 10080, "session_expire_delay": 10080,
"default_project_visibility": 0, "default_project_visibility": 1,
"default_snippet_visibility": 0, "default_snippet_visibility": 0,
"restricted_signup_domains": [], "restricted_signup_domains": [],
"user_oauth_applications": true, "user_oauth_applications": true,
......
# System hooks # System hooks
All methods require admin authorization. All methods require administrator authorization.
The URL endpoint of the system hooks can be configured in [the admin area under hooks](/admin/hooks). The URL endpoint of the system hooks can also be configured using the UI in
the admin area under **Hooks** (`/admin/hooks`).
Read more about [system hooks](../system_hooks/system_hooks.md).
## List system hooks ## List system hooks
Get list of system hooks Get a list of all system hooks.
---
``` ```
GET /hooks GET /hooks
``` ```
Parameters: Example request:
```bash
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/hooks
```
- **none** Example response:
```json ```json
[ [
{ {
"id": 3, "id" : 1,
"url": "http://example.com/hook", "url" : "https://gitlab.example.com/hook",
"created_at": "2013-10-02T10:15:31Z" "created_at" : "2015-11-04T20:07:35.874Z"
} }
] ]
``` ```
## Add new system hook hook ## Add new system hook
Add a new system hook.
---
``` ```
POST /hooks POST /hooks
``` ```
Parameters: | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `url` | string | yes | The hook URL |
- `url` (required) - The hook URL Example request:
```bash
curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/hooks?url=https://gitlab.example.com/hook"
```
Example response:
```json
[
{
"id" : 2,
"url" : "https://gitlab.example.com/hook",
"created_at" : "2015-11-04T20:07:35.874Z"
}
]
```
## Test system hook ## Test system hook
...@@ -42,29 +73,68 @@ Parameters: ...@@ -42,29 +73,68 @@ Parameters:
GET /hooks/:id GET /hooks/:id
``` ```
Parameters: | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the hook |
Example request:
```bash
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/hooks/2
```
- `id` (required) - The ID of hook Example response:
```json ```json
{ {
"event_name": "project_create", "project_id" : 1,
"name": "Ruby", "owner_email" : "example@gitlabhq.com",
"path": "ruby", "owner_name" : "Someone",
"project_id": 1, "name" : "Ruby",
"owner_name": "Someone", "path" : "ruby",
"owner_email": "example@gitlabhq.com" "event_name" : "project_create"
} }
``` ```
## Delete system hook ## Delete system hook
Deletes a system hook. This is an idempotent API function and returns `200 OK` even if the hook is not available. If the hook is deleted it is also returned as JSON. Deletes a system hook. This is an idempotent API function and returns `200 OK`
even if the hook is not available.
If the hook is deleted, a JSON object is returned. An error is raised if the
hook is not found.
---
``` ```
DELETE /hooks/:id DELETE /hooks/:id
``` ```
Parameters: | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the hook |
Example request:
```bash
curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/hooks/2
```
Example response:
- `id` (required) - The ID of hook ```json
{
"note_events" : false,
"project_id" : null,
"enable_ssl_verification" : true,
"url" : "https://gitlab.example.com/hook",
"updated_at" : "2015-11-04T20:12:15.931Z",
"issues_events" : false,
"merge_requests_events" : false,
"created_at" : "2015-11-04T20:12:15.931Z",
"service_id" : null,
"id" : 2,
"push_events" : true,
"tag_push_events" : false
}
```
...@@ -11,6 +11,11 @@ Starting from GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format ...@@ -11,6 +11,11 @@ Starting from GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format
changed to `ZIP`, and it is now possible to browse its contents, with the added changed to `ZIP`, and it is now possible to browse its contents, with the added
ability of downloading the files separately. ability of downloading the files separately.
**Note:**
The artifacts browser will be available only for new artifacts that are sent
to GitLab using GitLab Runner version 1.0 and up. You will not be available to
see the browser for old artifacts already uploaded to GitLab.
## Enabling build artifacts ## Enabling build artifacts
_If you are searching for ways to use artifacts, jump to _If you are searching for ways to use artifacts, jump to
...@@ -152,7 +157,7 @@ inside GitLab that make that possible. ...@@ -152,7 +157,7 @@ inside GitLab that make that possible.
1. While inside a specific build, you are presented with a download button 1. While inside a specific build, you are presented with a download button
along with the one that browses the archive along with the one that browses the archive
1. And finally, when browsing and archive you can see the download button at 1. And finally, when browsing an archive you can see the download button at
the top right corner the top right corner
--- ---
......
...@@ -8,7 +8,7 @@ See the documentation below for details on how to configure these services. ...@@ -8,7 +8,7 @@ See the documentation below for details on how to configure these services.
- [Jira](jira.md) Integrate with the JIRA issue tracker - [Jira](jira.md) Integrate with the JIRA issue tracker
- [External issue tracker](external-issue-tracker.md) Redmine, JIRA, etc. - [External issue tracker](external-issue-tracker.md) Redmine, JIRA, etc.
- [LDAP](ldap.md) Set up sign in via LDAP - [LDAP](ldap.md) Set up sign in via LDAP
- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab, and Google via OAuth. - [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab.com, Google, Bitbucket, Facebook, Shibboleth, SAML, Crowd and Azure
- [SAML](saml.md) Configure GitLab as a SAML 2.0 Service Provider - [SAML](saml.md) Configure GitLab as a SAML 2.0 Service Provider
- [CAS](cas.md) Configure GitLab to sign in using CAS - [CAS](cas.md) Configure GitLab to sign in using CAS
- [Slack](slack.md) Integrate with the Slack chat service - [Slack](slack.md) Integrate with the Slack chat service
......
...@@ -19,7 +19,7 @@ something else descriptive. ...@@ -19,7 +19,7 @@ something else descriptive.
1. Enter the address of your GitLab installation at the bottom of the package 1. Enter the address of your GitLab installation at the bottom of the package
![Facebook Website URL](facebook_website_url.png) ![Facebook Website URL](img/facebook_website_url.png)
1. Choose "Next" 1. Choose "Next"
...@@ -29,7 +29,7 @@ something else descriptive. ...@@ -29,7 +29,7 @@ something else descriptive.
1. Fill in a contact email for your app 1. Fill in a contact email for your app
![Facebook App Settings](facebook_app_settings.png) ![Facebook App Settings](img/facebook_app_settings.png)
1. Choose "Save Changes" 1. Choose "Save Changes"
...@@ -45,7 +45,7 @@ something else descriptive. ...@@ -45,7 +45,7 @@ something else descriptive.
1. You should now see an app key and app secret (see screenshot). Keep this page open as you continue configuration. 1. You should now see an app key and app secret (see screenshot). Keep this page open as you continue configuration.
![Facebook API Keys](facebook_api_keys.png) ![Facebook API Keys](img/facebook_api_keys.png)
1. On your GitLab server, open the configuration file. 1. On your GitLab server, open the configuration file.
......
...@@ -22,7 +22,7 @@ GitHub will generate an application ID and secret key for you to use. ...@@ -22,7 +22,7 @@ GitHub will generate an application ID and secret key for you to use.
1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot). 1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot).
Keep this page open as you continue configuration. Keep this page open as you continue configuration.
![GitHub app](github_app.png) ![GitHub app](img/github_app.png)
1. On your GitLab server, open the configuration file. 1. On your GitLab server, open the configuration file.
......
...@@ -28,7 +28,7 @@ GitLab.com will generate an application ID and secret key for you to use. ...@@ -28,7 +28,7 @@ GitLab.com will generate an application ID and secret key for you to use.
1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot). 1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot).
Keep this page open as you continue configuration. Keep this page open as you continue configuration.
![GitLab app](gitlab_app.png) ![GitLab app](img/gitlab_app.png)
1. On your GitLab server, open the configuration file. 1. On your GitLab server, open the configuration file.
......
...@@ -4,7 +4,7 @@ GitLab supports [Google actions in email](https://developers.google.com/gmail/ma ...@@ -4,7 +4,7 @@ GitLab supports [Google actions in email](https://developers.google.com/gmail/ma
If correctly setup, emails that require an action will be marked in Gmail. If correctly setup, emails that require an action will be marked in Gmail.
![gmail_actions_button.png](gmail_actions_button.png) ![gmail_actions_button.png](img/gmail_action_buttons_for_gitlab.png)
To get this functioning, you need to be registered with Google. To get this functioning, you need to be registered with Google.
[See how to register with Google in this document.](https://developers.google.com/gmail/markup/registering-with-google) [See how to register with Google in this document.](https://developers.google.com/gmail/markup/registering-with-google)
......
...@@ -25,7 +25,7 @@ To enable the Google OAuth2 OmniAuth provider you must register your application ...@@ -25,7 +25,7 @@ To enable the Google OAuth2 OmniAuth provider you must register your application
- Application type: "Web Application" - Application type: "Web Application"
- Authorized JavaScript origins: This isn't really used by GitLab but go ahead and put 'https://gitlab.example.com' here. - Authorized JavaScript origins: This isn't really used by GitLab but go ahead and put 'https://gitlab.example.com' here.
- Authorized redirect URI: 'https://gitlab.example.com/users/auth/google_oauth2/callback' - Authorized redirect URI: 'https://gitlab.example.com/users/auth/google_oauth2/callback'
1. Under the heading "Client ID for web application" you should see a Client ID and Client secret (see screenshot). Keep this page open as you continue configuration. ![Google app](google_app.png) 1. Under the heading "Client ID for web application" you should see a Client ID and Client secret (see screenshot). Keep this page open as you continue configuration. ![Google app](img/google_app.png)
1. On your GitLab server, open the configuration file. 1. On your GitLab server, open the configuration file.
......
...@@ -15,16 +15,16 @@ GitLab has two ways to add new OAuth2 application to an instance, you can add ap ...@@ -15,16 +15,16 @@ GitLab has two ways to add new OAuth2 application to an instance, you can add ap
### Adding application through profile ### Adding application through profile
Go to your profile section 'Application' and press button 'New Application' Go to your profile section 'Application' and press button 'New Application'
![applications](oauth_provider/user_wide_applications.png) ![applications](img/oauth_provider_user_wide_applications.png)
After this you will see application form, where "Name" is arbitrary name, "Redirect URI" is URL in your app where users will be sent after authorization on GitLab.com. After this you will see application form, where "Name" is arbitrary name, "Redirect URI" is URL in your app where users will be sent after authorization on GitLab.com.
![application_form](oauth_provider/application_form.png) ![application_form](img/oauth_provider_application_form.png)
### Authorized application ### Authorized application
Every application you authorized will be shown in your "Authorized application" sections. Every application you authorized will be shown in your "Authorized application" sections.
![authorized_application](oauth_provider/authorized_application.png) ![authorized_application](img/oauth_provider_authorized_application.png)
At any time you can revoke access just clicking button "Revoke" At any time you can revoke access just clicking button "Revoke"
...@@ -32,4 +32,4 @@ At any time you can revoke access just clicking button "Revoke" ...@@ -32,4 +32,4 @@ At any time you can revoke access just clicking button "Revoke"
If you want to create application that does not belong to certain user you can create it from admin area If you want to create application that does not belong to certain user you can create it from admin area
![admin_application](oauth_provider/admin_application.png) ![admin_application](img/oauth_provider_admin_application.png)
\ No newline at end of file
...@@ -9,6 +9,23 @@ Configuring OmniAuth does not prevent standard GitLab authentication or LDAP (if ...@@ -9,6 +9,23 @@ Configuring OmniAuth does not prevent standard GitLab authentication or LDAP (if
- [Enable OmniAuth for an Existing User](#enable-omniauth-for-an-existing-user) - [Enable OmniAuth for an Existing User](#enable-omniauth-for-an-existing-user)
- [OmniAuth configuration sample when using Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master#omniauth-google-twitter-github-login) - [OmniAuth configuration sample when using Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master#omniauth-google-twitter-github-login)
## Supported Providers
This is a list of the current supported OmniAuth providers. Before proceeding
on each provider's documentation, make sure to first read this document as it
contains some settings that are common for all providers.
- [GitHub](github.md)
- [Bitbucket](bitbucket.md)
- [GitLab.com](gitlab.md)
- [Google](google.md)
- [Facebook](facebook.md)
- [Twitter](twitter.md)
- [Shibboleth](shibboleth.md)
- [SAML](saml.md)
- [Crowd](crowd.md)
- [Azure](azure.md)
## Initial OmniAuth Configuration ## Initial OmniAuth Configuration
Before configuring individual OmniAuth providers there are a few global settings that are in common for all providers that we need to consider. Before configuring individual OmniAuth providers there are a few global settings that are in common for all providers that we need to consider.
...@@ -67,19 +84,6 @@ If you want to change these settings: ...@@ -67,19 +84,6 @@ If you want to change these settings:
Now we can choose one or more of the Supported Providers below to continue configuration. Now we can choose one or more of the Supported Providers below to continue configuration.
## Supported Providers
- [GitHub](github.md)
- [Bitbucket](bitbucket.md)
- [GitLab.com](gitlab.md)
- [Google](google.md)
- [Facebook](facebook.md)
- [Twitter](twitter.md)
- [Shibboleth](shibboleth.md)
- [SAML](saml.md)
- [Crowd](crowd.md)
- [Azure](azure.md)
## Enable OmniAuth for an Existing User ## Enable OmniAuth for an Existing User
Existing users can enable OmniAuth for specific providers after the account is created. For example, if the user originally signed in with LDAP an OmniAuth provider such as Twitter can be enabled. Follow the steps below to enable an OmniAuth provider for an existing user. Existing users can enable OmniAuth for specific providers after the account is created. For example, if the user originally signed in with LDAP an OmniAuth provider such as Twitter can be enabled. Follow the steps below to enable an OmniAuth provider for an existing user.
......
...@@ -14,7 +14,7 @@ To enable the Twitter OmniAuth provider you must register your application with ...@@ -14,7 +14,7 @@ To enable the Twitter OmniAuth provider you must register your application with
- Callback URL: 'https://gitlab.example.com/users/auth/twitter/callback' - Callback URL: 'https://gitlab.example.com/users/auth/twitter/callback'
- Agree to the "Developer Agreement". - Agree to the "Developer Agreement".
![Twitter App Details](twitter_app_details.png) ![Twitter App Details](img/twitter_app_details.png)
1. Select "Create your Twitter application." 1. Select "Create your Twitter application."
1. Select the "Settings" tab. 1. Select the "Settings" tab.
...@@ -27,7 +27,7 @@ To enable the Twitter OmniAuth provider you must register your application with ...@@ -27,7 +27,7 @@ To enable the Twitter OmniAuth provider you must register your application with
1. You should now see an API key and API secret (see screenshot). Keep this page open as you continue configuration. 1. You should now see an API key and API secret (see screenshot). Keep this page open as you continue configuration.
![Twitter app](twitter_app_api_keys.png) ![Twitter app](img/twitter_app_api_keys.png)
1. On your GitLab server, open the configuration file. 1. On your GitLab server, open the configuration file.
......
...@@ -102,6 +102,15 @@ Feature: Project Merge Requests ...@@ -102,6 +102,15 @@ Feature: Project Merge Requests
And I leave a comment like "Line is wrong" on diff And I leave a comment like "Line is wrong" on diff
And I switch to the merge request's comments tab And I switch to the merge request's comments tab
Then I should see a discussion has started on diff Then I should see a discussion has started on diff
And I should see a badge of "1" next to the discussion link
@javascript
Scenario: I see a new comment on merge request diff from another user in the discussion tab
Given project "Shop" have "Bug NS-05" open merge request with diffs inside
And I visit merge request page "Bug NS-05"
And user "John Doe" leaves a comment like "Line is wrong" on diff
Then I should see a discussion by user "John Doe" has started on diff
And I should see a badge of "1" next to the discussion link
@javascript @javascript
Scenario: I edit a comment on a merge request diff Scenario: I edit a comment on a merge request diff
...@@ -119,9 +128,11 @@ Feature: Project Merge Requests ...@@ -119,9 +128,11 @@ Feature: Project Merge Requests
And I visit merge request page "Bug NS-05" And I visit merge request page "Bug NS-05"
And I click on the Changes tab And I click on the Changes tab
And I leave a comment like "Line is wrong" on diff And I leave a comment like "Line is wrong" on diff
And I should see a badge of "1" next to the discussion link
And I delete the comment "Line is wrong" on diff And I delete the comment "Line is wrong" on diff
And I click on the Discussion tab And I click on the Discussion tab
Then I should not see any discussion Then I should not see any discussion
And I should see a badge of "0" next to the discussion link
@javascript @javascript
Scenario: I comment on a line of a commit in merge request Scenario: I comment on a line of a commit in merge request
......
...@@ -181,6 +181,15 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -181,6 +181,15 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
leave_comment "Line is wrong" leave_comment "Line is wrong"
end end
step 'user "John Doe" leaves a comment like "Line is wrong" on diff' do
mr = MergeRequest.find_by(title: "Bug NS-05")
create(:note_on_merge_request_diff, project: project,
noteable_id: mr.id,
author: user_exists("John Doe"),
line_code: sample_commit.line_code,
note: 'Line is wrong')
end
step 'I leave a comment like "Line is wrong" on diff in commit' do step 'I leave a comment like "Line is wrong" on diff in commit' do
click_diff_line(sample_commit.line_code) click_diff_line(sample_commit.line_code)
leave_comment "Line is wrong" leave_comment "Line is wrong"
...@@ -238,6 +247,22 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -238,6 +247,22 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end end
end end
step 'I should see a discussion by user "John Doe" has started on diff' do
page.within(".notes .discussion") do
page.should have_content "#{user_exists("John Doe").name} started a discussion"
page.should have_content sample_commit.line_code_path
page.should have_content "Line is wrong"
end
end
step 'I should see a badge of "1" next to the discussion link' do
expect_discussion_badge_to_have_counter("1")
end
step 'I should see a badge of "0" next to the discussion link' do
expect_discussion_badge_to_have_counter("0")
end
step 'I should see a discussion has started on commit diff' do step 'I should see a discussion has started on commit diff' do
page.within(".notes .discussion") do page.within(".notes .discussion") do
page.should have_content "#{current_user.name} started a discussion on commit" page.should have_content "#{current_user.name} started a discussion on commit"
...@@ -452,4 +477,10 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -452,4 +477,10 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
def have_visible_content (text) def have_visible_content (text)
have_css("*", text: text, visible: true) have_css("*", text: text, visible: true)
end end
def expect_discussion_badge_to_have_counter(value)
page.within(".notes-tab .badge") do
page.should have_content value
end
end
end end
...@@ -23,7 +23,7 @@ module SharedDiffNote ...@@ -23,7 +23,7 @@ module SharedDiffNote
page.within(diff_file_selector) do page.within(diff_file_selector) do
click_diff_line(sample_commit.line_code) click_diff_line(sample_commit.line_code)
page.within("form[rel$='#{sample_commit.line_code}']") do page.within("form[id$='#{sample_commit.line_code}']") do
fill_in "note[note]", with: "Typo, please fix" fill_in "note[note]", with: "Typo, please fix"
find(".js-comment-button").trigger("click") find(".js-comment-button").trigger("click")
sleep 0.05 sleep 0.05
...@@ -33,7 +33,7 @@ module SharedDiffNote ...@@ -33,7 +33,7 @@ module SharedDiffNote
step 'I leave a diff comment in a parallel view on the left side like "Old comment"' do step 'I leave a diff comment in a parallel view on the left side like "Old comment"' do
click_parallel_diff_line(sample_commit.line_code, 'old') click_parallel_diff_line(sample_commit.line_code, 'old')
page.within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}']") do
fill_in "note[note]", with: "Old comment" fill_in "note[note]", with: "Old comment"
find(".js-comment-button").trigger("click") find(".js-comment-button").trigger("click")
end end
...@@ -41,7 +41,7 @@ module SharedDiffNote ...@@ -41,7 +41,7 @@ module SharedDiffNote
step 'I leave a diff comment in a parallel view on the right side like "New comment"' do step 'I leave a diff comment in a parallel view on the right side like "New comment"' do
click_parallel_diff_line(sample_commit.line_code, 'new') click_parallel_diff_line(sample_commit.line_code, 'new')
page.within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}']") do
fill_in "note[note]", with: "New comment" fill_in "note[note]", with: "New comment"
find(".js-comment-button").trigger("click") find(".js-comment-button").trigger("click")
end end
...@@ -51,7 +51,7 @@ module SharedDiffNote ...@@ -51,7 +51,7 @@ module SharedDiffNote
page.within(diff_file_selector) do page.within(diff_file_selector) do
click_diff_line(sample_commit.line_code) click_diff_line(sample_commit.line_code)
page.within("form[rel$='#{sample_commit.line_code}']") do page.within("form[id$='#{sample_commit.line_code}']") do
fill_in "note[note]", with: "Should fix it :smile:" fill_in "note[note]", with: "Should fix it :smile:"
find('.js-md-preview-button').click find('.js-md-preview-button').click
end end
...@@ -62,7 +62,7 @@ module SharedDiffNote ...@@ -62,7 +62,7 @@ module SharedDiffNote
page.within(diff_file_selector) do page.within(diff_file_selector) do
click_diff_line(sample_commit.del_line_code) click_diff_line(sample_commit.del_line_code)
page.within("form[rel$='#{sample_commit.del_line_code}']") do page.within("form[id$='#{sample_commit.del_line_code}']") do
fill_in "note[note]", with: "DRY this up" fill_in "note[note]", with: "DRY this up"
find('.js-md-preview-button').click find('.js-md-preview-button').click
end end
...@@ -91,7 +91,7 @@ module SharedDiffNote ...@@ -91,7 +91,7 @@ module SharedDiffNote
page.within(diff_file_selector) do page.within(diff_file_selector) do
click_diff_line(sample_commit.line_code) click_diff_line(sample_commit.line_code)
page.within("form[rel$='#{sample_commit.line_code}']") do page.within("form[id$='#{sample_commit.line_code}']") do
fill_in 'note[note]', with: ':smile:' fill_in 'note[note]', with: ':smile:'
click_button('Add Comment') click_button('Add Comment')
end end
......
...@@ -43,6 +43,10 @@ module Banzai ...@@ -43,6 +43,10 @@ module Banzai
# Allow span elements # Allow span elements
whitelist[:elements].push('span') whitelist[:elements].push('span')
# Allow abbr elements with title attribute
whitelist[:elements].push('abbr')
whitelist[:attributes]['abbr'] = %w(title)
# Allow any protocol in `a` elements... # Allow any protocol in `a` elements...
whitelist[:protocols].delete('a') whitelist[:protocols].delete('a')
......
module Gitlab module Gitlab
class SnippetSearchResults < SearchResults class SnippetSearchResults < SearchResults
include SnippetsHelper
attr_reader :limit_snippet_ids attr_reader :limit_snippet_ids
def initialize(limit_snippet_ids, query) def initialize(limit_snippet_ids, query)
...@@ -47,85 +49,5 @@ module Gitlab ...@@ -47,85 +49,5 @@ module Gitlab
def default_scope def default_scope
'snippet_blobs' 'snippet_blobs'
end end
# Get an array of line numbers surrounding a matching
# line, bounded by min/max.
#
# @returns Array of line numbers
def bounded_line_numbers(line, min, max)
lower = line - surrounding_lines > min ? line - surrounding_lines : min
upper = line + surrounding_lines < max ? line + surrounding_lines : max
(lower..upper).to_a
end
# Returns a sorted set of lines to be included in a snippet preview.
# This ensures matching adjacent lines do not display duplicated
# surrounding code.
#
# @returns Array, unique and sorted.
def matching_lines(lined_content)
used_lines = []
lined_content.each_with_index do |line, line_number|
used_lines.concat bounded_line_numbers(
line_number,
0,
lined_content.size
) if line.include?(query)
end
used_lines.uniq.sort
end
# 'Chunkify' entire snippet. Splits the snippet data into matching lines +
# surrounding_lines() worth of unmatching lines.
#
# @returns a hash with {snippet_object, snippet_chunks:{data,start_line}}
def chunk_snippet(snippet)
lined_content = snippet.content.split("\n")
used_lines = matching_lines(lined_content)
snippet_chunk = []
snippet_chunks = []
snippet_start_line = 0
last_line = -1
# Go through each used line, and add consecutive lines as a single chunk
# to the snippet chunk array.
used_lines.each do |line_number|
if last_line < 0
# Start a new chunk.
snippet_start_line = line_number
snippet_chunk << lined_content[line_number]
elsif last_line == line_number - 1
# Consecutive line, continue chunk.
snippet_chunk << lined_content[line_number]
else
# Non-consecutive line, add chunk to chunk array.
snippet_chunks << {
data: snippet_chunk.join("\n"),
start_line: snippet_start_line + 1
}
# Start a new chunk.
snippet_chunk = [lined_content[line_number]]
snippet_start_line = line_number
end
last_line = line_number
end
# Add final chunk to chunk array
snippet_chunks << {
data: snippet_chunk.join("\n"),
start_line: snippet_start_line + 1
}
# Return snippet with chunk array
{ snippet_object: snippet, snippet_chunks: snippet_chunks }
end
# Defines how many unmatching lines should be
# included around the matching lines in a snippet
def surrounding_lines
3
end
end end
end end
#!/bin/bash
if [ -f /.dockerinit ]; then
export FLAGS=(--deployment --path /cache)
apt-get update -qq
apt-get install -y -qq nodejs
wget -q http://ftp.de.debian.org/debian/pool/main/p/phantomjs/phantomjs_1.9.0-1+b1_amd64.deb
dpkg -i phantomjs_1.9.0-1+b1_amd64.deb
cp config/database.yml.mysql config/database.yml
sed -i "s/username:.*/username: root/g" config/database.yml
sed -i "s/password:.*/password:/g" config/database.yml
sed -i "s/# socket:.*/host: mysql/g" config/database.yml
else
export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin
cp config/database.yml.mysql config/database.yml
sed -i "s/username\:.*$/username\: runner/" config/database.yml
sed -i "s/password\:.*$/password\: 'password'/" config/database.yml
sed -i "s/gitlab_ci_test/gitlab_ci_test_$((RANDOM/5000))/" config/database.yml
fi
#!/bin/bash #!/bin/bash
if [ -f /.dockerinit ]; then if [ -f /.dockerinit ]; then
# Docker runners use `/cache` folder which is persisted every build
if [ ! -e /cache/phantomjs_1.9.8-0jessie_amd64.deb ]; then
wget -q https://gitlab.com/axil/phantomjs-debian/raw/master/phantomjs_1.9.8-0jessie_amd64.deb wget -q https://gitlab.com/axil/phantomjs-debian/raw/master/phantomjs_1.9.8-0jessie_amd64.deb
dpkg -i phantomjs_1.9.8-0jessie_amd64.deb mv phantomjs_1.9.8-0jessie_amd64.deb /cache
fi
dpkg -i /cache/phantomjs_1.9.8-0jessie_amd64.deb
apt-get update -qq apt-get update -qq
apt-get install -y -qq libicu-dev libkrb5-dev cmake nodejs postgresql-client mysql-client apt-get -o dir::cache::archives="/cache/apt" install -y -qq --force-yes \
libicu-dev libkrb5-dev cmake nodejs postgresql-client mysql-client
cp config/database.yml.mysql config/database.yml cp config/database.yml.mysql config/database.yml
sed -i 's/username:.*/username: root/g' config/database.yml sed -i 's/username:.*/username: root/g' config/database.yml
...@@ -13,8 +19,8 @@ if [ -f /.dockerinit ]; then ...@@ -13,8 +19,8 @@ if [ -f /.dockerinit ]; then
cp config/resque.yml.example config/resque.yml cp config/resque.yml.example config/resque.yml
sed -i 's/localhost/redis/g' config/resque.yml sed -i 's/localhost/redis/g' config/resque.yml
FLAGS=(--deployment --path /cache)
export FLAGS export FLAGS=(--path /cache)
else else
export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin
cp config/database.yml.mysql config/database.yml cp config/database.yml.mysql config/database.yml
......
...@@ -167,7 +167,7 @@ describe 'Comments', feature: true do ...@@ -167,7 +167,7 @@ describe 'Comments', feature: true do
end end
it 'should be removed when canceled' do it 'should be removed when canceled' do
page.within(".diff-file form[rel$='#{line_code}']") do page.within(".diff-file form[id$='#{line_code}']") do
find('.js-close-discussion-note-form').trigger('click') find('.js-close-discussion-note-form').trigger('click')
end end
......
...@@ -75,6 +75,11 @@ describe Banzai::Filter::SanitizationFilter, lib: true do ...@@ -75,6 +75,11 @@ describe Banzai::Filter::SanitizationFilter, lib: true do
expect(filter(act).to_html).to eq exp expect(filter(act).to_html).to eq exp
end end
it 'allows `abbr` elements' do
exp = act = %q{<abbr title="HyperText Markup Language">HTML</abbr>}
expect(filter(act).to_html).to eq exp
end
it 'removes `rel` attribute from `a` elements' do it 'removes `rel` attribute from `a` elements' do
act = %q{<a href="#" rel="nofollow">Link</a>} act = %q{<a href="#" rel="nofollow">Link</a>}
exp = %q{<a href="#">Link</a>} exp = %q{<a href="#">Link</a>}
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/*!
* g.Raphael 0.5 - Charting library, based on Raphaël
*
* Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com)
* Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
* From: https://github.com/jhurt/g.raphael/blob/master/g.bar.js
*/
(function(){function c(c,d,e,f,g,h,i,j){var k,l={round:"round",sharp:"sharp",soft:"soft",square:"square"};if(g&&!f||!g&&!e)return i?"":j.path();switch(h=l[h]||"square",f=Math.round(f),e=Math.round(e),c=Math.round(c),d=Math.round(d),h){case"round":if(g)m=~~(e/2),m>f?(m=f,k=["M",c-~~(e/2),d,"l",0,0,"a",~~(e/2),m,0,0,1,e,0,"l",0,0,"z"]):k=["M",c-m,d,"l",0,m-f,"a",m,m,0,1,1,e,0,"l",0,f-m,"z"];else{var m=~~(f/2);m>e?(m=e,k=["M",c+.5,d+.5-~~(f/2),"l",0,0,"a",m,~~(f/2),0,0,1,0,f,"l",0,0,"z"]):k=["M",c+.5,d+.5-m,"l",e-m,0,"a",m,m,0,1,1,0,f,"l",m-e,0,"z"]}break;case"sharp":if(g)n=~~(e/2),k=["M",c+n,d,"l",-e,0,0,-b(f-n,0),n,-a(n,f),n,a(n,f),n,"z"];else{var n=~~(f/2);k=["M",c,d+n,"l",0,-f,b(e-n,0),0,a(n,e),n,-a(n,e),n+(f>2*n),"z"]}break;case"square":k=g?["M",c+~~(e/2),d,"l",1-e,0,0,-f,e-1,0,"z"]:["M",c,d+~~(f/2),"l",0,-f,e,0,0,f,"z"];break;case"soft":g?(m=a(Math.round(e/5),f),k=["M",c-~~(e/2),d,"l",0,m-f,"a",m,m,0,0,1,m,-m,"l",e-2*m,0,"a",m,m,0,0,1,m,m,"l",0,f-m,"z"]):(m=a(e,Math.round(f/5)),k=["M",c+.5,d+.5-~~(f/2),"l",e-m,0,"a",m,m,0,0,1,m,m,"l",0,f-2*m,"a",m,m,0,0,1,-m,m,"l",m-e,0,"z"])}return i?k.join(","):j.path(k)}function d(a,b,d,e,f,g,h){h=h||{};var i=this,j=h.type||"square",k=parseFloat(h.gutter||"20%"),l=a.set(),m=a.set(),n=a.set(),o=a.set(),p=Math.max.apply(Math,g),q=[],r=0,s=h.colors||i.colors,t=g.length;if(Raphael.is(g[0],"array")){p=[],r=t,t=0;for(var u=g.length;u--;)m.push(a.set()),p.push(Math.max.apply(Math,g[u])),t=Math.max(t,g[u].length);if(h.stacked)for(var u=t;u--;){for(var v=0,w=g.length;w--;)v+=+g[w][u]||0;q.push(v)}for(var u=g.length;u--;)if(t>g[u].length)for(var w=t;w--;)g[u].push(0);p=Math.max.apply(Math,h.stacked?q:p)}p=h.to||p;var x=100*(e/(t*(100+k)+k)),y=x*k/100,z=null==h.vgutter?20:h.vgutter,A=[],B=b+y,C=(f-2*z)/p;h.stretch||(y=Math.round(y),x=Math.floor(x)),!h.stacked&&(x/=r||1);for(var u=0;t>u;u++){A=[];for(var w=0;(r||1)>w;w++){var D=Math.round((r?g[w][u]:g[u])*C),E=d+f-z-D,F=c(Math.round(B+x/2),E+D,x,D,!0,j,null,a).attr({stroke:"none",fill:s[r?w:u]});r?m[w].push(F):m.push(F),F.y=E,F.x=Math.round(B+x/2),F.w=x,F.h=D,F.value=r?g[w][u]:g[u],h.stacked?A.push(F):B+=x}if(h.stacked){var G;o.push(G=a.rect(A[0].x-A[0].w/2,d,x,f).attr(i.shim)),G.bars=a.set();for(var H=0,I=A.length;I--;)A[I].toFront();for(var I=0,J=A.length;J>I;I++){var K,F=A[I],D=(H+F.value)*C,L=c(F.x,d+f-z-.5*!!H,x,D,!0,j,1,a);G.bars.push(F),H&&F.attr({path:L}),F.h=D,F.y=d+f-z-.5*!!H-D,n.push(K=a.rect(F.x-F.w/2,F.y,x,F.value*C).attr(i.shim)),K.bar=F,K.value=F.value,H+=F.value}B+=x}B+=y}if(o.toFront(),B=b+y,!h.stacked)for(var u=0;t>u;u++){for(var w=0;(r||1)>w;w++){var K;n.push(K=a.rect(Math.round(B),d+z,x,f-z).attr(i.shim)),K.bar=r?m[w][u]:m[u],K.value=K.bar.value,B+=x}B+=y}return l.label=function(b,c){b=b||[],this.labels=a.set();var e,j=-1/0;if(h.stacked){for(var k=0;t>k;k++)for(var l=0,o=0;(r||1)>o;o++)if(l+=r?g[o][k]:g[k],o==r-1){var q=i.labelise(b[k],l,p);e=a.text(m[o][k].x,d+f-z/2,q).attr(i.txtattr).attr({fill:h.legendcolor||"#000","text-anchor":"start"}).insertBefore(n[k*(r||1)+o]);var s=e.getBBox();j>s.x-7?e.remove():(this.labels.push(e),j=s.x+s.width)}}else for(var k=0;t>k;k++)for(var o=0;(r||1)>o;o++){var q=i.labelise(r?b[o]&&b[o][k]:b[k],r?g[o][k]:g[k],p);e=a.text(m[o][k].x-x/2,c?d+f-z/2:m[o][k].y-10,q).attr(i.txtattr).attr({fill:h.legendcolor||"#000","text-anchor":"start"}).insertBefore(n[k*(r||1)+o]);var s=e.getBBox();e.translate((x-s.width)/2,1),j>s.x-7?e.remove():(this.labels.push(e),j=s.x+s.width)}return this},l.hover=function(a,b){return o.hide(),n.show(),n.mouseover(a).mouseout(b),this},l.hoverColumn=function(a,b){return n.hide(),o.show(),b=b||function(){},o.mouseover(a).mouseout(b),this},l.click=function(a){return o.hide(),n.show(),n.click(a),this},l.each=function(a){if(!Raphael.is(a,"function"))return this;for(var b=n.length;b--;)a.call(n[b]);return this},l.eachColumn=function(a){if(!Raphael.is(a,"function"))return this;for(var b=o.length;b--;)a.call(o[b]);return this},l.clickColumn=function(a){return n.hide(),o.show(),o.click(a),this},l.push(m,n,o),l.bars=m,l.covers=n,l}function e(a,b,d,e,f,g,h){h=h||{};var i=this,j=h.type||"square",k=parseFloat(h.gutter||"20%"),l=a.set(),m=a.set(),n=a.set(),o=a.set(),p=Math.max.apply(Math,g),q=[],r=0,s=h.colors||i.colors,t=g.length;if(Raphael.is(g[0],"array")){p=[],r=t,t=0;for(var u=g.length;u--;)m.push(a.set()),p.push(Math.max.apply(Math,g[u])),t=Math.max(t,g[u].length);if(h.stacked)for(var u=t;u--;){for(var v=0,w=g.length;w--;)v+=+g[w][u]||0;q.push(v)}for(var u=g.length;u--;)if(t>g[u].length)for(var w=t;w--;)g[u].push(0);p=Math.max.apply(Math,h.stacked?q:p)}p=h.to||p;var x=Math.floor(100*(f/(t*(100+k)+k))),y=Math.floor(x*k/100),z=[],A=d+y,B=(e-1)/p;!h.stacked&&(x/=r||1);for(var u=0;t>u;u++){z=[];for(var w=0;(r||1)>w;w++){var C=r?g[w][u]:g[u],D=c(b,A+x/2,Math.round(C*B),x-1,!1,j,null,a).attr({stroke:"none",fill:s[r?w:u]});r?m[w].push(D):m.push(D),D.x=b+Math.round(C*B),D.y=A+x/2,D.w=Math.round(C*B),D.h=x,D.value=+C,h.stacked?z.push(D):A+=x}if(h.stacked){var E=a.rect(b,z[0].y-z[0].h/2,e,x).attr(i.shim);o.push(E),E.bars=a.set();for(var F=0,G=z.length;G--;)z[G].toFront();for(var G=0,H=z.length;H>G;G++){var I,D=z[G],C=Math.round((F+D.value)*B),J=c(b,D.y,C,x-1,!1,j,1,a);E.bars.push(D),F&&D.attr({path:J}),D.w=C,D.x=b+C,n.push(I=a.rect(b+F*B,D.y-D.h/2,D.value*B,x).attr(i.shim)),I.bar=D,F+=D.value}A+=x}A+=y}if(o.toFront(),A=d+y,!h.stacked)for(var u=0;t>u;u++){for(var w=0;(r||1)>w;w++){var I=a.rect(b,A,e,x).attr(i.shim);n.push(I),I.bar=r?m[w][u]:m[u],I.value=I.bar.value,A+=x}A+=y}return l.label=function(c,d){c=c||[],this.labels=a.set();for(var e=0;t>e;e++)for(var f=0;r>f;f++){var o,j=i.labelise(r?c[f]&&c[f][e]:c[e],r?g[f][e]:g[e],p),k=d?m[f][e].x-x/2+3:b+5;this.labels.push(o=a.text(k,m[f][e].y,j).attr(i.txtattr).attr({fill:h.legendcolor||"#000","text-anchor":"start"}).insertBefore(n[0])),b+5>o.getBBox().x?o.attr({x:b+5,"text-anchor":"start"}):m[f][e].label=o}return this},l.hover=function(a,b){return o.hide(),n.show(),b=b||function(){},n.mouseover(a).mouseout(b),this},l.hoverColumn=function(a,b){return n.hide(),o.show(),b=b||function(){},o.mouseover(a).mouseout(b),this},l.each=function(a){if(!Raphael.is(a,"function"))return this;for(var b=n.length;b--;)a.call(n[b]);return this},l.eachColumn=function(a){if(!Raphael.is(a,"function"))return this;for(var b=o.length;b--;)a.call(o[b]);return this},l.click=function(a){return o.hide(),n.show(),n.click(a),this},l.clickColumn=function(a){return n.hide(),o.show(),o.click(a),this},l.push(m,n,o),l.bars=m,l.covers=n,l}var a=Math.min,b=Math.max,f=function(){};f.prototype=Raphael.g,e.prototype=d.prototype=new f,Raphael.fn.hbarchart=function(a,b,c,d,f,g){return new e(this,a,b,c,d,f,g)},Raphael.fn.barchart=function(a,b,c,e,f,g){return new d(this,a,b,c,e,f,g)}})();
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment