Commit 20e269cb authored by Marco Wessel's avatar Marco Wessel

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into configure-protection

Conflicts:
	CHANGELOG
	db/schema.rb
parents 2a450211 604f3927
......@@ -3,10 +3,10 @@ Note: The upcoming release contains empty lines to reduce the number of merge co
v 7.8.0
- Replace highlight.js with rouge-fork rugments (Stefan Tatschner)
- Make project search case insensitive (Hannes Rosenögger)
-
- Include issue/mr participants in list of recipients for reassign/close/reopen emails
- Expose description in groups API
-
-
- Better UI for project services page
- Cleaner UI for web editor
- Add diff syntax highlighting in email-on-push service notifications (Hannes Rosenögger)
-
-
......@@ -17,8 +17,10 @@ v 7.8.0
- Show tags in commit view (Hannes Rosenögger)
- Only count a user's vote once on a merge request or issue (Michael Clarke)
-
- Increate font size when browse source files and diffs
- Create new file in empty repository using GitLab UI
-
-
- Ability to clone project using oauth2 token
-
- Upgrade Sidekiq gem to version 3.3.0
- Stop git zombie creation during force push check
......@@ -29,11 +31,13 @@ v 7.8.0
-
-
- Allow configuring protection of the default branch upon first push (Marco Wessel)
-
-
- Add a commit calendar to the user profile (Hannes Rosenögger)
-
-
-
-
- Fix long broadcast message cut-off on left sidebar (Visay Keo)
- Add Project Avatars (Steven Thonus and Hannes Rosenögger)
-
-
......@@ -59,6 +63,7 @@ v 7.8.0
-
-
-
- Added support for firing system hooks on group create/destroy and adding/removing users to group (Boyan Tabakov)
v 7.7.1
- Improve mention autocomplete performance
......
......@@ -154,6 +154,9 @@ gem "slack-notifier", "~> 1.0.0"
# d3
gem "d3_rails", "~> 3.1.4"
#cal-heatmap
gem "cal-heatmap-rails", "~> 0.0.1"
# underscore-rails
gem "underscore-rails", "~> 1.4.4"
......@@ -170,7 +173,7 @@ gem 'ace-rails-ap'
gem 'mousetrap-rails'
# Semantic UI Sass for Sidebar
gem 'semantic-ui-sass', '~> 0.16.1.0'
gem 'semantic-ui-sass', '~> 1.8.0'
gem "sass-rails", '~> 4.0.2'
gem "coffee-rails"
......
......@@ -52,6 +52,7 @@ GEM
sass (~> 3.2)
browser (0.7.2)
builder (3.2.2)
cal-heatmap-rails (0.0.1)
capybara (2.2.1)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
......@@ -490,7 +491,7 @@ GEM
activesupport (>= 3.1, < 4.2)
select2-rails (3.5.2)
thor (~> 0.14)
semantic-ui-sass (0.16.1.0)
semantic-ui-sass (1.8.0.0)
sass (~> 3.2)
settingslogic (2.0.9)
sexp_processor (4.4.0)
......@@ -627,6 +628,7 @@ DEPENDENCIES
binding_of_caller
bootstrap-sass (~> 3.0)
browser
cal-heatmap-rails (~> 0.0.1)
capybara (~> 2.2.1)
carrierwave
coffee-rails
......@@ -715,7 +717,7 @@ DEPENDENCIES
sdoc
seed-fu
select2-rails
semantic-ui-sass (~> 0.16.1.0)
semantic-ui-sass (~> 1.8.0)
settingslogic
shoulda-matchers (~> 2.1.0)
sidekiq (~> 3.3)
......
......@@ -39,6 +39,7 @@
#= require shortcuts_dashboard_navigation
#= require shortcuts_issueable
#= require shortcuts_network
#= require cal-heatmap
#= require_tree .
window.slugify = (text) ->
......
class @EditBlob
constructor: (assets_path, mode)->
ace.config.set "modePath", assets_path + '/ace'
ace.config.loadModule "ace/ext/searchbox"
if mode
ace_mode = mode
editor = ace.edit("editor")
editor.focus()
@editor = editor
if ace_mode
editor.getSession().setMode "ace/mode/" + ace_mode
disableButtonIfEmptyField "#commit_message", ".js-commit-button"
$(".js-commit-button").click ->
$("#file-content").val editor.getValue()
$(".file-editor form").submit()
return
editModePanes = $(".js-edit-mode-pane")
editModeLinks = $(".js-edit-mode a")
editModeLinks.click (event) ->
event.preventDefault()
currentLink = $(this)
paneId = currentLink.attr("href")
currentPane = editModePanes.filter(paneId)
editModeLinks.parent().removeClass "active hover"
currentLink.parent().addClass "active hover"
editModePanes.hide()
if paneId is "#preview"
currentPane.fadeIn 200
$.post currentLink.data("preview-url"),
content: editor.getValue()
, (response) ->
currentPane.empty().append response
return
else
currentPane.fadeIn 200
editor.focus()
return
editor: ->
return @editor
class @NewBlob
constructor: (assets_path, mode)->
ace.config.set "modePath", assets_path + '/ace'
ace.config.loadModule "ace/ext/searchbox"
if mode
ace_mode = mode
editor = ace.edit("editor")
editor.focus()
@editor = editor
if ace_mode
editor.getSession().setMode "ace/mode/" + ace_mode
disableButtonIfEmptyField "#commit_message", ".js-commit-button"
$(".js-commit-button").click ->
$("#file-content").val editor.getValue()
$(".file-editor form").submit()
return
editor: ->
return @editor
class @calendar
options =
month: "short"
day: "numeric"
year: "numeric"
constructor: (timestamps, starting_year, starting_month) ->
cal = new CalHeatMap()
cal.init
itemName: ["commit"]
data: timestamps
domain: "year"
subDomain: "month"
start: new Date(starting_year, starting_month)
domainLabelFormat: "%b"
id: "cal-heatmap"
domain: "month"
subDomain: "day"
range: 12
tooltip: true
domainDynamicDimension: false
colLimit: 4
label:
position: "top"
domainMargin: 1
legend: [
0
1
4
7
]
legendCellPadding: 3
onClick: (date, count) ->
return
return
......@@ -9,17 +9,3 @@ class @ProjectNew
initEvents: ->
disableButtonIfEmptyField '#project_name', '.project-submit'
$('#project_issues_enabled').change ->
if ($(this).is(':checked') == true)
$('#project_issues_tracker').removeAttr('disabled')
else
$('#project_issues_tracker').attr('disabled', 'disabled')
$('#project_issues_tracker').change()
$('#project_issues_tracker').change ->
if ($(this).val() == gon.default_issues_tracker || $(this).is(':disabled'))
$('#project_issues_tracker_id').attr('disabled', 'disabled')
else
$('#project_issues_tracker_id').removeAttr('disabled')
......@@ -6,7 +6,7 @@ class @ProjectShow
new Flash('Star toggle failed. Try again later.', 'alert')
$("a[data-toggle='tab']").on "shown.bs.tab", (e) ->
$.cookie "default_view", $(e.target).attr("href")
$.cookie "default_view", $(e.target).attr("href"), { expires: 30 }
defaultView = $.cookie("default_view")
if defaultView
......
......@@ -8,6 +8,7 @@
*= require select2
*= require_self
*= require dropzone/basic
*= require cal-heatmap
*/
@import "main/*";
......
......@@ -173,6 +173,11 @@
margin-right: 0px;
}
}
&.btn-lg {
font-size: 15px;
line-height: 1.4;
}
}
.btn-block {
......
.calendar_onclick_placeholder {
padding: 0 0 2px 0;
}
.calendar_commit_activity {
padding: 5px 0 0;
}
.calendar_onclick_second {
font-size: 14px;
display: block;
}
.calendar_onclick_hr {
padding: 0;
margin: 10px 0;
}
.calendar_commit_date {
color: #999;
}
.calendar_activity_summary {
font-size: 14px;
}
/**
* This overwrites the default values of the cal-heatmap gem
*/
.calendar {
.qi {
background-color: #999;
fill: #fff;
}
.q1 {
background-color: #dae289;
fill: #ededed;
}
.q2 {
background-color: #cedb9c;
fill: #ACD5F2;
}
.q3 {
background-color: #b5cf6b;
fill: #7FA8D1;
}
.q4 {
background-color: #637939;
fill: #49729B;
}
.q5 {
background-color: #3b6427;
fill: #254E77;
}
.domain-background {
fill: none;
shape-rendering: crispedges;
}
.ch-tooltip {
position: absolute;
display: none;
margin-top: 22px;
margin-left: 1px;
font-size: 13px;
padding: 3px;
font-weight: 550;
background-color: #222;
span {
position: absolute;
width: 200px;
text-align: center;
visibility: hidden;
border-radius: 10px;
&:after {
content: '';
position: absolute;
top: 100%;
left: 50%;
margin-left: -8px;
width: 0;
height: 0;
border-top: 8px solid #000000;
border-right: 8px solid transparent;
border-left: 8px solid transparent;
}
}
}
}
......@@ -10,8 +10,8 @@
border: none;
border-radius: 0;
font-family: $monospace_font;
font-size: 12px !important;
line-height: 16px !important;
font-size: $code_font_size !important;
line-height: $code_line_height !important;
margin: 0;
overflow: auto;
overflow-y: hidden;
......@@ -38,8 +38,8 @@
a {
font-family: $monospace_font;
display: block;
font-size: 12px !important;
line-height: 16px !important;
font-size: $code_font_size !important;
line-height: $code_line_height !important;
white-space: nowrap;
i {
......
/*
* Twitter bootstrap with GitLab customizations/additions
*
* Some unused bootstrap compontents like panels are not included.
* Other components like tabs are modified to GitLab style.
*
*/
$font-size-base: 13px !default;
......
......@@ -139,7 +139,7 @@
}
@mixin panel-colored {
border: none;
border: 1px solid #EEE;
background: $box_bg;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
......
......@@ -59,3 +59,5 @@ $list-font-size: 15px;
$sidebar_width: 230px;
$avatar_radius: 50%;
$code_font_size: 13px;
$code_line_height: 1.5;
......@@ -112,3 +112,7 @@
color: #FFF;
}
}
.dash-list .str-truncated {
max-width: 72%;
}
......@@ -37,7 +37,7 @@
overflow-y: hidden;
background: #FFF;
color: #333;
font-size: 12px;
font-size: $code_font_size;
.old {
span.idiff {
background-color: #F99;
......@@ -64,8 +64,8 @@
margin: 0px;
padding: 0px;
td {
line-height: 18px;
font-size: 12px;
line-height: $code_line_height;
font-size: $code_font_size;
}
}
......
......@@ -31,4 +31,26 @@
margin: 5px 8px 0 8px;
}
}
.file-title {
@extend .monospace;
font-size: 14px;
padding: 5px;
}
.editor-ref {
background: #f5f5f5;
padding: 11px 15px;
border-right: 1px solid #CCC;
display: inline-block;
margin: -5px -5px;
margin-right: 10px;
}
.editor-file-name {
.new-file-name {
display: inline-block;
width: 200px;
}
}
}
......@@ -138,9 +138,10 @@ header {
top: -1px;
padding-right: 0px !important;
img {
width: 26px;
height: 26px;
@include border-radius($avatar_radius);
width: 50px;
height: 50px;
margin: -15px;
margin-left: 5px;
}
}
......
......@@ -163,8 +163,9 @@ form.edit-issue {
}
}
.issue-title {
h3.issue-title {
margin-top: 0;
font-size: 2em;
}
.context .select2-container {
......
......@@ -122,6 +122,7 @@
background: $box_bg;
margin-bottom: 20px;
color: #666;
border: 1px solid #EEE;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
.ci_widget {
......
......@@ -16,6 +16,8 @@
.project-home-panel {
margin-bottom: 15px;
position: relative;
padding-left: 85px;
&.empty-project {
border-bottom: 0px;
......@@ -23,6 +25,22 @@
margin-bottom: 0px;
}
.project-identicon-holder {
position: absolute;
left: 0;
.avatar {
width: 70px;
height: 70px;
@include border-radius(0px);
}
.identicon {
font-size: 45px;
line-height: 1.6;
}
}
.project-home-dropdown {
margin-left: 10px;
float: right;
......
......@@ -181,7 +181,7 @@ class ApplicationController < ActionController::Base
end
def add_gon_variables
gon.default_issues_tracker = Project.issues_tracker.default_value
gon.default_issues_tracker = Project.new.default_issue_tracker.to_param
gon.api_version = API::API.version
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
......
class Projects::BaseTreeController < Projects::ApplicationController
include ExtractsPath
before_filter :authorize_download_code!
before_filter :require_non_empty_project
end
......@@ -2,7 +2,7 @@
class Projects::BlameController < Projects::ApplicationController
include ExtractsPath
# Authorize
before_filter :assign_ref_vars
before_filter :authorize_download_code!
before_filter :require_non_empty_project
......
......@@ -2,16 +2,70 @@
class Projects::BlobController < Projects::ApplicationController
include ExtractsPath
# Authorize
# Raised when given an invalid file path
class InvalidPathError < StandardError; end
before_filter :authorize_download_code!
before_filter :require_non_empty_project
before_filter :require_non_empty_project, except: [:new, :create]
before_filter :authorize_push_code!, only: [:destroy]
before_filter :assign_blob_vars
before_filter :commit, except: [:new, :create]
before_filter :blob, except: [:new, :create]
before_filter :from_merge_request, only: [:edit, :update]
before_filter :after_edit_path, only: [:edit, :update]
before_filter :require_branch_head, only: [:edit, :update]
def new
commit unless @repository.empty?
end
before_filter :blob
def create
file_path = File.join(@path, File.basename(params[:file_name]))
result = Files::CreateService.new(@project, current_user, params, @ref, file_path).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
redirect_to project_blob_path(@project, File.join(@ref, file_path))
else
flash[:alert] = result[:message]
render :new
end
end
def show
end
def edit
@last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha
end
def update
result = Files::UpdateService.
new(@project, current_user, params, @ref, @path).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
if from_merge_request
from_merge_request.reload_code
end
redirect_to after_edit_path
else
flash[:alert] = result[:message]
render :edit
end
end
def preview
@content = params[:content]
diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3',
include_diff_info: true)
@diff_lines = Gitlab::Diff::Parser.new.parse(diffy.diff.scan(/.*\n/))
render layout: false
end
def destroy
result = Files::DeleteService.new(@project, current_user, params, @ref, @path).execute
......@@ -46,10 +100,44 @@ class Projects::BlobController < Projects::ApplicationController
if @blob
@blob
elsif tree.entries.any?
redirect_to project_tree_path(@project, File.join(@ref, @path)) and return
else
if tree = @repository.tree(@commit.id, @path)
if tree.entries.any?
redirect_to project_tree_path(@project, File.join(@ref, @path)) and return
end
end
return not_found!
end
end
def commit
@commit = @repository.commit(@ref)
return not_found! unless @commit
end
def assign_blob_vars
@id = params[:id]
@ref, @path = extract_ref(@id)
rescue InvalidPathError
not_found!
end
def after_edit_path
@after_edit_path ||=
if from_merge_request
diffs_project_merge_request_path(from_merge_request.target_project, from_merge_request) +
"#file-path-#{hexdigest(@path)}"
else
project_blob_path(@project, @id)
end
end
def from_merge_request
# If blob edit was initiated from merge request page
@from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id])
end
end
......@@ -3,7 +3,7 @@ require "base64"
class Projects::CommitsController < Projects::ApplicationController
include ExtractsPath
# Authorize
before_filter :assign_ref_vars
before_filter :authorize_download_code!
before_filter :require_non_empty_project
......
class Projects::EditTreeController < Projects::BaseTreeController
before_filter :require_branch_head
before_filter :blob
before_filter :authorize_push_code!
before_filter :from_merge_request
before_filter :after_edit_path
def show
@last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha
end
def update
result = Files::UpdateService.
new(@project, current_user, params, @ref, @path).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
if from_merge_request
from_merge_request.reload_code
end
redirect_to after_edit_path
else
flash[:alert] = result[:message]
render :show
end
end
def preview
@content = params[:content]
diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3',
include_diff_info: true)
@diff_lines = Gitlab::Diff::Parser.new.parse(diffy.diff.scan(/.*\n/))
render layout: false
end
private
def blob
@blob ||= @repository.blob_at(@commit.id, @path)
end
def after_edit_path
@after_edit_path ||=
if from_merge_request
diffs_project_merge_request_path(from_merge_request.target_project, from_merge_request) +
"#file-path-#{hexdigest(@path)}"
else
project_blob_path(@project, @id)
end
end
def from_merge_request
# If blob edit was initiated from merge request page
@from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id])
end
end
......@@ -2,7 +2,7 @@ class Projects::NetworkController < Projects::ApplicationController
include ExtractsPath
include ApplicationHelper
# Authorize
before_filter :assign_ref_vars
before_filter :authorize_download_code!
before_filter :require_non_empty_project
......
class Projects::NewTreeController < Projects::BaseTreeController
before_filter :require_branch_head
before_filter :authorize_push_code!
def show
end
def update
file_path = File.join(@path, File.basename(params[:file_name]))
result = Files::CreateService.new(@project, current_user, params, @ref, file_path).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
redirect_to project_blob_path(@project, File.join(@ref, file_path))
else
flash[:alert] = result[:message]
render :show
end
end
end
......@@ -2,7 +2,7 @@
class Projects::RawController < Projects::ApplicationController
include ExtractsPath
# Authorize
before_filter :assign_ref_vars
before_filter :authorize_download_code!
before_filter :require_non_empty_project
......
class Projects::RefsController < Projects::ApplicationController
include ExtractsPath
# Authorize
before_filter :assign_ref_vars
before_filter :authorize_download_code!
before_filter :require_non_empty_project
......
......@@ -9,7 +9,7 @@ class Projects::ServicesController < Projects::ApplicationController
def index
@project.build_missing_services
@services = @project.services.reload
@services = @project.services.visible.reload
end
def edit
......@@ -17,7 +17,8 @@ class Projects::ServicesController < Projects::ApplicationController
def update
if @service.update_attributes(service_params)
redirect_to edit_project_service_path(@project, @service.to_param)
redirect_to edit_project_service_path(@project, @service.to_param),
notice: 'Successfully updated.'
else
render 'edit'
end
......@@ -45,7 +46,8 @@ class Projects::ServicesController < Projects::ApplicationController
:title, :token, :type, :active, :api_key, :subdomain,
:room, :recipients, :project_url, :webhook,
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
:build_key, :server, :teamcity_url, :build_type
:build_key, :server, :teamcity_url, :build_type,
:description, :issues_url, :new_issue_url
)
end
end
# Controller for viewing a repository's file structure
class Projects::TreeController < Projects::BaseTreeController
def show
class Projects::TreeController < Projects::ApplicationController
include ExtractsPath
before_filter :assign_ref_vars
before_filter :authorize_download_code!
before_filter :require_non_empty_project, except: [:new, :create]
def show
if tree.entries.empty?
if @repository.blob_at(@commit.id, @path)
redirect_to project_blob_path(@project, File.join(@ref, @path)) and return
......
class UsersController < ApplicationController
skip_before_filter :authenticate_user!, only: [:show]
skip_before_filter :authenticate_user!
before_filter :set_user
layout :determine_layout
def show
@user = User.find_by_username!(params[:username])
unless current_user || @user.public_profile?
return authenticate_user!
end
# Projects user can view
authorized_projects_ids = ProjectsFinder.new.execute(current_user).pluck(:id)
visible_projects = ProjectsFinder.new.execute(current_user)
authorized_projects_ids = visible_projects.pluck(:id)
@projects = @user.personal_projects.
where(id: authorized_projects_ids)
......@@ -30,6 +26,19 @@ class UsersController < ApplicationController
end
end
def calendar
visible_projects = ProjectsFinder.new.execute(current_user)
# Get user repositories and collect timestamps for commits
user_repositories = visible_projects.map(&:repository)
calendar = Gitlab::CommitsCalendar.new(user_repositories, @user)
@timestamps = calendar.timestamps
@starting_year = (Time.now - 1.year).strftime("%Y")
@starting_month = Date.today.strftime("%m").to_i
render 'calendar', layout: false
end
def determine_layout
if current_user
'navless'
......@@ -37,4 +46,14 @@ class UsersController < ApplicationController
'public_users'
end
end
private
def set_user
@user = User.find_by_username!(params[:username])
unless current_user || @user.public_profile?
return authenticate_user!
end
end
end
......@@ -56,8 +56,6 @@ module ApplicationHelper
image_tag project.avatar.url, options
elsif project.avatar_in_git
image_tag project_avatar_path(project), options
elsif options[:only_uploaded]
image_tag '/assets/no_project_icon.png', options
else # generated icon
project_identicon(project, options)
end
......
......@@ -19,4 +19,42 @@ module BlobHelper
def no_highlight_files
%w(credits changelog copying copyright license authors)
end
def edit_blob_link(project, ref, path, options = {})
blob =
begin
project.repository.blob_at(ref, path)
rescue
nil
end
if blob && blob.text?
text = 'Edit'
after = options[:after] || ''
from_mr = options[:from_merge_request_id]
link_opts = {}
link_opts[:from_merge_request_id] = from_mr if from_mr
cls = 'btn btn-small'
if allowed_tree_edit?(project, ref)
link_to text, project_edit_blob_path(project, tree_join(ref, path),
link_opts), class: cls
else
content_tag :span, text, class: cls + ' disabled'
end + after.html_safe
else
''
end
end
def leave_edit_message
"Leave edit mode?\nAll unsaved changes will be lost."
end
def editing_preview_title(filename)
if Gitlab::MarkdownHelper.previewable?(filename)
'Preview'
else
'Preview changes'
end
end
end
......@@ -16,45 +16,25 @@ module IssuesHelper
def url_for_project_issues(project = @project)
return '' if project.nil?
if project.used_default_issues_tracker? || !external_issues_tracker_enabled?
project_issues_path(project)
else
url = Gitlab.config.issues_tracker[project.issues_tracker]['project_url']
url.gsub(':project_id', project.id.to_s).
gsub(':issues_tracker_id', project.issues_tracker_id.to_s)
end
project.issues_tracker.project_url
end
def url_for_new_issue(project = @project)
return '' if project.nil?
if project.used_default_issues_tracker? || !external_issues_tracker_enabled?
url = new_project_issue_path project_id: project
else
issues_tracker = Gitlab.config.issues_tracker[project.issues_tracker]
url = issues_tracker['new_issue_url']
url.gsub(':project_id', project.id.to_s).
gsub(':issues_tracker_id', project.issues_tracker_id.to_s)
end
project.issues_tracker.new_issue_url
end
def url_for_issue(issue_iid, project = @project)
return '' if project.nil?
if project.used_default_issues_tracker? || !external_issues_tracker_enabled?
url = project_issue_url project_id: project, id: issue_iid
else
url = Gitlab.config.issues_tracker[project.issues_tracker]['issues_url']
url.gsub(':id', issue_iid.to_s).
gsub(':project_id', project.id.to_s).
gsub(':issues_tracker_id', project.issues_tracker_id.to_s)
end
project.issues_tracker.issue_url(issue_iid)
end
def title_for_issue(issue_iid, project = @project)
return '' if project.nil?
if project.used_default_issues_tracker?
if project.default_issues_tracker?
issue = project.issues.where(iid: issue_iid).first
return issue.title if issue
end
......@@ -77,11 +57,6 @@ module IssuesHelper
ts.html_safe
end
# Checks if issues_tracker setting exists in gitlab.yml
def external_issues_tracker_enabled?
Gitlab.config.issues_tracker && Gitlab.config.issues_tracker.values.any?
end
def bulk_update_milestone_options
options_for_select(['None (backlog)']) +
options_from_collection_for_select(project_active_milestones, 'id',
......
......@@ -72,18 +72,6 @@ module ProjectsHelper
@project.milestones.active.order("due_date, title ASC")
end
def project_issues_trackers(current_tracker = nil)
values = Project.issues_tracker.values.map do |tracker_key|
if tracker_key.to_sym == :gitlab
['GitLab', tracker_key]
else
[Gitlab.config.issues_tracker[tracker_key]['title'] || tracker_key, tracker_key]
end
end
options_for_select(values, current_tracker)
end
def link_to_toggle_star(title, starred, signed_in)
cls = 'star-btn'
cls << ' disabled' unless signed_in
......@@ -187,7 +175,13 @@ module ProjectsHelper
"Issues - " + title
end
elsif current_controller?(:blob)
"#{@project.path}\/#{@blob.path} at #{@ref} - " + title
if current_action?(:new) || current_action?(:create)
"New file at #{@ref}"
elsif current_action?(:show)
"#{@blob.path} at #{@ref}"
elsif @blob
"Edit file #{@blob.path} at #{@ref}"
end
elsif current_controller?(:commits)
"Commits at #{@ref} - " + title
elsif current_controller?(:merge_requests)
......@@ -257,7 +251,7 @@ module ProjectsHelper
end
def github_import_enabled?
Gitlab.config.omniauth.enabled && enabled_oauth_providers.include?(:github)
enabled_oauth_providers.include?(:github)
end
end
......@@ -64,32 +64,6 @@ module TreeHelper
::Gitlab::GitAccess.can_push_to_branch?(current_user, project, ref)
end
def edit_blob_link(project, ref, path, options = {})
blob =
begin
project.repository.blob_at(ref, path)
rescue
nil
end
if blob && blob.text?
text = 'Edit'
after = options[:after] || ''
from_mr = options[:from_merge_request_id]
link_opts = {}
link_opts[:from_merge_request_id] = from_mr if from_mr
cls = 'btn btn-small'
if allowed_tree_edit?(project, ref)
link_to text, project_edit_tree_path(project, tree_join(ref, path),
link_opts), class: cls
else
content_tag :span, text, class: cls + ' disabled'
end + after.html_safe
else
''
end
end
def tree_breadcrumbs(tree, max_links = 2)
if @path.present?
part_path = ""
......@@ -121,16 +95,4 @@ module TreeHelper
return tree.name
end
end
def leave_edit_message
"Leave edit mode?\nAll unsaved changes will be lost."
end
def editing_preview_title(filename)
if Gitlab::MarkdownHelper.previewable?(filename)
'Preview'
else
'Diff'
end
end
end
......@@ -25,6 +25,9 @@ class Group < Namespace
mount_uploader :avatar, AttachmentUploader
after_create :post_create_hook
after_destroy :post_destroy_hook
def human_name
name
end
......@@ -74,6 +77,18 @@ class Group < Namespace
projects.public_only.any?
end
def post_create_hook
system_hook_service.execute_hooks_for(self, :create)
end
def post_destroy_hook
system_hook_service.execute_hooks_for(self, :destroy)
end
def system_hook_service
SystemHooksService.new
end
class << self
def search(query)
where("LOWER(namespaces.name) LIKE :query", query: "%#{query.downcase}%")
......
......@@ -27,8 +27,9 @@ class GroupMember < Member
scope :with_group, ->(group) { where(source_id: group.id) }
scope :with_user, ->(user) { where(user_id: user.id) }
after_create :notify_create
after_create :post_create_hook
after_update :notify_update
after_destroy :post_destroy_hook
def self.access_level_roles
Gitlab::Access.options_with_owner
......@@ -42,8 +43,9 @@ class GroupMember < Member
access_level
end
def notify_create
def post_create_hook
notification_service.new_group_member(self)
system_hook_service.execute_hooks_for(self, :create)
end
def notify_update
......@@ -52,6 +54,14 @@ class GroupMember < Member
end
end
def post_destroy_hook
system_hook_service.execute_hooks_for(self, :destroy)
end
def system_hook_service
SystemHooksService.new
end
def notification_service
NotificationService.new
end
......
......@@ -74,7 +74,13 @@ class Project < ActiveRecord::Base
has_one :bamboo_service, dependent: :destroy
has_one :teamcity_service, dependent: :destroy
has_one :pushover_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: 'forked_to_project_id'
has_one :jira_service, dependent: :destroy
has_one :redmine_service, dependent: :destroy
has_one :custom_issue_tracker_service, dependent: :destroy
has_one :gitlab_issue_tracker_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link
# Merge Requests for target project should be removed with it
has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id'
......@@ -144,8 +150,6 @@ class Project < ActiveRecord::Base
scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) }
scope :non_archived, -> { where(archived: false) }
enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab
state_machine :import_status, initial: :none do
event :import_start do
transition [:none, :finished] => :started
......@@ -305,19 +309,43 @@ class Project < ActiveRecord::Base
end
def issue_exists?(issue_id)
if used_default_issues_tracker?
if default_issues_tracker?
self.issues.where(iid: issue_id).first.present?
else
true
end
end
def used_default_issues_tracker?
self.issues_tracker == Project.issues_tracker.default_value
def default_issue_tracker
gitlab_issue_tracker_service ||= create_gitlab_issue_tracker_service
end
def issues_tracker
if external_issue_tracker
external_issue_tracker
else
default_issue_tracker
end
end
def default_issues_tracker?
if external_issue_tracker
false
else
true
end
end
def external_issues_trackers
services.select(&:issue_tracker?).reject(&:default?)
end
def external_issue_tracker
@external_issues_tracker ||= external_issues_trackers.select(&:activated?).first
end
def can_have_issues_tracker_id?
self.issues_enabled && !self.used_default_issues_tracker?
self.issues_enabled && !self.default_issues_tracker?
end
def build_missing_services
......@@ -332,7 +360,7 @@ class Project < ActiveRecord::Base
def available_services_names
%w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla
emails_on_push gemnasium slack pushover buildbox bamboo teamcity)
emails_on_push gemnasium slack pushover buildbox bamboo teamcity jira redmine custom_issue_tracker)
end
def gitlab_ci?
......
class CustomIssueTrackerService < IssueTrackerService
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
def title
if self.properties && self.properties['title'].present?
self.properties['title']
else
'Custom Issue Tracker'
end
end
def description
if self.properties && self.properties['description'].present?
self.properties['description']
else
'Custom issue tracker'
end
end
def to_param
'custom_issue_tracker'
end
def fields
[
{ type: 'text', name: 'title', placeholder: title },
{ type: 'text', name: 'description', placeholder: description },
{ type: 'text', name: 'project_url', placeholder: 'Project url' },
{ type: 'text', name: 'issues_url', placeholder: 'Issue url'},
{ type: 'text', name: 'new_issue_url', placeholder: 'New Issue url'}
]
end
def initialize_properties
self.properties = {} if properties.nil?
end
end
class GitlabIssueTrackerService < IssueTrackerService
include Rails.application.routes.url_helpers
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
def default?
true
end
def to_param
'gitlab'
end
def project_url
project_issues_path(project)
end
def new_issue_url
new_project_issue_path project_id: project
end
def issue_url(iid)
"#{Gitlab.config.gitlab.url}#{project_issue_path(project_id: project, id: iid)}"
end
end
class IssueTrackerService < Service
validates :project_url, :issues_url, :new_issue_url, presence: true, if: :activated?
def category
:issue_tracker
end
def default?
false
end
def project_url
# implement inside child
end
def issues_url
# implement inside child
end
def new_issue_url
# implement inside child
end
def issue_url(iid)
self.issues_url.gsub(':id', iid.to_s)
end
def fields
[
{ type: 'text', name: 'description', placeholder: description },
{ type: 'text', name: 'project_url', placeholder: 'Project url' },
{ type: 'text', name: 'issues_url', placeholder: 'Issue url'},
{ type: 'text', name: 'new_issue_url', placeholder: 'New Issue url'}
]
end
def initialize_properties
if properties.nil?
if enabled_in_gitlab_config
self.properties = {
title: issues_tracker['title'],
project_url: set_project_url,
issues_url: issues_tracker['issues_url'],
new_issue_url: issues_tracker['new_issue_url']
}
else
self.properties = {}
end
end
end
private
def enabled_in_gitlab_config
Gitlab.config.issues_tracker &&
Gitlab.config.issues_tracker.values.any? &&
issues_tracker
end
def issues_tracker
Gitlab.config.issues_tracker[to_param]
end
def set_project_url
id = self.project.issues_tracker_id
if id
issues_tracker['project_url'].gsub(":issues_tracker_id", id)
else
issues_tracker['project_url']
end
end
end
class JiraService < IssueTrackerService
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
def title
if self.properties && self.properties['title'].present?
self.properties['title']
else
'JIRA'
end
end
def description
if self.properties && self.properties['description'].present?
self.properties['description']
else
'Jira issue tracker'
end
end
def to_param
'jira'
end
end
class RedmineService < IssueTrackerService
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
def title
if self.properties && self.properties['title'].present?
self.properties['title']
else
'Redmine'
end
end
def description
if self.properties && self.properties['description'].present?
self.properties['description']
else
'Redmine issue tracker'
end
end
def to_param
'redmine'
end
end
......@@ -139,8 +139,10 @@ class Repository
def graph_log
Rails.cache.fetch(cache_key(:graph_log)) do
commits = raw_repository.log(limit: 6000, skip_merges: true,
commits = raw_repository.log(limit: 6000,
skip_merges: true,
ref: root_ref)
commits.map do |rugged_commit|
commit = Gitlab::Git::Commit.new(rugged_commit)
......@@ -148,12 +150,32 @@ class Repository
author_name: commit.author_name.force_encoding('UTF-8'),
author_email: commit.author_email.force_encoding('UTF-8'),
additions: commit.stats.additions,
deletions: commit.stats.deletions
deletions: commit.stats.deletions,
}
end
end
end
def timestamps_by_user_log(user)
args = %W(git log --author=#{user.email} --since=#{(Date.today - 1.year).to_s} --pretty=format:%cd --date=short)
dates = Gitlab::Popen.popen(args, path_to_repo).first.split("\n")
if dates.present?
dates
else
[]
end
end
def commits_per_day_for_user(user)
timestamps_by_user_log(user).
group_by { |commit_date| commit_date }.
inject({}) do |hash, (timestamp_date, commits)|
hash[timestamp_date] = commits.count
hash
end
end
def cache_key(type)
"#{type}:#{path_with_namespace}"
end
......
......@@ -26,6 +26,8 @@ class Service < ActiveRecord::Base
validates :project_id, presence: true
scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') }
def activated?
active
end
......@@ -86,4 +88,12 @@ class Service < ActiveRecord::Base
def async_execute(data)
Sidekiq::Client.enqueue(ProjectServiceWorker, id, data)
end
def issue_tracker?
self.category == :issue_tracker
end
def self.issue_tracker_service_list
Service.select(&:issue_tracker?).map{ |s| s.to_param }
end
end
......@@ -9,10 +9,6 @@ module Files
return error("You are not allowed to create file in this branch")
end
unless repository.branch_names.include?(ref)
return error("You can only create files if you are on top of a branch")
end
file_name = File.basename(path)
file_path = path
......@@ -23,12 +19,21 @@ module Files
)
end
blob = repository.blob_at_branch(ref, file_path)
if project.empty_repo?
# everything is ok because repo does not have a commits yet
else
unless repository.branch_names.include?(ref)
return error("You can only create files if you are on top of a branch")
end
if blob
return error("Your changes could not be committed, because file with such name exists")
blob = repository.blob_at_branch(ref, file_path)
if blob
return error("Your changes could not be committed, because file with such name exists")
end
end
new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, file_path)
created_successfully = new_file_action.commit!(
params[:content],
......
......@@ -2,9 +2,9 @@ module Issues
class CloseService < Issues::BaseService
def execute(issue, commit = nil)
if issue.close
notification_service.close_issue(issue, current_user)
event_service.close_issue(issue, current_user)
create_note(issue, commit)
notification_service.close_issue(issue, current_user)
execute_hooks(issue, 'close')
end
......
......@@ -23,8 +23,8 @@ module Issues
end
if issue.previous_changes.include?('assignee_id')
notification_service.reassigned_issue(issue, current_user)
create_assignee_note(issue)
notification_service.reassigned_issue(issue, current_user)
end
issue.notice_added_references(issue.project, current_user)
......
......@@ -11,9 +11,9 @@ module MergeRequests
if Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message)
merge_request.merge
notification_service.merge_mr(merge_request, current_user)
create_merge_event(merge_request, current_user)
create_note(merge_request)
notification_service.merge_mr(merge_request, current_user)
execute_hooks(merge_request)
true
......
......@@ -7,8 +7,8 @@ module MergeRequests
if merge_request.close
event_service.close_mr(merge_request, current_user)
notification_service.close_mr(merge_request, current_user)
create_note(merge_request)
notification_service.close_mr(merge_request, current_user)
execute_hooks(merge_request, 'close')
end
......
......@@ -9,9 +9,9 @@ module MergeRequests
def execute(merge_request, commit_message)
merge_request.merge
notification_service.merge_mr(merge_request, current_user)
create_merge_event(merge_request, current_user)
create_note(merge_request)
notification_service.merge_mr(merge_request, current_user)
execute_hooks(merge_request, 'merge')
true
......
......@@ -3,8 +3,8 @@ module MergeRequests
def execute(merge_request)
if merge_request.reopen
event_service.reopen_mr(merge_request, current_user)
notification_service.reopen_mr(merge_request, current_user)
create_note(merge_request)
notification_service.reopen_mr(merge_request, current_user)
execute_hooks(merge_request, 'reopen')
merge_request.reload_code
merge_request.mark_as_unchecked
......
......@@ -33,8 +33,8 @@ module MergeRequests
end
if merge_request.previous_changes.include?('assignee_id')
notification_service.reassigned_merge_request(merge_request, current_user)
create_assignee_note(merge_request)
notification_service.reassigned_merge_request(merge_request, current_user)
end
merge_request.notice_added_references(merge_request.project, current_user)
......
......@@ -314,15 +314,7 @@ class NotificationService
end
def new_resource_email(target, project, method)
if target.respond_to?(:participants)
recipients = target.participants
else
recipients = []
end
recipients = reject_muted_users(recipients, project)
recipients = reject_mention_users(recipients, project)
recipients = recipients.concat(project_watchers(project)).uniq
recipients = build_recipients(target, project)
recipients.delete(target.author)
recipients.each do |recipient|
......@@ -331,9 +323,7 @@ class NotificationService
end
def close_resource_email(target, project, current_user, method)
recipients = reject_muted_users([target.author, target.assignee], project)
recipients = reject_mention_users(recipients, project)
recipients = recipients.concat(project_watchers(project)).uniq
recipients = build_recipients(target, project)
recipients.delete(current_user)
recipients.each do |recipient|
......@@ -343,17 +333,7 @@ class NotificationService
def reassign_resource_email(target, project, current_user, method)
assignee_id_was = previous_record(target, "assignee_id")
recipients = User.where(id: [target.assignee_id, assignee_id_was])
# Add watchers to email list
recipients = recipients.concat(project_watchers(project))
# reject users with disabled notifications
recipients = reject_muted_users(recipients, project)
recipients = reject_mention_users(recipients, project)
# Reject me from recipients if I reassign an item
recipients = build_recipients(target, project)
recipients.delete(current_user)
recipients.each do |recipient|
......@@ -362,9 +342,7 @@ class NotificationService
end
def reopen_resource_email(target, project, current_user, method, status)
recipients = reject_muted_users([target.author, target.assignee], project)
recipients = reject_mention_users(recipients, project)
recipients = recipients.concat(project_watchers(project)).uniq
recipients = build_recipients(target, project)
recipients.delete(current_user)
recipients.each do |recipient|
......@@ -372,6 +350,20 @@ class NotificationService
end
end
def build_recipients(target, project)
recipients =
if target.respond_to?(:participants)
target.participants
else
[target.author, target.assignee]
end
recipients = reject_muted_users(recipients, project)
recipients = reject_mention_users(recipients, project)
recipients = recipients.concat(project_watchers(project)).uniq
recipients
end
def mailer
Notify.delay
end
......
......@@ -60,6 +60,26 @@ class SystemHooksService
access_level: model.human_access,
project_visibility: Project.visibility_levels.key(model.project.visibility_level_field).downcase
})
when Group
owner = model.owner
data.merge!(
name: model.name,
path: model.path,
group_id: model.id,
owner_name: owner.respond_to?(:name) ? owner.name : nil,
owner_email: owner.respond_to?(:email) ? owner.email : nil,
)
when GroupMember
data.merge!(
group_name: model.group.name,
group_path: model.group.path,
group_id: model.group.id,
user_name: model.user.name,
user_email: model.user.email,
user_id: model.user.id,
group_access: model.human_access,
)
end
end
......@@ -68,6 +88,9 @@ class SystemHooksService
when ProjectMember
return "user_add_to_team" if event == :create
return "user_remove_from_team" if event == :destroy
when GroupMember
return 'user_add_to_group' if event == :create
return 'user_remove_from_group' if event == :destroy
else
"#{model.class.name.downcase}_#{event.to_s}"
end
......
......@@ -32,7 +32,7 @@
%span.light.pull-right
= Milestone.count
%p
Active users last 30 days
Users who signed in during last 30 days
%span.light.pull-right
= User.where("current_sign_in_at > ?", 30.days.ago).count
.col-md-4
......
......@@ -11,5 +11,4 @@
- elsif event.note?
= render "events/event/note", event: event
- else
= render "events/event/common", event: event
= render "events/event/common", event: event
\ No newline at end of file
......@@ -5,7 +5,11 @@
.form-group
= f.label :access_level, "Group Access", class: 'control-label'
.col-sm-10= select_tag :access_level, options_for_select(GroupMember.access_level_roles, @users_group.access_level), class: "project-access-select select2"
.col-sm-10
= select_tag :access_level, options_for_select(GroupMember.access_level_roles, @users_group.access_level), class: "project-access-select select2"
.help-block
Read more about role permissions
%strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
.form-actions
= f.submit 'Add users into group', class: "btn btn-create"
%ul.sidebar-subnav
= nav_link(path: 'groups#edit') do
= link_to edit_group_path(@group) do
= link_to edit_group_path(@group), title: 'Group' do
%i.fa.fa-pencil-square-o
%span
Group
= nav_link(path: 'groups#projects') do
= link_to projects_group_path(@group) do
= link_to projects_group_path(@group), title: 'Projects' do
%i.fa.fa-folder
%span
Projects
......@@ -43,6 +43,6 @@
%i.fa.fa-sign-out
%li.hidden-xs
= link_to current_user, class: "profile-pic", id: 'profile-pic' do
= image_tag avatar_icon(current_user.email, 26), alt: 'User activity'
= image_tag avatar_icon(current_user.email, 60), alt: 'User activity'
= render 'shared/outdated_browser'
- if defined?(sidebar)
.page-with-sidebar
= render "layouts/broadcast"
.sidebar-wrapper
= render(sidebar)
.content-wrapper
......
......@@ -2,6 +2,5 @@
%html{ lang: "en"}
= render "layouts/head", title: "Admin area"
%body{class: "#{app_theme} #{theme_type} admin", :'data-page' => body_data_page}
= render "layouts/broadcast"
= render "layouts/head_panel", title: "Admin area"
= render 'layouts/page', sidebar: 'layouts/nav/admin'
......@@ -2,6 +2,5 @@
%html{ lang: "en"}
= render "layouts/head", title: "Dashboard"
%body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page }
= render "layouts/broadcast"
= render "layouts/head_panel", title: "Dashboard"
= render 'layouts/page', sidebar: 'layouts/nav/dashboard'
......@@ -2,6 +2,5 @@
%html{ lang: "en"}
= render "layouts/head", title: group_head_title
%body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page}
= render "layouts/broadcast"
= render "layouts/head_panel", title: @group.name
= render 'layouts/page', sidebar: 'layouts/nav/group'
......@@ -5,49 +5,50 @@
%span
Overview
= nav_link(controller: :projects) do
= link_to admin_projects_path do
= link_to admin_projects_path, title: 'Projects' do
%i.fa.fa-cube
%span
Projects
= nav_link(controller: :users) do
= link_to admin_users_path do
= link_to admin_users_path, title: 'Users' do
%i.fa.fa-user
%span
Users
= nav_link(controller: :groups) do
= link_to admin_groups_path do
= link_to admin_groups_path, title: 'Groups' do
%i.fa.fa-group
%span
Groups
= nav_link(controller: :logs) do
= link_to admin_logs_path do
= link_to admin_logs_path, title: 'Logs' do
%i.fa.fa-file-text
%span
Logs
= nav_link(controller: :broadcast_messages) do
= link_to admin_broadcast_messages_path do
= link_to admin_broadcast_messages_path, title: 'Broadcast Messages' do
%i.fa.fa-bullhorn
%span
Messages
= nav_link(controller: :hooks) do
= link_to admin_hooks_path do
= link_to admin_hooks_path, title: 'Hooks' do
%i.fa.fa-external-link
%span
Hooks
= nav_link(controller: :background_jobs) do
= link_to admin_background_jobs_path do
= link_to admin_background_jobs_path, title: 'Background Jobs' do
%i.fa.fa-cog
%span
Background Jobs
= nav_link(controller: :application_settings) do
= link_to admin_application_settings_path do
%i.fa.fa-cogs
%span
Settings
= nav_link(controller: :applications) do
= link_to admin_applications_path do
= link_to admin_applications_path, title: 'Applications' do
%i.fa.fa-cloud
%span
Applications
= nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
= link_to admin_application_settings_path, title: 'Settings' do
%i.fa.fa-cogs
%span
Settings
......@@ -5,24 +5,24 @@
%span
Activity
= nav_link(path: 'dashboard#projects') do
= link_to projects_dashboard_path, class: 'shortcuts-projects' do
= link_to projects_dashboard_path, title: 'Projects', class: 'shortcuts-projects' do
%i.fa.fa-cube
%span
Projects
= nav_link(path: 'dashboard#issues') do
= link_to assigned_issues_dashboard_path, class: 'shortcuts-issues' do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues' do
%i.fa.fa-exclamation-circle
%span
Issues
%span.count= current_user.assigned_issues.opened.count
= nav_link(path: 'dashboard#merge_requests') do
= link_to assigned_mrs_dashboard_path, class: 'shortcuts-merge_requests' do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests' do
%i.fa.fa-tasks
%span
Merge Requests
%span.count= current_user.assigned_merge_requests.opened.count
= nav_link(controller: :help) do
= link_to help_path do
= link_to help_path, title: 'Help' do
%i.fa.fa-question-circle
%span
Help
......
......@@ -6,33 +6,33 @@
Activity
- if current_user
= nav_link(controller: [:group, :milestones]) do
= link_to group_milestones_path(@group) do
= link_to group_milestones_path(@group), title: 'Milestones' do
%i.fa.fa-clock-o
%span
Milestones
= nav_link(path: 'groups#issues') do
= link_to issues_group_path(@group) do
= link_to issues_group_path(@group), title: 'Issues' do
%i.fa.fa-exclamation-circle
%span
Issues
- if current_user
%span.count= Issue.opened.of_group(@group).count
= nav_link(path: 'groups#merge_requests') do
= link_to merge_requests_group_path(@group) do
= link_to merge_requests_group_path(@group), title: 'Merge Requests' do
%i.fa.fa-tasks
%span
Merge Requests
- if current_user
%span.count= MergeRequest.opened.of_group(@group).count
= nav_link(path: 'groups#members') do
= link_to members_group_path(@group) do
= link_to members_group_path(@group), title: 'Members' do
%i.fa.fa-users
%span
Members
- if can?(current_user, :manage_group, @group)
= nav_link(html_options: { class: "#{"active" if group_settings_page?} separate-item" }) do
= link_to edit_group_path(@group), class: "tab no-highlight" do
= link_to edit_group_path(@group), title: 'Settings', class: "tab no-highlight" do
%i.fa.fa-cogs
%span
Settings
......
......@@ -5,52 +5,51 @@
%span
Profile
= nav_link(controller: :accounts) do
= link_to profile_account_path do
= link_to profile_account_path, title: 'Account' do
%i.fa.fa-gear
%span
Account
= nav_link(path: ['profiles#applications', 'applications#edit', 'applications#show', 'applications#new']) do
= link_to applications_profile_path do
= link_to applications_profile_path, title: 'Applications' do
%i.fa.fa-cloud
%span
Applications
= nav_link(controller: :emails) do
= link_to profile_emails_path do
= link_to profile_emails_path, title: 'Emails' do
%i.fa.fa-envelope-o
%span
Emails
%span.count= current_user.emails.count + 1
- unless current_user.ldap_user?
= nav_link(controller: :passwords) do
= link_to edit_profile_password_path do
= link_to edit_profile_password_path, title: 'Password' do
%i.fa.fa-lock
%span
Password
= nav_link(controller: :notifications) do
= link_to profile_notifications_path do
= link_to profile_notifications_path, title: 'Notifications' do
%i.fa.fa-inbox
%span
Notifications
= nav_link(controller: :keys) do
= link_to profile_keys_path do
= link_to profile_keys_path, title: 'SSH Keys' do
%i.fa.fa-key
%span
SSH Keys
%span.count= current_user.keys.count
= nav_link(path: 'profiles#design') do
= link_to design_profile_path do
= link_to design_profile_path, title: 'Design' do
%i.fa.fa-image
%span
Design
= nav_link(controller: :groups) do
= link_to profile_groups_path do
= link_to profile_groups_path, title: 'Groups' do
%i.fa.fa-group
%span
Groups
= nav_link(path: 'profiles#history') do
= link_to history_profile_path do
= link_to history_profile_path, title: 'History' do
%i.fa.fa-history
%span
History
......@@ -6,45 +6,44 @@
Project
- if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree)) do
= link_to project_tree_path(@project, @ref || @repository.root_ref), class: 'shortcuts-tree' do
= link_to project_tree_path(@project, @ref || @repository.root_ref), title: 'Files', class: 'shortcuts-tree' do
%i.fa.fa-files-o
%span
Files
- if project_nav_tab? :commits
= nav_link(controller: %w(commit commits compare repositories tags branches)) do
= link_to project_commits_path(@project, @ref || @repository.root_ref), class: 'shortcuts-commits' do
= link_to project_commits_path(@project, @ref || @repository.root_ref), title: 'Commits', class: 'shortcuts-commits' do
%i.fa.fa-history
%span
Commits
- if project_nav_tab? :network
= nav_link(controller: %w(network)) do
= link_to project_network_path(@project, @ref || @repository.root_ref), class: 'shortcuts-network' do
= link_to project_network_path(@project, @ref || @repository.root_ref), title: 'Network', class: 'shortcuts-network' do
%i.fa.fa-code-fork
%span
Network
- if project_nav_tab? :graphs
= nav_link(controller: %w(graphs)) do
= link_to project_graph_path(@project, @ref || @repository.root_ref), class: 'shortcuts-graphs' do
= link_to project_graph_path(@project, @ref || @repository.root_ref), title: 'Graphs', class: 'shortcuts-graphs' do
%i.fa.fa-area-chart
%span
Graphs
- if project_nav_tab? :issues
= nav_link(controller: %w(issues milestones labels)) do
= link_to url_for_project_issues, class: 'shortcuts-issues' do
= link_to url_for_project_issues, title: 'Issues', class: 'shortcuts-issues' do
%i.fa.fa-exclamation-circle
%span
Issues
- if @project.used_default_issues_tracker?
- if @project.default_issues_tracker?
%span.count.issue_counter= @project.issues.opened.count
- if project_nav_tab? :merge_requests
= nav_link(controller: :merge_requests) do
= link_to project_merge_requests_path(@project), class: 'shortcuts-merge_requests' do
= link_to project_merge_requests_path(@project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
%i.fa.fa-tasks
%span
Merge Requests
......@@ -52,21 +51,21 @@
- if project_nav_tab? :wiki
= nav_link(controller: :wikis) do
= link_to project_wiki_path(@project, :home), class: 'shortcuts-wiki' do
= link_to project_wiki_path(@project, :home), title: 'Wiki', class: 'shortcuts-wiki' do
%i.fa.fa-book
%span
Wiki
- if project_nav_tab? :snippets
= nav_link(controller: :snippets) do
= link_to project_snippets_path(@project), class: 'shortcuts-snippets' do
= link_to project_snippets_path(@project), title: 'Snippets', class: 'shortcuts-snippets' do
%i.fa.fa-file-text-o
%span
Snippets
- if project_nav_tab? :settings
= nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do
= link_to edit_project_path(@project), class: "stat-tab tab no-highlight" do
= link_to edit_project_path(@project), title: 'Settings', class: "stat-tab tab no-highlight" do
%i.fa.fa-cogs
%span
Settings
......
......@@ -2,6 +2,5 @@
%html{ lang: "en"}
= render "layouts/head", title: "Profile"
%body{class: "#{app_theme} #{theme_type} profile", :'data-page' => body_data_page}
= render "layouts/broadcast"
= render "layouts/head_panel", title: "Profile"
= render 'layouts/page', sidebar: 'layouts/nav/profile'
......@@ -2,7 +2,6 @@
%html{ lang: "en"}
= render "layouts/head", title: @project.name_with_namespace
%body{class: "#{app_theme} #{theme_type} project", :'data-page' => body_data_page, :'data-project-id' => @project.id }
= render "layouts/broadcast"
= render "layouts/head_panel", title: project_title(@project)
= render "layouts/init_auto_complete"
- @project_settings_nav = true
......
......@@ -2,7 +2,6 @@
%html{ lang: "en"}
= render "layouts/head", title: project_head_title
%body{class: "#{app_theme} #{theme_type} project", :'data-page' => body_data_page, :'data-project-id' => @project.id }
= render "layouts/broadcast"
= render "layouts/head_panel", title: project_title(@project)
= render "layouts/init_auto_complete"
= render 'layouts/page', sidebar: 'layouts/nav/project'
......@@ -2,6 +2,5 @@
%html{ lang: "en"}
= render "layouts/head", title: group_head_title
%body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page}
= render "layouts/broadcast"
= render "layouts/public_head_panel", title: "group: #{@group.name}"
= render 'layouts/page', sidebar: 'layouts/nav/group'
......@@ -2,6 +2,5 @@
%html{ lang: "en"}
= render "layouts/head", title: @project.name_with_namespace
%body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page}
= render "layouts/broadcast"
= render "layouts/public_head_panel", title: project_title(@project)
= render 'layouts/page', sidebar: 'layouts/nav/project'
......@@ -2,6 +2,5 @@
%html{ lang: "en"}
= render "layouts/head", title: @title
%body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page}
= render "layouts/broadcast"
= render "layouts/public_head_panel", title: @title
= render 'layouts/page'
- empty_repo = @project.empty_repo?
.project-home-panel{:class => ("empty-project" if empty_repo)}
.project-identicon-holder
= project_icon(@project.to_param, alt: '', class: 'avatar')
.project-home-row
.project-home-desc
= project_icon(@project.to_param, alt: '', class: 'avatar s32')
- if @project.description.present?
= escaped_autolink(@project.description)
- if can?(current_user, :admin_project, @project)
......
%ul.project-settings-nav.sidebar-subnav
= nav_link(path: 'projects#edit') do
= link_to edit_project_path(@project), class: "stat-tab tab " do
= link_to edit_project_path(@project), title: 'Project', class: "stat-tab tab " do
%i.fa.fa-pencil-square-o
%span
Project
= nav_link(controller: [:team_members, :teams]) do
= link_to project_team_index_path(@project), class: "team-tab tab" do
= link_to project_team_index_path(@project), title: 'Members', class: "team-tab tab" do
%i.fa.fa-users
%span
Members
= nav_link(controller: :deploy_keys) do
= link_to project_deploy_keys_path(@project) do
= link_to project_deploy_keys_path(@project), title: 'Deploy Keys' do
%i.fa.fa-key
%span
Deploy Keys
= nav_link(controller: :hooks) do
= link_to project_hooks_path(@project) do
= link_to project_hooks_path(@project), title: 'Web Hooks' do
%i.fa.fa-link
%span
Web Hooks
= nav_link(controller: :services) do
= link_to project_services_path(@project) do
= link_to project_services_path(@project), title: 'Services' do
%i.fa.fa-cogs
%span
Services
= nav_link(controller: :protected_branches) do
= link_to project_protected_branches_path(@project) do
= link_to project_protected_branches_path(@project), title: 'Protected Branches' do
%i.fa.fa-lock
%span
Protected branches
.file-holder.file
.file-title
%i.icon-file
%span.file_name
%span.monospace.light #{ref}
- if local_assigns[:path]
= ': ' + local_assigns[:path]
.editor-ref
%i.fa.fa-code-fork
= ref
%span.editor-file-name
- if @path
%span.monospace
= @path
- if current_action?(:new) || current_action?(:create)
\/
= text_field_tag 'file_name', params[:file_name], placeholder: "File name",
required: true, class: 'form-control new-file-name'
.pull-right
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
.file-content.code
%pre.js-edit-mode-pane#editor
= params[:content] || local_assigns[:blob_data]
......
.file-editor
%ul.nav.nav-tabs.js-edit-mode
%li.active
= link_to '#editor' do
%i.fa.fa-edit
Edit file
%li
= link_to '#preview', 'data-preview-url' => project_preview_blob_path(@project, @id) do
%i.fa.fa-eye
= editing_preview_title(@blob.name)
= form_tag(project_update_blob_path(@project, @id), method: :put, class: "form-horizontal") do
= render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
= render 'shared/commit_message_container', params: params,
placeholder: "Update #{@blob.name}"
= hidden_field_tag 'last_commit', @last_commit
= hidden_field_tag 'content', '', id: "file-content"
= hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
= render 'projects/commit_button', ref: @ref,
cancel_path: @after_edit_path
:javascript
blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}")
%h3.page-title New file
.file-editor
= form_tag(project_create_blob_path(@project, @id), method: :post, class: 'form-horizontal form-new-file') do
= render 'projects/blob/editor', ref: @ref
= render 'shared/commit_message_container', params: params,
placeholder: 'Add new file'
= hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref,
cancel_path: project_tree_path(@project, @id)
:javascript
blob = new NewBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", null)
......@@ -51,15 +51,6 @@
= f.check_box :issues_enabled
%span.descr Lightweight issue tracking system for this project
- if Project.issues_tracker.values.count > 1
.form-group
= f.label :issues_tracker, "Issues tracker", class: 'control-label'
.col-sm-10= f.select(:issues_tracker, project_issues_trackers(@project.issues_tracker), {}, { disabled: !@project.issues_enabled })
.form-group
= f.label :issues_tracker_id, "Project name or id in issues tracker", class: 'control-label'
.col-sm-10= f.text_field :issues_tracker_id, disabled: !@project.can_have_issues_tracker_id?, class: 'form-control'
.form-group
= f.label :merge_requests_enabled, "Merge Requests", class: 'control-label'
.col-sm-10
......@@ -89,8 +80,6 @@
.col-sm-10
- if @project.avatar?
= project_icon(@project.to_param, alt: '', class: 'avatar s160')
- else
= project_icon(@project.to_param, alt: '', class: 'avatar s160', only_uploaded: true)
%p.light
- if @project.avatar_in_git
Project avatar in repository: #{ @project.avatar_in_git }
......
.file-editor
%ul.nav.nav-tabs.js-edit-mode
%li.active
= link_to 'Edit', '#editor'
%li
= link_to editing_preview_title(@blob.name), '#preview', 'data-preview-url' => preview_project_edit_tree_path(@project, @id)
= form_tag(project_edit_tree_path(@project, @id), method: :put, class: "form-horizontal") do
= render 'projects/blob_editor', ref: @ref, path: @path, blob_data: @blob.data
= render 'shared/commit_message_container', params: params,
placeholder: "Update #{@blob.name}"
= hidden_field_tag 'last_commit', @last_commit
= hidden_field_tag 'content', '', id: "file-content"
= hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
= render 'projects/commit_button', ref: @ref,
cancel_path: @after_edit_path
:javascript
ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace")
ace.config.loadModule("ace/ext/searchbox");
var ace_mode = "#{@blob.language.try(:ace_mode)}";
var editor = ace.edit("editor");
if (ace_mode) {
editor.getSession().setMode('ace/mode/' + ace_mode);
}
disableButtonIfEmptyField("#commit_message", ".js-commit-button");
$(".js-commit-button").click(function(){
$("#file-content").val(editor.getValue());
$(".file-editor form").submit();
});
var editModePanes = $('.js-edit-mode-pane'),
editModeLinks = $('.js-edit-mode a');
editModeLinks.click(function(event) {
event.preventDefault();
var currentLink = $(this),
paneId = currentLink.attr('href'),
currentPane = editModePanes.filter(paneId);
editModeLinks.parent().removeClass('active hover');
currentLink.parent().addClass('active hover');
editModePanes.hide();
if (paneId == '#preview') {
currentPane.fadeIn(200);
$.post(currentLink.data('preview-url'), { content: editor.getValue() }, function(response) {
currentPane.empty().append(response);
})
} else {
currentPane.fadeIn(200);
editor.focus()
}
})
......@@ -3,6 +3,17 @@
= render "home_panel"
.center.well
%h3
The repository for this project is empty
%h4
You can
= link_to project_new_blob_path(@project, 'master'), class: 'btn btn-new btn-lg' do
add a file
&nbsp;or push it via command line.
%h4
%strong Command line instructions
%div.git-empty
%fieldset
%legend Git global setup
......
%h3.page-title Fork project
%p.lead Select namespace where to fork this project
%p.lead
Click to fork the project to a user or group
%hr
.fork-namespaces
......
......@@ -45,10 +45,17 @@
.automerge_widget.cannot_be_merged.hide
%h4
This request can't be merged with GitLab.
%p
You should do it manually with
%strong
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link", title: "How To Merge", "data-toggle" => "modal"
= link_to "#modal_merge_info", class: "underlined-link how_to_merge_link", title: "How To Merge", "data-toggle" => "modal" do
command line
%p
%button.btn.disabled
%i.fa.fa-warning
Accept Merge Request
&nbsp;
This usually happens when git can not resolve conflicts between branches automatically.
.automerge_widget.unchecked
%p
......
%h3.page-title New file
%hr
.file-editor
= form_tag(project_new_tree_path(@project, @id), method: :put, class: 'form-horizontal form-new-file') do
.form-group.commit_message-group
= label_tag 'file_name', class: 'control-label' do
File name
.col-sm-10
.input-group
%span.input-group-addon
= @path[-1] == "/" ? @path : @path + "/"
= text_field_tag 'file_name', params[:file_name], placeholder: "sample.rb", required: true, class: 'form-control'
%span.input-group-addon
on
%span= @ref
.form-group.commit_message-group
= label_tag :encoding, class: "control-label" do
Encoding
.col-sm-10
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
= render 'projects/blob_editor', ref: @ref
= render 'shared/commit_message_container', params: params,
placeholder: 'Add new file'
= hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref,
cancel_path: project_tree_path(@project, @id)
:javascript
ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace-src-noconflict")
var editor = ace.edit("editor");
disableButtonIfAnyEmptyField($('.form-new-file'), '.form-control', '.btn-create')
$(".js-commit-button").click(function(){
$("#file-content").val(editor.getValue());
$(".file-editor form").submit();
});
......@@ -17,7 +17,12 @@
%p 2. Set access level for them
.form-group
= f.label :access_level, "Project Access", class: 'control-label'
.col-sm-10= select_tag :access_level, options_for_select(Gitlab::Access.options, @user_project_relation.access_level), class: "project-access-select select2"
.col-sm-10
= select_tag :access_level, options_for_select(Gitlab::Access.options, @user_project_relation.access_level), class: "project-access-select select2"
.help-block
Read more about role permissions
%strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
.form-actions
= f.submit 'Add users', class: "btn btn-create"
......
......@@ -10,7 +10,7 @@
= link_to title, '#'
- if current_user && can_push_branch?(@project, @ref)
%li
= link_to project_new_tree_path(@project, @id), title: 'New file', id: 'new-file-link' do
= link_to project_new_blob_path(@project, @id), title: 'New file', id: 'new-file-link' do
%small
%i.fa.fa-plus
......
%h4 Calendar:
#cal-heatmap.calendar
:javascript
new calendar(
#{@timestamps.to_json},
#{@starting_year},
#{@starting_month}
);
......@@ -18,6 +18,11 @@
%h4 Groups:
= render 'groups', groups: @groups
%hr
.user-calendar
%h4.center.light
%i.fa.fa-spinner.fa-spin
%hr
%h4
User Activity:
......@@ -32,3 +37,8 @@
= render 'profile', user: @user
- if @projects.present?
= render 'projects', projects: @projects
:coffeescript
$ ->
$(".user-calendar").load("#{user_calendar_path}")
......@@ -153,9 +153,9 @@ production: &base
label: 'LDAP'
host: '_your_ldap_server'
port: 636
port: 389
uid: 'sAMAccountName'
method: 'ssl' # "tls" or "ssl" or "plain"
method: 'plain' # "tls" or "ssl" or "plain"
bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
password: '_the_password_of_the_bind_user'
......
......@@ -157,6 +157,9 @@ Gitlab::Application.routes.draw do
end
end
get 'u/:username/calendar' => 'users#calendar', as: :user_calendar,
constraints: { username: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }
get '/u/:username' => 'users#show', as: :user,
constraints: { username: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }
......@@ -211,17 +214,20 @@ Gitlab::Application.routes.draw do
end
scope module: :projects do
# Blob routes:
get '/new/:id', to: 'blob#new', constraints: {id: /.+/}, as: 'new_blob'
post '/create/:id', to: 'blob#create', constraints: {id: /.+/}, as: 'create_blob'
get '/edit/:id', to: 'blob#edit', constraints: {id: /.+/}, as: 'edit_blob'
put '/update/:id', to: 'blob#update', constraints: {id: /.+/}, as: 'update_blob'
post '/preview/:id', to: 'blob#preview', constraints: {id: /.+/}, as: 'preview_blob'
resources :blob, only: [:show, :destroy], constraints: { id: /.+/, format: false } do
get :diff, on: :member
end
resources :raw, only: [:show], constraints: {id: /.+/}
resources :tree, only: [:show], constraints: {id: /.+/, format: /(html|js)/ }
resources :edit_tree, only: [:show, :update], constraints: { id: /.+/ }, path: 'edit' do
# Cannot be GET to differentiate from GET paths that end in preview.
post :preview, on: :member
end
resource :avatar, only: [:show, :destroy]
resources :new_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'new'
resources :commit, only: [:show], constraints: {id: /[[:alnum:]]{6,40}/}
resources :commits, only: [:show], constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/}
resources :compare, only: [:index, :create]
......
class AddGitlabAccessTokenToUser < ActiveRecord::Migration
def change
add_column :users, :gitlab_access_token, :string
end
end
......@@ -435,6 +435,7 @@ ActiveRecord::Schema.define(version: 20150125163100) do
t.boolean "hide_no_ssh_key", default: false
t.string "website_url", default: "", null: false
t.string "github_access_token"
t.string "gitlab_access_token"
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
......
......@@ -322,6 +322,31 @@ Parameters:
- `title` (required) - new SSH Key's title
- `key` (required) - new SSH key
```json
{
"created_at": "2015-01-21T17:44:33.512Z",
"key": "ssh-dss AAAAB3NzaC1kc3MAAACBAMLrhYgI3atfrSD6KDas1b/3n6R/HP+bLaHHX6oh+L1vg31mdUqK0Ac/NjZoQunavoyzqdPYhFz9zzOezCrZKjuJDS3NRK9rspvjgM0xYR4d47oNZbdZbwkI4cTv/gcMlquRy0OvpfIvJtjtaJWMwTLtM5VhRusRuUlpH99UUVeXAAAAFQCVyX+92hBEjInEKL0v13c/egDCTQAAAIEAvFdWGq0ccOPbw4f/F8LpZqvWDydAcpXHV3thwb7WkFfppvm4SZte0zds1FJ+Hr8Xzzc5zMHe6J4Nlay/rP4ewmIW7iFKNBEYb/yWa+ceLrs+TfR672TaAgO6o7iSRofEq5YLdwgrwkMmIawa21FrZ2D9SPao/IwvENzk/xcHu7YAAACAQFXQH6HQnxOrw4dqf0NqeKy1tfIPxYYUZhPJfo9O0AmBW2S36pD2l14kS89fvz6Y1g8gN/FwFnRncMzlLY/hX70FSc/3hKBSbH6C6j8hwlgFKfizav21eS358JJz93leOakJZnGb8XlWvz1UJbwCsnR2VEY8Dz90uIk1l/UqHkA= loic@call",
"title": "ABC",
"id": 4
}
```
Will return created key with status `201 Created` on success. If an
error occurs a `400 Bad Request` is returned with a message explaining the error:
```json
{
"message": {
"fingerprint": [
"has already been taken"
],
"key": [
"has already been taken"
]
}
}
```
## Add SSH key for user
Create new key owned by specified user. Available only for admin
......
......@@ -29,9 +29,9 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server
label: 'LDAP'
host: '_your_ldap_server'
port: 636
port: 389
uid: 'sAMAccountName'
method: 'ssl' # "tls" or "ssl" or "plain"
method: 'plain' # "tls" or "ssl" or "plain"
bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
password: '_the_password_of_the_bind_user'
......@@ -76,6 +76,9 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server
EOS
```
If you are getting 'Connection Refused' errors when trying to connect to the LDAP server please double-check the LDAP `port` and `method` settings used by GitLab.
Common combinations are `method: 'plain'` and `port: 389`, OR `method: 'ssl'` and `port: 636`.
If you are using a GitLab installation from source you can find the LDAP settings in `/home/git/gitlab/config/gitlab.yml`:
```
......
# System hooks
Your GitLab instance can perform HTTP POST requests on the following events: `project_create`, `project_destroy`, `user_add_to_team`, `user_remove_from_team`, `user_create`, `user_destroy`, `key_create` and `key_destroy`.
Your GitLab instance can perform HTTP POST requests on the following events: `project_create`, `project_destroy`, `user_add_to_team`, `user_remove_from_team`, `user_create`, `user_destroy`, `key_create`, `key_destroy`, `group_create`, `group_destroy`, `user_add_to_group` and `user_remove_from_group`.
System hooks can be used, e.g. for logging or changing information in a LDAP server.
......@@ -50,6 +50,7 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser
"project_path": "storecloud",
"user_email": "johnsmith@gmail.com",
"user_name": "John Smith",
"user_id": 41,
"project_visibility": "private",
}
```
......@@ -66,6 +67,7 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser
"project_path": "storecloud",
"user_email": "johnsmith@gmail.com",
"user_name": "John Smith",
"user_id": 41,
"project_visibility": "private",
}
```
......@@ -117,3 +119,62 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser
"id": 4
}
```
**Group created:**
```json
{
"created_at": "2012-07-21T07:30:54Z",
"event_name": "group_create",
"name": "StormCloud",
"owner_email": "johnsmith@gmail.com",
"owner_name": "John Smith",
"path": "stormcloud",
"group_id": 78
}
```
**Group removed:**
```json
{
"created_at": "2012-07-21T07:30:54Z",
"event_name": "group_destroy",
"name": "StoreCloud",
"owner_email": "johnsmith@gmail.com",
"owner_name": "John Smith",
"path": "storecloud",
"group_id": 78
}
```
**New Group Member:**
```json
{
"created_at": "2012-07-21T07:30:56Z",
"event_name": "user_add_to_group",
"group_access": "Master",
"group_id": 78,
"group_name": "StoreCloud",
"group_path": "storecloud",
"user_email": "johnsmith@gmail.com",
"user_name": "John Smith",
"user_id": 41
}
```
**Group Member Removed:**
```json
{
"created_at": "2012-07-21T07:30:56Z",
"event_name": "user_remove_from_group",
"group_access": "Master",
"group_id": 78,
"group_name": "StoreCloud",
"group_path": "storecloud",
"user_email": "johnsmith@gmail.com",
"user_name": "John Smith",
"user_id": 41
}
```
......@@ -11,7 +11,7 @@ RUN apt-get update -q \
# If the Omnibus package version below is outdated please contribute a merge request to update it.
# If you run GitLab Enterprise Edition point it to a location where you have downloaded it.
RUN TMP_FILE=$(mktemp); \
wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.6.2-omnibus.5.3.0.ci.1-1_amd64.deb \
wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.7.1-omnibus.5.4.1.ci-1_amd64.deb \
&& dpkg -i $TMP_FILE \
&& rm -f $TMP_FILE
......
Feature: Project Issue Tracker
Background:
Given I sign in as a user
And I own project "Shop"
And project "Shop" has issues enabled
And I visit project "Shop" page
Scenario: I set the issue tracker to "GitLab"
When I visit edit project "Shop" page
And change the issue tracker to "GitLab"
And I save project
Then I the project should have "GitLab" as issue tracker
Scenario: I set the issue tracker to "Redmine"
When I visit edit project "Shop" page
And change the issue tracker to "Redmine"
And I save project
Then I the project should have "Redmine" as issue tracker
......@@ -34,6 +34,19 @@ Feature: Project Source Browse Files
Then I am redirected to the new file
And I should see its new content
@javascript
Scenario: I can create file in empty repo
Given I own an empty project
And I visit my empty project page
And I create bare repo
When I click on "add a file" link
And I edit code
And I fill the new file name
And I fill the commit message
And I click on "Commit Changes"
Then I am redirected to the new file
And I should see its new content
@javascript
Scenario: If I enter an illegal file name I see an error message
Given I click on "new file" link in repo
......
class Spinach::Features::ProjectIssueTracker < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
step 'project "Shop" has issues enabled' do
@project = Project.find_by(name: "Shop")
@project ||= create(:project, name: "Shop", namespace: @user.namespace)
@project.issues_enabled = true
end
step 'change the issue tracker to "GitLab"' do
select 'GitLab', from: 'project_issues_tracker'
end
step 'I the project should have "GitLab" as issue tracker' do
find_field('project_issues_tracker').value.should == 'gitlab'
end
step 'change the issue tracker to "Redmine"' do
select 'Redmine', from: 'project_issues_tracker'
end
step 'I the project should have "Redmine" as issue tracker' do
find_field('project_issues_tracker').value.should == 'redmine'
end
step 'I save project' do
click_button 'Save changes'
end
end
......@@ -173,7 +173,9 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
merge!: true,
)
click_button "Accept Merge Request"
within '.can_be_merged' do
click_button "Accept Merge Request"
end
end
step 'I should see merged request' do
......
......@@ -58,7 +58,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step 'I can edit code' do
set_new_content
evaluate_script('editor.getValue()').should == new_gitignore_content
evaluate_script('blob.editor.getValue()').should == new_gitignore_content
end
step 'I edit code' do
......@@ -78,7 +78,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I click link "Diff"' do
click_link 'Diff'
click_link 'Preview changes'
end
step 'I click on "Commit Changes"' do
......@@ -103,7 +103,6 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step 'I can see new file page' do
page.should have_content "New file"
page.should have_content "File name"
page.should have_content "Commit message"
end
......@@ -167,10 +166,21 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
expect(page).to have_content('Your changes could not be committed')
end
step 'I create bare repo' do
click_link 'Create empty bare repository'
end
step 'I click on "add a file" link' do
click_link 'add a file'
# Remove pre-receive hook so we can push without auth
FileUtils.rm(File.join(Project.last.repository.path, 'hooks', 'pre-receive'))
end
private
def set_new_content
execute_script("editor.setValue('#{new_gitignore_content}')")
execute_script("blob.editor.setValue('#{new_gitignore_content}')")
end
# Content of the gitignore file on the seed repository.
......
......@@ -284,11 +284,11 @@ module SharedPaths
end
step 'I am on the new file page' do
current_path.should eq(project_new_tree_path(@project, root_ref))
current_path.should eq(project_create_blob_path(@project, root_ref))
end
step 'I am on the ".gitignore" edit file page' do
current_path.should eq(project_edit_tree_path(
current_path.should eq(project_edit_blob_path(
@project, File.join(root_ref, '.gitignore')))
end
......
......@@ -28,6 +28,10 @@ module SharedProject
@project.team << [@user, :master]
end
step 'I visit my empty project page' do
visit project_path(Project.find_by(name: 'Empty Project'))
end
step 'project "Shop" has push event' do
@project = Project.find_by(name: "Shop")
......
......@@ -58,11 +58,13 @@ module API
# ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used
# Example Request:
# GET /projects/:id/repository/tree
get ":id/repository/tree" do
get ':id/repository/tree' do
ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
path = params[:path] || nil
commit = user_project.repository.commit(ref)
not_found!('Tree') unless commit
tree = user_project.repository.tree(commit.id, path)
present tree.sorted_entries, with: Entities::RepoTreeObject
......@@ -100,14 +102,18 @@ module API
# sha (required) - The blob's sha
# Example Request:
# GET /projects/:id/repository/raw_blobs/:sha
get ":id/repository/raw_blobs/:sha" do
get ':id/repository/raw_blobs/:sha' do
ref = params[:sha]
repo = user_project.repository
blob = Gitlab::Git::Blob.raw(repo, ref)
begin
blob = Gitlab::Git::Blob.raw(repo, ref)
rescue
not_found! 'Blob'
end
not_found! "Blob" unless blob
not_found! 'Blob' unless blob
env['api.format'] = :txt
......@@ -122,13 +128,23 @@ module API
# sha (optional) - the commit sha to download defaults to the tip of the default branch
# Example Request:
# GET /projects/:id/repository/archive
get ":id/repository/archive", requirements: { format: Gitlab::Regex.archive_formats_regex } do
get ':id/repository/archive',
requirements: { format: Gitlab::Regex.archive_formats_regex } do
authorize! :download_code, user_project
file_path = ArchiveRepositoryService.new.execute(user_project, params[:sha], params[:format])
begin
file_path = ArchiveRepositoryService.new.execute(
user_project,
params[:sha],
params[:format])
rescue
not_found!('File')
end
if file_path && File.exists?(file_path)
data = File.open(file_path, 'rb').read
header["Content-Disposition"] = "attachment; filename=\"#{File.basename(file_path)}\""
basename = File.basename(file_path)
header['Content-Disposition'] = "attachment; filename=\"#{basename}\""
content_type MIME::Types.type_for(file_path).first.content_type
env['api.format'] = :binary
present data
......@@ -161,7 +177,12 @@ module API
get ':id/repository/contributors' do
authorize! :download_code, user_project
present user_project.repository.contributors, with: Entities::Contributor
begin
present user_project.repository.contributors,
with: Entities::Contributor
rescue
not_found!
end
end
end
end
......
# Module providing methods for dealing with separating a tree-ish string and a
# file path string when combined in a request parameter
module ExtractsPath
extend ActiveSupport::Concern
# Raised when given an invalid file path
class InvalidPathError < StandardError; end
included do
if respond_to?(:before_filter)
before_filter :assign_ref_vars
end
end
# Given a string containing both a Git tree-ish, such as a branch or tag, and
# a filesystem path joined by forward slashes, attempts to separate the two.
#
......
......@@ -34,7 +34,7 @@ module Grack
def auth!
if @auth.provided?
return bad_request unless @auth.basic?
# Authentication with username and password
login, password = @auth.credentials
......@@ -71,8 +71,20 @@ module Grack
false
end
def oauth_access_token_check(login, password)
if login == "oauth2" && git_cmd == 'git-upload-pack' && password.present?
token = Doorkeeper::AccessToken.by_token(password)
token && token.accessible? && User.find_by(id: token.resource_owner_id)
end
end
def authenticate_user(login, password)
user = Gitlab::Auth.new.find(login, password)
unless user
user = oauth_access_token_check(login, password)
end
return user if user.present?
# At this point, we know the credentials were wrong. We let Rack::Attack
......
module Gitlab
class CommitsCalendar
attr_reader :timestamps
def initialize(repositories, user)
@timestamps = {}
date_timestamps = []
repositories.select(&:exists?).reject(&:empty?).each do |raw_repository|
commits_log = raw_repository.commits_per_day_for_user(user)
date_timestamps << commits_log
end
date_timestamps = date_timestamps.inject do |collection, date|
collection.merge(date) { |k, old_v, new_v| old_v + new_v }
end
date_timestamps ||= []
date_timestamps.each do |date, commits|
timestamp = Date.parse(date).to_time.to_i.to_s rescue nil
@timestamps[timestamp] = commits if timestamp
end
end
end
end
......@@ -73,7 +73,7 @@ module Gitlab
changes = changes.lines if changes.kind_of?(String)
# Iterate over all changes to find if user allowed all of them to be applied
changes.each do |change|
changes.map(&:strip).reject(&:blank?).each do |change|
status = change_access_check(user, project, change)
unless status.allowed?
# If user does not have access to make at least one change - cancel all push
......
......@@ -40,12 +40,16 @@ module Gitlab
def update_user_attributes
gl_user.email = auth_hash.email
gl_user.identities.build(provider: auth_hash.provider, extern_uid: auth_hash.uid)
# Build new identity only if we dont have have same one
gl_user.identities.find_or_initialize_by(provider: auth_hash.provider,
extern_uid: auth_hash.uid)
gl_user
end
def changed?
gl_user.changed?
gl_user.changed? || gl_user.identities.any?(&:changed?)
end
def needs_blocking?
......
......@@ -208,7 +208,7 @@ module Gitlab
end
def reference_issue(identifier, project = @project, prefix_text = nil)
if project.used_default_issues_tracker? || !external_issues_tracker_enabled?
if project.default_issues_tracker?
if project.issue_exists? identifier
url = url_for_issue(identifier, project)
title = title_for_issue(identifier, project)
......@@ -220,10 +220,8 @@ module Gitlab
link_to("#{prefix_text}##{identifier}", url, options)
end
else
config = Gitlab.config
external_issue_tracker = config.issues_tracker[project.issues_tracker]
if external_issue_tracker.present?
reference_external_issue(identifier, external_issue_tracker, project,
if project.external_issue_tracker.present?
reference_external_issue(identifier, project,
prefix_text)
end
end
......@@ -267,10 +265,10 @@ module Gitlab
end
end
def reference_external_issue(identifier, issue_tracker, project = @project,
def reference_external_issue(identifier, project = @project,
prefix_text = nil)
url = url_for_issue(identifier, project)
title = issue_tracker['title']
title = project.external_issue_tracker.title
options = html_options.merge(
title: "Issue in #{title}",
......
......@@ -14,7 +14,14 @@ module Gitlab
prepare_satellite!(repo)
# create target branch in satellite at the corresponding commit from bare repo
repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}")
current_ref =
if @project.empty_repo?
# skip this step if we want to add first file to empty repo
Satellite::PARKING_BRANCH
else
repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}")
ref
end
file_path_in_satellite = File.join(repo.working_dir, file_path)
dir_name_in_satellite = File.dirname(file_path_in_satellite)
......@@ -38,10 +45,9 @@ module Gitlab
# will raise CommandFailed when commit fails
repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
# push commit back to bare repo
# will raise CommandFailed when push fails
repo.git.push({raise: true, timeout: true}, :origin, ref)
repo.git.push({raise: true, timeout: true}, :origin, "#{current_ref}:#{ref}")
# everything worked
true
......
require 'spec_helper'
describe UsersController do
let(:user) { create(:user, username: "user1", name: "User 1", email: "user1@gitlab.com") }
before do
sign_in(user)
end
describe "GET #show" do
render_views
it "renders the show template" do
get :show, username: user.username
expect(response.status).to eq(200)
expect(response).to render_template("show")
end
end
describe "GET #calendar" do
it "renders calendar" do
get :calendar, username: user.username
expect(response).to render_template("calendar")
end
end
end
......@@ -76,7 +76,19 @@ FactoryGirl.define do
end
factory :redmine_project, parent: :project do
issues_tracker { "redmine" }
issues_tracker_id { "project_name_in_redmine" }
after :create do |project|
project.create_redmine_service(
active: true,
properties: {
'project_url' => 'http://redmine/projects/project_name_in_redmine',
'issues_url' => "http://redmine/#{project.id}/project_name_in_redmine/:id",
'new_issue_url' => 'http://redmine/projects/project_name_in_redmine/issues/new'
}
)
end
after :create do |project|
project.issues_tracker = 'redmine'
project.issues_tracker_id = 'project_name_in_redmine'
end
end
end
......@@ -2,7 +2,7 @@ require 'spec_helper'
describe "Dashboard Feed", feature: true do
describe "GET /" do
let!(:user) { create(:user) }
let!(:user) { create(:user, name: "Jonh") }
context "projects atom feed via private token" do
it "should render projects atom feed" do
......
......@@ -23,6 +23,7 @@ describe GitlabMarkdownHelper do
@project = project
@ref = 'markdown'
@repository = project.repository
@request.host = Gitlab.config.gitlab.host
end
describe "#gfm" do
......@@ -296,10 +297,13 @@ describe GitlabMarkdownHelper do
let(:reference) { "JIRA-#{issue.iid}" }
before do
issue_tracker_config = { "jira" => { "title" => "JIRA tracker", "issues_url" => "http://jira.example/browse/:id" } }
Gitlab.config.stub(:issues_tracker).and_return(issue_tracker_config)
@project.stub(:issues_tracker).and_return("jira")
@project.stub(:issues_tracker_id).and_return("JIRA")
jira = @project.create_jira_service if @project.jira_service.nil?
properties = {"title"=>"JIRA tracker", "project_url"=>"http://jira.example/issues/?jql=project=A", "issues_url"=>"http://jira.example/browse/:id", "new_issue_url"=>"http://jira.example/secure/CreateIssue.jspa"}
jira.update_attributes(properties: properties, active: true)
end
after do
@project.jira_service.destroy! unless @project.jira_service.nil?
end
it "should link using a valid id" do
......
......@@ -24,7 +24,7 @@ describe IssuesHelper do
end
describe :url_for_project_issues do
let(:project_url) { Gitlab.config.issues_tracker.redmine.project_url}
let(:project_url) { ext_project.external_issue_tracker.project_url }
let(:ext_expected) do
project_url.gsub(':project_id', ext_project.id.to_s)
.gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s)
......@@ -54,17 +54,16 @@ describe IssuesHelper do
Gitlab.config.stub(:issues_tracker).and_return(nil)
end
it "should return path to internal tracker" do
url_for_project_issues.should match(polymorphic_path([@project]))
it "should return path to external tracker" do
url_for_project_issues.should match(ext_expected)
end
end
end
describe :url_for_issue do
let(:issue_id) { 3 }
let(:issues_url) { Gitlab.config.issues_tracker.redmine.issues_url}
let(:issues_url) { ext_project.external_issue_tracker.issues_url}
let(:ext_expected) do
issues_url.gsub(':id', issue_id.to_s)
issues_url.gsub(':id', issue.iid.to_s)
.gsub(':project_id', ext_project.id.to_s)
.gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s)
end
......@@ -78,7 +77,7 @@ describe IssuesHelper do
it "should return path to external tracker" do
@project = ext_project
url_for_issue(issue_id).should match(ext_expected)
url_for_issue(issue.iid).should match(ext_expected)
end
it "should return empty string if project nil" do
......@@ -93,14 +92,14 @@ describe IssuesHelper do
Gitlab.config.stub(:issues_tracker).and_return(nil)
end
it "should return internal path" do
url_for_issue(issue.iid).should match(polymorphic_path([@project, issue]))
it "should return external path" do
url_for_issue(issue.iid).should match(ext_expected)
end
end
end
describe :url_for_new_issue do
let(:issues_url) { Gitlab.config.issues_tracker.redmine.new_issue_url}
let(:issues_url) { ext_project.external_issue_tracker.new_issue_url }
let(:ext_expected) do
issues_url.gsub(':project_id', ext_project.id.to_s)
.gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s)
......@@ -131,7 +130,7 @@ describe IssuesHelper do
end
it "should return internal path" do
url_for_new_issue.should match(new_project_issue_path(@project))
url_for_new_issue.should match(ext_expected)
end
end
end
......
require 'spec_helper'
describe ProjectsHelper do
describe '#project_issues_trackers' do
it "returns the correct issues trackers available" do
project_issues_trackers.should ==
"<option value=\"redmine\">Redmine</option>\n" \
"<option value=\"gitlab\">GitLab</option>"
end
it "returns the correct issues trackers available with current tracker 'gitlab' selected" do
project_issues_trackers('gitlab').should ==
"<option value=\"redmine\">Redmine</option>\n" \
"<option selected=\"selected\" value=\"gitlab\">GitLab</option>"
end
it "returns the correct issues trackers available with current tracker 'redmine' selected" do
project_issues_trackers('redmine').should ==
"<option selected=\"selected\" value=\"redmine\">Redmine</option>\n" \
"<option value=\"gitlab\">GitLab</option>"
end
end
describe "#project_status_css_class" do
it "returns appropriate class" do
project_status_css_class("started").should == "active"
project_status_css_class("failed").should == "danger"
project_status_css_class("finished").should == "success"
end
end
end
......@@ -13,6 +13,23 @@ describe Gitlab::LDAP::User do
double(uid: 'my-uid', provider: 'ldapmain', info: double(info))
end
describe :changed? do
it "marks existing ldap user as changed" do
existing_user = create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain')
expect(gl_user.changed?).to be_true
end
it "marks existing non-ldap user if the email matches as changed" do
existing_user = create(:user, email: 'john@example.com')
expect(gl_user.changed?).to be_true
end
it "dont marks existing ldap user as changed" do
existing_user = create(:omniauth_user, email: 'john@example.com', extern_uid: 'my-uid', provider: 'ldapmain')
expect(gl_user.changed?).to be_false
end
end
describe :find_or_create do
it "finds the user if already existing" do
existing_user = create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain')
......
......@@ -12,7 +12,6 @@ describe Gitlab::ReferenceExtractor do
end
it 'extracts JIRA issue references' do
Gitlab.config.gitlab.stub(:issues_tracker).and_return('jira')
subject.analyze('this one talks about issue JIRA-1234', nil)
subject.issues.should == [{ project: nil, id: 'JIRA-1234' }]
end
......
require 'spec_helper'
describe JiraService do
describe "Associations" do
it { should belong_to :project }
it { should have_one :service_hook }
end
describe "Validations" do
context "active" do
before do
subject.active = true
end
it { should validate_presence_of :project_url }
it { should validate_presence_of :issues_url }
it { should validate_presence_of :new_issue_url }
end
end
describe 'description and title' do
let(:project) { create(:project) }
context 'when it is not set' do
before do
@service = project.create_jira_service(active: true)
end
after do
@service.destroy!
end
it 'should be initialized' do
expect(@service.title).to eq('JIRA')
expect(@service.description).to eq("Jira issue tracker")
end
end
context 'when it is set' do
before do
properties = { 'title' => 'Jira One', 'description' => 'Jira One issue tracker' }
@service = project.create_jira_service(active: true, properties: properties)
end
after do
@service.destroy!
end
it "should be correct" do
expect(@service.title).to eq('Jira One')
expect(@service.description).to eq('Jira One issue tracker')
end
end
end
describe 'project and issue urls' do
let(:project) { create(:project) }
context 'when gitlab.yml was initialized' do
before do
settings = { "jira" => {
"title" => "Jira",
"project_url" => "http://jira.sample/projects/project_a",
"issues_url" => "http://jira.sample/issues/:id",
"new_issue_url" => "http://jira.sample/projects/project_a/issues/new"
}
}
Gitlab.config.stub(:issues_tracker).and_return(settings)
@service = project.create_jira_service(active: true)
end
after do
@service.destroy!
end
it 'should be prepopulated with the settings' do
expect(@service.properties[:project_url]).to eq('http://jira.sample/projects/project_a')
expect(@service.properties[:issues_url]).to eq("http://jira.sample/issues/:id")
expect(@service.properties[:new_issue_url]).to eq("http://jira.sample/projects/project_a/issues/new")
end
end
end
end
......@@ -198,16 +198,16 @@ describe Project do
end
end
describe :used_default_issues_tracker? do
describe :default_issues_tracker? do
let(:project) { create(:project) }
let(:ext_project) { create(:redmine_project) }
it 'should be true if used internal tracker' do
project.used_default_issues_tracker?.should be_true
it "should be true if used internal tracker" do
project.default_issues_tracker?.should be_true
end
it 'should be false if used other tracker' do
ext_project.used_default_issues_tracker?.should be_false
it "should be false if used other tracker" do
ext_project.default_issues_tracker?.should be_false
end
end
......
......@@ -61,5 +61,40 @@ describe SystemHook do
project.project_members.destroy_all
WebMock.should have_requested(:post, @system_hook.url).with(body: /user_remove_from_team/).once
end
it 'group create hook' do
create(:group)
WebMock.should have_requested(:post, @system_hook.url).with(
body: /group_create/
).once
end
it 'group destroy hook' do
group = create(:group)
group.destroy
WebMock.should have_requested(:post, @system_hook.url).with(
body: /group_destroy/
).once
end
it 'group member create hook' do
group = create(:group)
user = create(:user)
group.add_user(user, Gitlab::Access::MASTER)
WebMock.should have_requested(:post, @system_hook.url).with(
body: /user_add_to_group/
).once
end
it 'group member destroy hook' do
group = create(:group)
user = create(:user)
group.add_user(user, Gitlab::Access::MASTER)
group.group_members.destroy_all
WebMock.should have_requested(:post, @system_hook.url).with(
body: /user_remove_from_group/
).once
end
end
end
......@@ -101,6 +101,14 @@ describe API::API, api: true do
json_response.first['type'].should == 'tree'
json_response.first['mode'].should == '040000'
end
it 'should return a 404 for unknown ref' do
get api("/projects/#{project.id}/repository/tree?ref_name=foo", user)
response.status.should == 404
json_response.should be_an Object
json_response['message'] == '404 Tree Not Found'
end
end
context "unauthorized user" do
......@@ -145,6 +153,14 @@ describe API::API, api: true do
get api("/projects/#{project.id}/repository/raw_blobs/#{sample_blob.oid}", user)
response.status.should == 200
end
it 'should return a 404 for unknown blob' do
get api("/projects/#{project.id}/repository/raw_blobs/123456", user)
response.status.should == 404
json_response.should be_an Object
json_response['message'] == '404 Blob Not Found'
end
end
describe "GET /projects/:id/repository/archive(.:format)?:sha" do
......
......@@ -430,21 +430,17 @@ describe Projects::TreeController, 'routing' do
end
end
describe Projects::EditTreeController, 'routing' do
it 'to #show' do
describe Projects::BlobController, 'routing' do
it 'to #edit' do
get('/gitlab/gitlabhq/edit/master/app/models/project.rb').should(
route_to('projects/edit_tree#show',
route_to('projects/blob#edit',
project_id: 'gitlab/gitlabhq',
id: 'master/app/models/project.rb'))
get('/gitlab/gitlabhq/edit/master/app/models/project.rb/preview').should(
route_to('projects/edit_tree#show',
project_id: 'gitlab/gitlabhq',
id: 'master/app/models/project.rb/preview'))
end
it 'to #preview' do
post('/gitlab/gitlabhq/edit/master/app/models/project.rb/preview').should(
route_to('projects/edit_tree#preview',
post('/gitlab/gitlabhq/preview/master/app/models/project.rb').should(
route_to('projects/blob#preview',
project_id: 'gitlab/gitlabhq',
id: 'master/app/models/project.rb'))
end
......
......@@ -22,6 +22,7 @@ describe Issues::UpdateService do
}
@issue = Issues::UpdateService.new(project, user, opts).execute(issue)
@issue.reload
end
it { @issue.should be_valid }
......
......@@ -21,12 +21,14 @@ describe MergeRequests::UpdateService do
state_event: 'close'
}
end
let(:service) { MergeRequests::UpdateService.new(project, user, opts) }
before do
service.stub(:execute_hooks)
@merge_request = service.execute(merge_request)
@merge_request.reload
end
it { @merge_request.should be_valid }
......
......@@ -187,7 +187,7 @@ describe NotificationService do
end
describe 'Issues' do
let(:issue) { create :issue, assignee: create(:user) }
let(:issue) { create :issue, assignee: create(:user), description: 'cc @participant' }
before do
build_team(issue.project)
......@@ -197,6 +197,7 @@ describe NotificationService do
it do
should_email(issue.assignee_id)
should_email(@u_watcher.id)
should_email(@u_participant_mentioned.id)
should_not_email(@u_mentioned.id)
should_not_email(@u_participating.id)
should_not_email(@u_disabled.id)
......@@ -222,6 +223,7 @@ describe NotificationService do
it 'should email new assignee' do
should_email(issue.assignee_id)
should_email(@u_watcher.id)
should_email(@u_participant_mentioned.id)
should_not_email(@u_participating.id)
should_not_email(@u_disabled.id)
......@@ -242,6 +244,7 @@ describe NotificationService do
should_email(issue.assignee_id)
should_email(issue.author_id)
should_email(@u_watcher.id)
should_email(@u_participant_mentioned.id)
should_not_email(@u_participating.id)
should_not_email(@u_disabled.id)
......@@ -262,6 +265,7 @@ describe NotificationService do
should_email(issue.assignee_id)
should_email(issue.author_id)
should_email(@u_watcher.id)
should_email(@u_participant_mentioned.id)
should_not_email(@u_participating.id)
should_not_email(@u_disabled.id)
......@@ -404,6 +408,7 @@ describe NotificationService do
def build_team(project)
@u_watcher = create(:user, notification_level: Notification::N_WATCH)
@u_participating = create(:user, notification_level: Notification::N_PARTICIPATING)
@u_participant_mentioned = create(:user, username: 'participant', notification_level: Notification::N_PARTICIPATING)
@u_disabled = create(:user, notification_level: Notification::N_DISABLED)
@u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_MENTION)
@u_committer = create(:user, username: 'committer')
......
......@@ -5,6 +5,8 @@ describe SystemHooksService do
let (:project) { create :project }
let (:project_member) { create :project_member }
let (:key) { create(:key, user: user) }
let (:group) { create(:group) }
let (:group_member) { create(:group_member) }
context 'event data' do
it { event_data(user, :create).should include(:event_name, :name, :created_at, :email, :user_id) }
......@@ -15,6 +17,31 @@ describe SystemHooksService do
it { event_data(project_member, :destroy).should include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :access_level, :project_visibility) }
it { event_data(key, :create).should include(:username, :key, :id) }
it { event_data(key, :destroy).should include(:username, :key, :id) }
it do
event_data(group, :create).should include(
:event_name, :name, :created_at, :path, :group_id, :owner_name,
:owner_email
)
end
it do
event_data(group, :destroy).should include(
:event_name, :name, :created_at, :path, :group_id, :owner_name,
:owner_email
)
end
it do
event_data(group_member, :create).should include(
:event_name, :created_at, :group_name, :group_path, :group_id, :user_id,
:user_name, :user_email, :group_access
)
end
it do
event_data(group_member, :destroy).should include(
:event_name, :created_at, :group_name, :group_path, :group_id, :user_id,
:user_name, :user_email, :group_access
)
end
end
context 'event names' do
......@@ -26,6 +53,10 @@ describe SystemHooksService do
it { event_name(project_member, :destroy).should eq "user_remove_from_team" }
it { event_name(key, :create).should eq 'key_create' }
it { event_name(key, :destroy).should eq 'key_destroy' }
it { event_name(group, :create).should eq 'group_create' }
it { event_name(group, :destroy).should eq 'group_destroy' }
it { event_name(group_member, :create).should eq 'user_add_to_group' }
it { event_name(group_member, :destroy).should eq 'user_remove_from_group' }
end
def event_data(*args)
......
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