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 ...@@ -3,10 +3,10 @@ Note: The upcoming release contains empty lines to reduce the number of merge co
v 7.8.0 v 7.8.0
- Replace highlight.js with rouge-fork rugments (Stefan Tatschner) - Replace highlight.js with rouge-fork rugments (Stefan Tatschner)
- Make project search case insensitive (Hannes Rosenögger) - 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 - 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) - Add diff syntax highlighting in email-on-push service notifications (Hannes Rosenögger)
- -
- -
...@@ -17,8 +17,10 @@ v 7.8.0 ...@@ -17,8 +17,10 @@ v 7.8.0
- Show tags in commit view (Hannes Rosenögger) - Show tags in commit view (Hannes Rosenögger)
- Only count a user's vote once on a merge request or issue (Michael Clarke) - 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 - Upgrade Sidekiq gem to version 3.3.0
- Stop git zombie creation during force push check - Stop git zombie creation during force push check
...@@ -31,9 +33,11 @@ v 7.8.0 ...@@ -31,9 +33,11 @@ v 7.8.0
- Allow configuring protection of the default branch upon first push (Marco Wessel) - 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) - Add Project Avatars (Steven Thonus and Hannes Rosenögger)
- -
- -
...@@ -59,6 +63,7 @@ v 7.8.0 ...@@ -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 v 7.7.1
- Improve mention autocomplete performance - Improve mention autocomplete performance
......
...@@ -154,6 +154,9 @@ gem "slack-notifier", "~> 1.0.0" ...@@ -154,6 +154,9 @@ gem "slack-notifier", "~> 1.0.0"
# d3 # d3
gem "d3_rails", "~> 3.1.4" gem "d3_rails", "~> 3.1.4"
#cal-heatmap
gem "cal-heatmap-rails", "~> 0.0.1"
# underscore-rails # underscore-rails
gem "underscore-rails", "~> 1.4.4" gem "underscore-rails", "~> 1.4.4"
...@@ -170,7 +173,7 @@ gem 'ace-rails-ap' ...@@ -170,7 +173,7 @@ gem 'ace-rails-ap'
gem 'mousetrap-rails' gem 'mousetrap-rails'
# Semantic UI Sass for Sidebar # 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 "sass-rails", '~> 4.0.2'
gem "coffee-rails" gem "coffee-rails"
......
...@@ -52,6 +52,7 @@ GEM ...@@ -52,6 +52,7 @@ GEM
sass (~> 3.2) sass (~> 3.2)
browser (0.7.2) browser (0.7.2)
builder (3.2.2) builder (3.2.2)
cal-heatmap-rails (0.0.1)
capybara (2.2.1) capybara (2.2.1)
mime-types (>= 1.16) mime-types (>= 1.16)
nokogiri (>= 1.3.3) nokogiri (>= 1.3.3)
...@@ -490,7 +491,7 @@ GEM ...@@ -490,7 +491,7 @@ GEM
activesupport (>= 3.1, < 4.2) activesupport (>= 3.1, < 4.2)
select2-rails (3.5.2) select2-rails (3.5.2)
thor (~> 0.14) thor (~> 0.14)
semantic-ui-sass (0.16.1.0) semantic-ui-sass (1.8.0.0)
sass (~> 3.2) sass (~> 3.2)
settingslogic (2.0.9) settingslogic (2.0.9)
sexp_processor (4.4.0) sexp_processor (4.4.0)
...@@ -627,6 +628,7 @@ DEPENDENCIES ...@@ -627,6 +628,7 @@ DEPENDENCIES
binding_of_caller binding_of_caller
bootstrap-sass (~> 3.0) bootstrap-sass (~> 3.0)
browser browser
cal-heatmap-rails (~> 0.0.1)
capybara (~> 2.2.1) capybara (~> 2.2.1)
carrierwave carrierwave
coffee-rails coffee-rails
...@@ -715,7 +717,7 @@ DEPENDENCIES ...@@ -715,7 +717,7 @@ DEPENDENCIES
sdoc sdoc
seed-fu seed-fu
select2-rails select2-rails
semantic-ui-sass (~> 0.16.1.0) semantic-ui-sass (~> 1.8.0)
settingslogic settingslogic
shoulda-matchers (~> 2.1.0) shoulda-matchers (~> 2.1.0)
sidekiq (~> 3.3) sidekiq (~> 3.3)
......
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
#= require shortcuts_dashboard_navigation #= require shortcuts_dashboard_navigation
#= require shortcuts_issueable #= require shortcuts_issueable
#= require shortcuts_network #= require shortcuts_network
#= require cal-heatmap
#= require_tree . #= require_tree .
window.slugify = (text) -> 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 ...@@ -9,17 +9,3 @@ class @ProjectNew
initEvents: -> initEvents: ->
disableButtonIfEmptyField '#project_name', '.project-submit' 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 ...@@ -6,7 +6,7 @@ class @ProjectShow
new Flash('Star toggle failed. Try again later.', 'alert') new Flash('Star toggle failed. Try again later.', 'alert')
$("a[data-toggle='tab']").on "shown.bs.tab", (e) -> $("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") defaultView = $.cookie("default_view")
if defaultView if defaultView
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
*= require select2 *= require select2
*= require_self *= require_self
*= require dropzone/basic *= require dropzone/basic
*= require cal-heatmap
*/ */
@import "main/*"; @import "main/*";
......
...@@ -173,6 +173,11 @@ ...@@ -173,6 +173,11 @@
margin-right: 0px; margin-right: 0px;
} }
} }
&.btn-lg {
font-size: 15px;
line-height: 1.4;
}
} }
.btn-block { .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 @@ ...@@ -10,8 +10,8 @@
border: none; border: none;
border-radius: 0; border-radius: 0;
font-family: $monospace_font; font-family: $monospace_font;
font-size: 12px !important; font-size: $code_font_size !important;
line-height: 16px !important; line-height: $code_line_height !important;
margin: 0; margin: 0;
overflow: auto; overflow: auto;
overflow-y: hidden; overflow-y: hidden;
...@@ -38,8 +38,8 @@ ...@@ -38,8 +38,8 @@
a { a {
font-family: $monospace_font; font-family: $monospace_font;
display: block; display: block;
font-size: 12px !important; font-size: $code_font_size !important;
line-height: 16px !important; line-height: $code_line_height !important;
white-space: nowrap; white-space: nowrap;
i { i {
......
/* /*
* Twitter bootstrap with GitLab customizations/additions * 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; $font-size-base: 13px !default;
......
...@@ -139,7 +139,7 @@ ...@@ -139,7 +139,7 @@
} }
@mixin panel-colored { @mixin panel-colored {
border: none; border: 1px solid #EEE;
background: $box_bg; background: $box_bg;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09)); @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
......
...@@ -59,3 +59,5 @@ $list-font-size: 15px; ...@@ -59,3 +59,5 @@ $list-font-size: 15px;
$sidebar_width: 230px; $sidebar_width: 230px;
$avatar_radius: 50%; $avatar_radius: 50%;
$code_font_size: 13px;
$code_line_height: 1.5;
...@@ -112,3 +112,7 @@ ...@@ -112,3 +112,7 @@
color: #FFF; color: #FFF;
} }
} }
.dash-list .str-truncated {
max-width: 72%;
}
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
overflow-y: hidden; overflow-y: hidden;
background: #FFF; background: #FFF;
color: #333; color: #333;
font-size: 12px; font-size: $code_font_size;
.old { .old {
span.idiff { span.idiff {
background-color: #F99; background-color: #F99;
...@@ -64,8 +64,8 @@ ...@@ -64,8 +64,8 @@
margin: 0px; margin: 0px;
padding: 0px; padding: 0px;
td { td {
line-height: 18px; line-height: $code_line_height;
font-size: 12px; font-size: $code_font_size;
} }
} }
......
...@@ -31,4 +31,26 @@ ...@@ -31,4 +31,26 @@
margin: 5px 8px 0 8px; 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 { ...@@ -138,9 +138,10 @@ header {
top: -1px; top: -1px;
padding-right: 0px !important; padding-right: 0px !important;
img { img {
width: 26px; width: 50px;
height: 26px; height: 50px;
@include border-radius($avatar_radius); margin: -15px;
margin-left: 5px;
} }
} }
......
...@@ -163,8 +163,9 @@ form.edit-issue { ...@@ -163,8 +163,9 @@ form.edit-issue {
} }
} }
.issue-title { h3.issue-title {
margin-top: 0; margin-top: 0;
font-size: 2em;
} }
.context .select2-container { .context .select2-container {
......
...@@ -122,6 +122,7 @@ ...@@ -122,6 +122,7 @@
background: $box_bg; background: $box_bg;
margin-bottom: 20px; margin-bottom: 20px;
color: #666; color: #666;
border: 1px solid #EEE;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09)); @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
.ci_widget { .ci_widget {
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
.project-home-panel { .project-home-panel {
margin-bottom: 15px; margin-bottom: 15px;
position: relative;
padding-left: 85px;
&.empty-project { &.empty-project {
border-bottom: 0px; border-bottom: 0px;
...@@ -23,6 +25,22 @@ ...@@ -23,6 +25,22 @@
margin-bottom: 0px; 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 { .project-home-dropdown {
margin-left: 10px; margin-left: 10px;
float: right; float: right;
......
...@@ -181,7 +181,7 @@ class ApplicationController < ActionController::Base ...@@ -181,7 +181,7 @@ class ApplicationController < ActionController::Base
end end
def add_gon_variables 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.api_version = API::API.version
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root 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 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 @@ ...@@ -2,7 +2,7 @@
class Projects::BlameController < Projects::ApplicationController class Projects::BlameController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
# Authorize before_filter :assign_ref_vars
before_filter :authorize_download_code! before_filter :authorize_download_code!
before_filter :require_non_empty_project before_filter :require_non_empty_project
......
...@@ -2,16 +2,70 @@ ...@@ -2,16 +2,70 @@
class Projects::BlobController < Projects::ApplicationController class Projects::BlobController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
# Authorize # Raised when given an invalid file path
class InvalidPathError < StandardError; end
before_filter :authorize_download_code! 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 :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 def show
end 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 def destroy
result = Files::DeleteService.new(@project, current_user, params, @ref, @path).execute result = Files::DeleteService.new(@project, current_user, params, @ref, @path).execute
...@@ -46,10 +100,44 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -46,10 +100,44 @@ class Projects::BlobController < Projects::ApplicationController
if @blob if @blob
@blob @blob
elsif tree.entries.any?
redirect_to project_tree_path(@project, File.join(@ref, @path)) and return
else 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! return not_found!
end end
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 end
...@@ -3,7 +3,7 @@ require "base64" ...@@ -3,7 +3,7 @@ require "base64"
class Projects::CommitsController < Projects::ApplicationController class Projects::CommitsController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
# Authorize before_filter :assign_ref_vars
before_filter :authorize_download_code! before_filter :authorize_download_code!
before_filter :require_non_empty_project 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 ...@@ -2,7 +2,7 @@ class Projects::NetworkController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include ApplicationHelper include ApplicationHelper
# Authorize before_filter :assign_ref_vars
before_filter :authorize_download_code! before_filter :authorize_download_code!
before_filter :require_non_empty_project 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 @@ ...@@ -2,7 +2,7 @@
class Projects::RawController < Projects::ApplicationController class Projects::RawController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
# Authorize before_filter :assign_ref_vars
before_filter :authorize_download_code! before_filter :authorize_download_code!
before_filter :require_non_empty_project before_filter :require_non_empty_project
......
class Projects::RefsController < Projects::ApplicationController class Projects::RefsController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
# Authorize before_filter :assign_ref_vars
before_filter :authorize_download_code! before_filter :authorize_download_code!
before_filter :require_non_empty_project before_filter :require_non_empty_project
......
...@@ -9,7 +9,7 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -9,7 +9,7 @@ class Projects::ServicesController < Projects::ApplicationController
def index def index
@project.build_missing_services @project.build_missing_services
@services = @project.services.reload @services = @project.services.visible.reload
end end
def edit def edit
...@@ -17,7 +17,8 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -17,7 +17,8 @@ class Projects::ServicesController < Projects::ApplicationController
def update def update
if @service.update_attributes(service_params) 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 else
render 'edit' render 'edit'
end end
...@@ -45,7 +46,8 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -45,7 +46,8 @@ class Projects::ServicesController < Projects::ApplicationController
:title, :token, :type, :active, :api_key, :subdomain, :title, :token, :type, :active, :api_key, :subdomain,
:room, :recipients, :project_url, :webhook, :room, :recipients, :project_url, :webhook,
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password, :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
end end
# Controller for viewing a repository's file structure # Controller for viewing a repository's file structure
class Projects::TreeController < Projects::BaseTreeController class Projects::TreeController < Projects::ApplicationController
def show 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 tree.entries.empty?
if @repository.blob_at(@commit.id, @path) if @repository.blob_at(@commit.id, @path)
redirect_to project_blob_path(@project, File.join(@ref, @path)) and return redirect_to project_blob_path(@project, File.join(@ref, @path)) and return
......
class UsersController < ApplicationController class UsersController < ApplicationController
skip_before_filter :authenticate_user!, only: [:show] skip_before_filter :authenticate_user!
before_filter :set_user
layout :determine_layout layout :determine_layout
def show def show
@user = User.find_by_username!(params[:username])
unless current_user || @user.public_profile?
return authenticate_user!
end
# Projects user can view # 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. @projects = @user.personal_projects.
where(id: authorized_projects_ids) where(id: authorized_projects_ids)
...@@ -30,6 +26,19 @@ class UsersController < ApplicationController ...@@ -30,6 +26,19 @@ class UsersController < ApplicationController
end end
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 def determine_layout
if current_user if current_user
'navless' 'navless'
...@@ -37,4 +46,14 @@ class UsersController < ApplicationController ...@@ -37,4 +46,14 @@ class UsersController < ApplicationController
'public_users' 'public_users'
end end
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 end
...@@ -56,8 +56,6 @@ module ApplicationHelper ...@@ -56,8 +56,6 @@ module ApplicationHelper
image_tag project.avatar.url, options image_tag project.avatar.url, options
elsif project.avatar_in_git elsif project.avatar_in_git
image_tag project_avatar_path(project), options image_tag project_avatar_path(project), options
elsif options[:only_uploaded]
image_tag '/assets/no_project_icon.png', options
else # generated icon else # generated icon
project_identicon(project, options) project_identicon(project, options)
end end
......
...@@ -19,4 +19,42 @@ module BlobHelper ...@@ -19,4 +19,42 @@ module BlobHelper
def no_highlight_files def no_highlight_files
%w(credits changelog copying copyright license authors) %w(credits changelog copying copyright license authors)
end 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 end
...@@ -16,45 +16,25 @@ module IssuesHelper ...@@ -16,45 +16,25 @@ module IssuesHelper
def url_for_project_issues(project = @project) def url_for_project_issues(project = @project)
return '' if project.nil? return '' if project.nil?
if project.used_default_issues_tracker? || !external_issues_tracker_enabled? project.issues_tracker.project_url
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
end end
def url_for_new_issue(project = @project) def url_for_new_issue(project = @project)
return '' if project.nil? return '' if project.nil?
if project.used_default_issues_tracker? || !external_issues_tracker_enabled? project.issues_tracker.new_issue_url
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
end end
def url_for_issue(issue_iid, project = @project) def url_for_issue(issue_iid, project = @project)
return '' if project.nil? return '' if project.nil?
if project.used_default_issues_tracker? || !external_issues_tracker_enabled? project.issues_tracker.issue_url(issue_iid)
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
end end
def title_for_issue(issue_iid, project = @project) def title_for_issue(issue_iid, project = @project)
return '' if project.nil? return '' if project.nil?
if project.used_default_issues_tracker? if project.default_issues_tracker?
issue = project.issues.where(iid: issue_iid).first issue = project.issues.where(iid: issue_iid).first
return issue.title if issue return issue.title if issue
end end
...@@ -77,11 +57,6 @@ module IssuesHelper ...@@ -77,11 +57,6 @@ module IssuesHelper
ts.html_safe ts.html_safe
end 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 def bulk_update_milestone_options
options_for_select(['None (backlog)']) + options_for_select(['None (backlog)']) +
options_from_collection_for_select(project_active_milestones, 'id', options_from_collection_for_select(project_active_milestones, 'id',
......
...@@ -72,18 +72,6 @@ module ProjectsHelper ...@@ -72,18 +72,6 @@ module ProjectsHelper
@project.milestones.active.order("due_date, title ASC") @project.milestones.active.order("due_date, title ASC")
end 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) def link_to_toggle_star(title, starred, signed_in)
cls = 'star-btn' cls = 'star-btn'
cls << ' disabled' unless signed_in cls << ' disabled' unless signed_in
...@@ -187,7 +175,13 @@ module ProjectsHelper ...@@ -187,7 +175,13 @@ module ProjectsHelper
"Issues - " + title "Issues - " + title
end end
elsif current_controller?(:blob) 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) elsif current_controller?(:commits)
"Commits at #{@ref} - " + title "Commits at #{@ref} - " + title
elsif current_controller?(:merge_requests) elsif current_controller?(:merge_requests)
...@@ -257,7 +251,7 @@ module ProjectsHelper ...@@ -257,7 +251,7 @@ module ProjectsHelper
end end
def github_import_enabled? def github_import_enabled?
Gitlab.config.omniauth.enabled && enabled_oauth_providers.include?(:github) enabled_oauth_providers.include?(:github)
end end
end end
...@@ -64,32 +64,6 @@ module TreeHelper ...@@ -64,32 +64,6 @@ module TreeHelper
::Gitlab::GitAccess.can_push_to_branch?(current_user, project, ref) ::Gitlab::GitAccess.can_push_to_branch?(current_user, project, ref)
end 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) def tree_breadcrumbs(tree, max_links = 2)
if @path.present? if @path.present?
part_path = "" part_path = ""
...@@ -121,16 +95,4 @@ module TreeHelper ...@@ -121,16 +95,4 @@ module TreeHelper
return tree.name return tree.name
end end
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 end
...@@ -25,6 +25,9 @@ class Group < Namespace ...@@ -25,6 +25,9 @@ class Group < Namespace
mount_uploader :avatar, AttachmentUploader mount_uploader :avatar, AttachmentUploader
after_create :post_create_hook
after_destroy :post_destroy_hook
def human_name def human_name
name name
end end
...@@ -74,6 +77,18 @@ class Group < Namespace ...@@ -74,6 +77,18 @@ class Group < Namespace
projects.public_only.any? projects.public_only.any?
end 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 class << self
def search(query) def search(query)
where("LOWER(namespaces.name) LIKE :query", query: "%#{query.downcase}%") where("LOWER(namespaces.name) LIKE :query", query: "%#{query.downcase}%")
......
...@@ -27,8 +27,9 @@ class GroupMember < Member ...@@ -27,8 +27,9 @@ class GroupMember < Member
scope :with_group, ->(group) { where(source_id: group.id) } scope :with_group, ->(group) { where(source_id: group.id) }
scope :with_user, ->(user) { where(user_id: user.id) } scope :with_user, ->(user) { where(user_id: user.id) }
after_create :notify_create after_create :post_create_hook
after_update :notify_update after_update :notify_update
after_destroy :post_destroy_hook
def self.access_level_roles def self.access_level_roles
Gitlab::Access.options_with_owner Gitlab::Access.options_with_owner
...@@ -42,8 +43,9 @@ class GroupMember < Member ...@@ -42,8 +43,9 @@ class GroupMember < Member
access_level access_level
end end
def notify_create def post_create_hook
notification_service.new_group_member(self) notification_service.new_group_member(self)
system_hook_service.execute_hooks_for(self, :create)
end end
def notify_update def notify_update
...@@ -52,6 +54,14 @@ class GroupMember < Member ...@@ -52,6 +54,14 @@ class GroupMember < Member
end end
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 def notification_service
NotificationService.new NotificationService.new
end end
......
...@@ -74,7 +74,13 @@ class Project < ActiveRecord::Base ...@@ -74,7 +74,13 @@ class Project < ActiveRecord::Base
has_one :bamboo_service, dependent: :destroy has_one :bamboo_service, dependent: :destroy
has_one :teamcity_service, dependent: :destroy has_one :teamcity_service, dependent: :destroy
has_one :pushover_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 has_one :forked_from_project, through: :forked_project_link
# Merge Requests for target project should be removed with it # Merge Requests for target project should be removed with it
has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id' has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id'
...@@ -144,8 +150,6 @@ class Project < ActiveRecord::Base ...@@ -144,8 +150,6 @@ class Project < ActiveRecord::Base
scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) } scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) }
scope :non_archived, -> { where(archived: false) } 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 state_machine :import_status, initial: :none do
event :import_start do event :import_start do
transition [:none, :finished] => :started transition [:none, :finished] => :started
...@@ -305,19 +309,43 @@ class Project < ActiveRecord::Base ...@@ -305,19 +309,43 @@ class Project < ActiveRecord::Base
end end
def issue_exists?(issue_id) def issue_exists?(issue_id)
if used_default_issues_tracker? if default_issues_tracker?
self.issues.where(iid: issue_id).first.present? self.issues.where(iid: issue_id).first.present?
else else
true true
end end
end end
def used_default_issues_tracker? def default_issue_tracker
self.issues_tracker == Project.issues_tracker.default_value 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 end
def can_have_issues_tracker_id? def can_have_issues_tracker_id?
self.issues_enabled && !self.used_default_issues_tracker? self.issues_enabled && !self.default_issues_tracker?
end end
def build_missing_services def build_missing_services
...@@ -332,7 +360,7 @@ class Project < ActiveRecord::Base ...@@ -332,7 +360,7 @@ class Project < ActiveRecord::Base
def available_services_names def available_services_names
%w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla %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 end
def gitlab_ci? 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 ...@@ -139,8 +139,10 @@ class Repository
def graph_log def graph_log
Rails.cache.fetch(cache_key(:graph_log)) do 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) ref: root_ref)
commits.map do |rugged_commit| commits.map do |rugged_commit|
commit = Gitlab::Git::Commit.new(rugged_commit) commit = Gitlab::Git::Commit.new(rugged_commit)
...@@ -148,12 +150,32 @@ class Repository ...@@ -148,12 +150,32 @@ class Repository
author_name: commit.author_name.force_encoding('UTF-8'), author_name: commit.author_name.force_encoding('UTF-8'),
author_email: commit.author_email.force_encoding('UTF-8'), author_email: commit.author_email.force_encoding('UTF-8'),
additions: commit.stats.additions, additions: commit.stats.additions,
deletions: commit.stats.deletions deletions: commit.stats.deletions,
} }
end end
end 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) def cache_key(type)
"#{type}:#{path_with_namespace}" "#{type}:#{path_with_namespace}"
end end
......
...@@ -26,6 +26,8 @@ class Service < ActiveRecord::Base ...@@ -26,6 +26,8 @@ class Service < ActiveRecord::Base
validates :project_id, presence: true validates :project_id, presence: true
scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') }
def activated? def activated?
active active
end end
...@@ -86,4 +88,12 @@ class Service < ActiveRecord::Base ...@@ -86,4 +88,12 @@ class Service < ActiveRecord::Base
def async_execute(data) def async_execute(data)
Sidekiq::Client.enqueue(ProjectServiceWorker, id, data) Sidekiq::Client.enqueue(ProjectServiceWorker, id, data)
end 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 end
...@@ -9,10 +9,6 @@ module Files ...@@ -9,10 +9,6 @@ module Files
return error("You are not allowed to create file in this branch") return error("You are not allowed to create file in this branch")
end 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_name = File.basename(path)
file_path = path file_path = path
...@@ -23,11 +19,20 @@ module Files ...@@ -23,11 +19,20 @@ module Files
) )
end end
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
blob = repository.blob_at_branch(ref, file_path) blob = repository.blob_at_branch(ref, file_path)
if blob if blob
return error("Your changes could not be committed, because file with such name exists") return error("Your changes could not be committed, because file with such name exists")
end end
end
new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, file_path) new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, file_path)
created_successfully = new_file_action.commit!( created_successfully = new_file_action.commit!(
......
...@@ -2,9 +2,9 @@ module Issues ...@@ -2,9 +2,9 @@ module Issues
class CloseService < Issues::BaseService class CloseService < Issues::BaseService
def execute(issue, commit = nil) def execute(issue, commit = nil)
if issue.close if issue.close
notification_service.close_issue(issue, current_user)
event_service.close_issue(issue, current_user) event_service.close_issue(issue, current_user)
create_note(issue, commit) create_note(issue, commit)
notification_service.close_issue(issue, current_user)
execute_hooks(issue, 'close') execute_hooks(issue, 'close')
end end
......
...@@ -23,8 +23,8 @@ module Issues ...@@ -23,8 +23,8 @@ module Issues
end end
if issue.previous_changes.include?('assignee_id') if issue.previous_changes.include?('assignee_id')
notification_service.reassigned_issue(issue, current_user)
create_assignee_note(issue) create_assignee_note(issue)
notification_service.reassigned_issue(issue, current_user)
end end
issue.notice_added_references(issue.project, current_user) issue.notice_added_references(issue.project, current_user)
......
...@@ -11,9 +11,9 @@ module MergeRequests ...@@ -11,9 +11,9 @@ module MergeRequests
if Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message) if Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message)
merge_request.merge merge_request.merge
notification_service.merge_mr(merge_request, current_user)
create_merge_event(merge_request, current_user) create_merge_event(merge_request, current_user)
create_note(merge_request) create_note(merge_request)
notification_service.merge_mr(merge_request, current_user)
execute_hooks(merge_request) execute_hooks(merge_request)
true true
......
...@@ -7,8 +7,8 @@ module MergeRequests ...@@ -7,8 +7,8 @@ module MergeRequests
if merge_request.close if merge_request.close
event_service.close_mr(merge_request, current_user) event_service.close_mr(merge_request, current_user)
notification_service.close_mr(merge_request, current_user)
create_note(merge_request) create_note(merge_request)
notification_service.close_mr(merge_request, current_user)
execute_hooks(merge_request, 'close') execute_hooks(merge_request, 'close')
end end
......
...@@ -9,9 +9,9 @@ module MergeRequests ...@@ -9,9 +9,9 @@ module MergeRequests
def execute(merge_request, commit_message) def execute(merge_request, commit_message)
merge_request.merge merge_request.merge
notification_service.merge_mr(merge_request, current_user)
create_merge_event(merge_request, current_user) create_merge_event(merge_request, current_user)
create_note(merge_request) create_note(merge_request)
notification_service.merge_mr(merge_request, current_user)
execute_hooks(merge_request, 'merge') execute_hooks(merge_request, 'merge')
true true
......
...@@ -3,8 +3,8 @@ module MergeRequests ...@@ -3,8 +3,8 @@ module MergeRequests
def execute(merge_request) def execute(merge_request)
if merge_request.reopen if merge_request.reopen
event_service.reopen_mr(merge_request, current_user) event_service.reopen_mr(merge_request, current_user)
notification_service.reopen_mr(merge_request, current_user)
create_note(merge_request) create_note(merge_request)
notification_service.reopen_mr(merge_request, current_user)
execute_hooks(merge_request, 'reopen') execute_hooks(merge_request, 'reopen')
merge_request.reload_code merge_request.reload_code
merge_request.mark_as_unchecked merge_request.mark_as_unchecked
......
...@@ -33,8 +33,8 @@ module MergeRequests ...@@ -33,8 +33,8 @@ module MergeRequests
end end
if merge_request.previous_changes.include?('assignee_id') if merge_request.previous_changes.include?('assignee_id')
notification_service.reassigned_merge_request(merge_request, current_user)
create_assignee_note(merge_request) create_assignee_note(merge_request)
notification_service.reassigned_merge_request(merge_request, current_user)
end end
merge_request.notice_added_references(merge_request.project, current_user) merge_request.notice_added_references(merge_request.project, current_user)
......
...@@ -314,15 +314,7 @@ class NotificationService ...@@ -314,15 +314,7 @@ class NotificationService
end end
def new_resource_email(target, project, method) def new_resource_email(target, project, method)
if target.respond_to?(:participants) recipients = build_recipients(target, project)
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.delete(target.author) recipients.delete(target.author)
recipients.each do |recipient| recipients.each do |recipient|
...@@ -331,9 +323,7 @@ class NotificationService ...@@ -331,9 +323,7 @@ class NotificationService
end end
def close_resource_email(target, project, current_user, method) def close_resource_email(target, project, current_user, method)
recipients = reject_muted_users([target.author, target.assignee], project) recipients = build_recipients(target, project)
recipients = reject_mention_users(recipients, project)
recipients = recipients.concat(project_watchers(project)).uniq
recipients.delete(current_user) recipients.delete(current_user)
recipients.each do |recipient| recipients.each do |recipient|
...@@ -343,17 +333,7 @@ class NotificationService ...@@ -343,17 +333,7 @@ class NotificationService
def reassign_resource_email(target, project, current_user, method) def reassign_resource_email(target, project, current_user, method)
assignee_id_was = previous_record(target, "assignee_id") assignee_id_was = previous_record(target, "assignee_id")
recipients = build_recipients(target, project)
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.delete(current_user) recipients.delete(current_user)
recipients.each do |recipient| recipients.each do |recipient|
...@@ -362,9 +342,7 @@ class NotificationService ...@@ -362,9 +342,7 @@ class NotificationService
end end
def reopen_resource_email(target, project, current_user, method, status) def reopen_resource_email(target, project, current_user, method, status)
recipients = reject_muted_users([target.author, target.assignee], project) recipients = build_recipients(target, project)
recipients = reject_mention_users(recipients, project)
recipients = recipients.concat(project_watchers(project)).uniq
recipients.delete(current_user) recipients.delete(current_user)
recipients.each do |recipient| recipients.each do |recipient|
...@@ -372,6 +350,20 @@ class NotificationService ...@@ -372,6 +350,20 @@ class NotificationService
end end
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 def mailer
Notify.delay Notify.delay
end end
......
...@@ -60,6 +60,26 @@ class SystemHooksService ...@@ -60,6 +60,26 @@ class SystemHooksService
access_level: model.human_access, access_level: model.human_access,
project_visibility: Project.visibility_levels.key(model.project.visibility_level_field).downcase 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
end end
...@@ -68,6 +88,9 @@ class SystemHooksService ...@@ -68,6 +88,9 @@ class SystemHooksService
when ProjectMember when ProjectMember
return "user_add_to_team" if event == :create return "user_add_to_team" if event == :create
return "user_remove_from_team" if event == :destroy 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 else
"#{model.class.name.downcase}_#{event.to_s}" "#{model.class.name.downcase}_#{event.to_s}"
end end
......
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
%span.light.pull-right %span.light.pull-right
= Milestone.count = Milestone.count
%p %p
Active users last 30 days Users who signed in during last 30 days
%span.light.pull-right %span.light.pull-right
= User.where("current_sign_in_at > ?", 30.days.ago).count = User.where("current_sign_in_at > ?", 30.days.ago).count
.col-md-4 .col-md-4
......
...@@ -12,4 +12,3 @@ ...@@ -12,4 +12,3 @@
= render "events/event/note", event: event = render "events/event/note", event: event
- else - else
= render "events/event/common", event: event = render "events/event/common", event: event
...@@ -5,7 +5,11 @@ ...@@ -5,7 +5,11 @@
.form-group .form-group
= f.label :access_level, "Group Access", class: 'control-label' = 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 .form-actions
= f.submit 'Add users into group', class: "btn btn-create" = f.submit 'Add users into group', class: "btn btn-create"
%ul.sidebar-subnav %ul.sidebar-subnav
= nav_link(path: 'groups#edit') do = 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 %i.fa.fa-pencil-square-o
%span %span
Group Group
= nav_link(path: 'groups#projects') do = 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 %i.fa.fa-folder
%span %span
Projects Projects
...@@ -43,6 +43,6 @@ ...@@ -43,6 +43,6 @@
%i.fa.fa-sign-out %i.fa.fa-sign-out
%li.hidden-xs %li.hidden-xs
= link_to current_user, class: "profile-pic", id: 'profile-pic' do = 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' = render 'shared/outdated_browser'
- if defined?(sidebar) - if defined?(sidebar)
.page-with-sidebar .page-with-sidebar
= render "layouts/broadcast"
.sidebar-wrapper .sidebar-wrapper
= render(sidebar) = render(sidebar)
.content-wrapper .content-wrapper
......
...@@ -2,6 +2,5 @@ ...@@ -2,6 +2,5 @@
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head", title: "Admin area" = render "layouts/head", title: "Admin area"
%body{class: "#{app_theme} #{theme_type} admin", :'data-page' => body_data_page} %body{class: "#{app_theme} #{theme_type} admin", :'data-page' => body_data_page}
= render "layouts/broadcast"
= render "layouts/head_panel", title: "Admin area" = render "layouts/head_panel", title: "Admin area"
= render 'layouts/page', sidebar: 'layouts/nav/admin' = render 'layouts/page', sidebar: 'layouts/nav/admin'
...@@ -2,6 +2,5 @@ ...@@ -2,6 +2,5 @@
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head", title: "Dashboard" = render "layouts/head", title: "Dashboard"
%body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page } %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page }
= render "layouts/broadcast"
= render "layouts/head_panel", title: "Dashboard" = render "layouts/head_panel", title: "Dashboard"
= render 'layouts/page', sidebar: 'layouts/nav/dashboard' = render 'layouts/page', sidebar: 'layouts/nav/dashboard'
...@@ -2,6 +2,5 @@ ...@@ -2,6 +2,5 @@
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head", title: group_head_title = render "layouts/head", title: group_head_title
%body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page}
= render "layouts/broadcast"
= render "layouts/head_panel", title: @group.name = render "layouts/head_panel", title: @group.name
= render 'layouts/page', sidebar: 'layouts/nav/group' = render 'layouts/page', sidebar: 'layouts/nav/group'
...@@ -5,49 +5,50 @@ ...@@ -5,49 +5,50 @@
%span %span
Overview Overview
= nav_link(controller: :projects) do = nav_link(controller: :projects) do
= link_to admin_projects_path do = link_to admin_projects_path, title: 'Projects' do
%i.fa.fa-cube %i.fa.fa-cube
%span %span
Projects Projects
= nav_link(controller: :users) do = nav_link(controller: :users) do
= link_to admin_users_path do = link_to admin_users_path, title: 'Users' do
%i.fa.fa-user %i.fa.fa-user
%span %span
Users Users
= nav_link(controller: :groups) do = nav_link(controller: :groups) do
= link_to admin_groups_path do = link_to admin_groups_path, title: 'Groups' do
%i.fa.fa-group %i.fa.fa-group
%span %span
Groups Groups
= nav_link(controller: :logs) do = nav_link(controller: :logs) do
= link_to admin_logs_path do = link_to admin_logs_path, title: 'Logs' do
%i.fa.fa-file-text %i.fa.fa-file-text
%span %span
Logs Logs
= nav_link(controller: :broadcast_messages) do = 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 %i.fa.fa-bullhorn
%span %span
Messages Messages
= nav_link(controller: :hooks) do = nav_link(controller: :hooks) do
= link_to admin_hooks_path do = link_to admin_hooks_path, title: 'Hooks' do
%i.fa.fa-external-link %i.fa.fa-external-link
%span %span
Hooks Hooks
= nav_link(controller: :background_jobs) do = 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 %i.fa.fa-cog
%span %span
Background Jobs 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 = nav_link(controller: :applications) do
= link_to admin_applications_path do = link_to admin_applications_path, title: 'Applications' do
%i.fa.fa-cloud %i.fa.fa-cloud
%span %span
Applications 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 @@ ...@@ -5,24 +5,24 @@
%span %span
Activity Activity
= nav_link(path: 'dashboard#projects') do = 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 %i.fa.fa-cube
%span %span
Projects Projects
= nav_link(path: 'dashboard#issues') do = 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 %i.fa.fa-exclamation-circle
%span %span
Issues Issues
%span.count= current_user.assigned_issues.opened.count %span.count= current_user.assigned_issues.opened.count
= nav_link(path: 'dashboard#merge_requests') do = 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 %i.fa.fa-tasks
%span %span
Merge Requests Merge Requests
%span.count= current_user.assigned_merge_requests.opened.count %span.count= current_user.assigned_merge_requests.opened.count
= nav_link(controller: :help) do = nav_link(controller: :help) do
= link_to help_path do = link_to help_path, title: 'Help' do
%i.fa.fa-question-circle %i.fa.fa-question-circle
%span %span
Help Help
......
...@@ -6,33 +6,33 @@ ...@@ -6,33 +6,33 @@
Activity Activity
- if current_user - if current_user
= nav_link(controller: [:group, :milestones]) do = 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 %i.fa.fa-clock-o
%span %span
Milestones Milestones
= nav_link(path: 'groups#issues') do = 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 %i.fa.fa-exclamation-circle
%span %span
Issues Issues
- if current_user - if current_user
%span.count= Issue.opened.of_group(@group).count %span.count= Issue.opened.of_group(@group).count
= nav_link(path: 'groups#merge_requests') do = 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 %i.fa.fa-tasks
%span %span
Merge Requests Merge Requests
- if current_user - if current_user
%span.count= MergeRequest.opened.of_group(@group).count %span.count= MergeRequest.opened.of_group(@group).count
= nav_link(path: 'groups#members') do = 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 %i.fa.fa-users
%span %span
Members Members
- if can?(current_user, :manage_group, @group) - if can?(current_user, :manage_group, @group)
= nav_link(html_options: { class: "#{"active" if group_settings_page?} separate-item" }) do = 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 %i.fa.fa-cogs
%span %span
Settings Settings
......
...@@ -5,52 +5,51 @@ ...@@ -5,52 +5,51 @@
%span %span
Profile Profile
= nav_link(controller: :accounts) do = nav_link(controller: :accounts) do
= link_to profile_account_path do = link_to profile_account_path, title: 'Account' do
%i.fa.fa-gear %i.fa.fa-gear
%span %span
Account Account
= nav_link(path: ['profiles#applications', 'applications#edit', 'applications#show', 'applications#new']) do = 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 %i.fa.fa-cloud
%span %span
Applications Applications
= nav_link(controller: :emails) do = nav_link(controller: :emails) do
= link_to profile_emails_path do = link_to profile_emails_path, title: 'Emails' do
%i.fa.fa-envelope-o %i.fa.fa-envelope-o
%span %span
Emails Emails
%span.count= current_user.emails.count + 1 %span.count= current_user.emails.count + 1
- unless current_user.ldap_user? - unless current_user.ldap_user?
= nav_link(controller: :passwords) do = 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 %i.fa.fa-lock
%span %span
Password Password
= nav_link(controller: :notifications) do = nav_link(controller: :notifications) do
= link_to profile_notifications_path do = link_to profile_notifications_path, title: 'Notifications' do
%i.fa.fa-inbox %i.fa.fa-inbox
%span %span
Notifications Notifications
= nav_link(controller: :keys) do = nav_link(controller: :keys) do
= link_to profile_keys_path do = link_to profile_keys_path, title: 'SSH Keys' do
%i.fa.fa-key %i.fa.fa-key
%span %span
SSH Keys SSH Keys
%span.count= current_user.keys.count %span.count= current_user.keys.count
= nav_link(path: 'profiles#design') do = nav_link(path: 'profiles#design') do
= link_to design_profile_path do = link_to design_profile_path, title: 'Design' do
%i.fa.fa-image %i.fa.fa-image
%span %span
Design Design
= nav_link(controller: :groups) do = nav_link(controller: :groups) do
= link_to profile_groups_path do = link_to profile_groups_path, title: 'Groups' do
%i.fa.fa-group %i.fa.fa-group
%span %span
Groups Groups
= nav_link(path: 'profiles#history') do = nav_link(path: 'profiles#history') do
= link_to history_profile_path do = link_to history_profile_path, title: 'History' do
%i.fa.fa-history %i.fa.fa-history
%span %span
History History
...@@ -6,45 +6,44 @@ ...@@ -6,45 +6,44 @@
Project Project
- if project_nav_tab? :files - if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree)) do = 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 %i.fa.fa-files-o
%span %span
Files Files
- if project_nav_tab? :commits - if project_nav_tab? :commits
= nav_link(controller: %w(commit commits compare repositories tags branches)) do = 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 %i.fa.fa-history
%span %span
Commits Commits
- if project_nav_tab? :network - if project_nav_tab? :network
= nav_link(controller: %w(network)) do = 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 %i.fa.fa-code-fork
%span %span
Network Network
- if project_nav_tab? :graphs - if project_nav_tab? :graphs
= nav_link(controller: %w(graphs)) do = 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 %i.fa.fa-area-chart
%span %span
Graphs Graphs
- if project_nav_tab? :issues - if project_nav_tab? :issues
= nav_link(controller: %w(issues milestones labels)) do = 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 %i.fa.fa-exclamation-circle
%span %span
Issues Issues
- if @project.used_default_issues_tracker? - if @project.default_issues_tracker?
%span.count.issue_counter= @project.issues.opened.count %span.count.issue_counter= @project.issues.opened.count
- if project_nav_tab? :merge_requests - if project_nav_tab? :merge_requests
= nav_link(controller: :merge_requests) do = 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 %i.fa.fa-tasks
%span %span
Merge Requests Merge Requests
...@@ -52,21 +51,21 @@ ...@@ -52,21 +51,21 @@
- if project_nav_tab? :wiki - if project_nav_tab? :wiki
= nav_link(controller: :wikis) do = 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 %i.fa.fa-book
%span %span
Wiki Wiki
- if project_nav_tab? :snippets - if project_nav_tab? :snippets
= nav_link(controller: :snippets) do = 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 %i.fa.fa-file-text-o
%span %span
Snippets Snippets
- if project_nav_tab? :settings - if project_nav_tab? :settings
= nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do = 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 %i.fa.fa-cogs
%span %span
Settings Settings
......
...@@ -2,6 +2,5 @@ ...@@ -2,6 +2,5 @@
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head", title: "Profile" = render "layouts/head", title: "Profile"
%body{class: "#{app_theme} #{theme_type} profile", :'data-page' => body_data_page} %body{class: "#{app_theme} #{theme_type} profile", :'data-page' => body_data_page}
= render "layouts/broadcast"
= render "layouts/head_panel", title: "Profile" = render "layouts/head_panel", title: "Profile"
= render 'layouts/page', sidebar: 'layouts/nav/profile' = render 'layouts/page', sidebar: 'layouts/nav/profile'
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head", title: @project.name_with_namespace = 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 } %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/head_panel", title: project_title(@project)
= render "layouts/init_auto_complete" = render "layouts/init_auto_complete"
- @project_settings_nav = true - @project_settings_nav = true
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head", title: project_head_title = render "layouts/head", title: project_head_title
%body{class: "#{app_theme} #{theme_type} project", :'data-page' => body_data_page, :'data-project-id' => @project.id } %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/head_panel", title: project_title(@project)
= render "layouts/init_auto_complete" = render "layouts/init_auto_complete"
= render 'layouts/page', sidebar: 'layouts/nav/project' = render 'layouts/page', sidebar: 'layouts/nav/project'
...@@ -2,6 +2,5 @@ ...@@ -2,6 +2,5 @@
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head", title: group_head_title = render "layouts/head", title: group_head_title
%body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} %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/public_head_panel", title: "group: #{@group.name}"
= render 'layouts/page', sidebar: 'layouts/nav/group' = render 'layouts/page', sidebar: 'layouts/nav/group'
...@@ -2,6 +2,5 @@ ...@@ -2,6 +2,5 @@
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head", title: @project.name_with_namespace = render "layouts/head", title: @project.name_with_namespace
%body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} %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/public_head_panel", title: project_title(@project)
= render 'layouts/page', sidebar: 'layouts/nav/project' = render 'layouts/page', sidebar: 'layouts/nav/project'
...@@ -2,6 +2,5 @@ ...@@ -2,6 +2,5 @@
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head", title: @title = render "layouts/head", title: @title
%body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} %body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page}
= render "layouts/broadcast"
= render "layouts/public_head_panel", title: @title = render "layouts/public_head_panel", title: @title
= render 'layouts/page' = render 'layouts/page'
- empty_repo = @project.empty_repo? - empty_repo = @project.empty_repo?
.project-home-panel{:class => ("empty-project" if 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-row
.project-home-desc .project-home-desc
= project_icon(@project.to_param, alt: '', class: 'avatar s32')
- if @project.description.present? - if @project.description.present?
= escaped_autolink(@project.description) = escaped_autolink(@project.description)
- if can?(current_user, :admin_project, @project) - if can?(current_user, :admin_project, @project)
......
%ul.project-settings-nav.sidebar-subnav %ul.project-settings-nav.sidebar-subnav
= nav_link(path: 'projects#edit') do = 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 %i.fa.fa-pencil-square-o
%span %span
Project Project
= nav_link(controller: [:team_members, :teams]) do = 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 %i.fa.fa-users
%span %span
Members Members
= nav_link(controller: :deploy_keys) do = 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 %i.fa.fa-key
%span %span
Deploy Keys Deploy Keys
= nav_link(controller: :hooks) do = 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 %i.fa.fa-link
%span %span
Web Hooks Web Hooks
= nav_link(controller: :services) do = 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 %i.fa.fa-cogs
%span %span
Services Services
= nav_link(controller: :protected_branches) do = 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 %i.fa.fa-lock
%span %span
Protected branches Protected branches
.file-holder.file .file-holder.file
.file-title .file-title
%i.icon-file .editor-ref
%span.file_name %i.fa.fa-code-fork
%span.monospace.light #{ref} = ref
- if local_assigns[:path] %span.editor-file-name
= ': ' + local_assigns[:path] - 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 .file-content.code
%pre.js-edit-mode-pane#editor %pre.js-edit-mode-pane#editor
= params[:content] || local_assigns[:blob_data] = 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 @@ ...@@ -51,15 +51,6 @@
= f.check_box :issues_enabled = f.check_box :issues_enabled
%span.descr Lightweight issue tracking system for this project %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 .form-group
= f.label :merge_requests_enabled, "Merge Requests", class: 'control-label' = f.label :merge_requests_enabled, "Merge Requests", class: 'control-label'
.col-sm-10 .col-sm-10
...@@ -89,8 +80,6 @@ ...@@ -89,8 +80,6 @@
.col-sm-10 .col-sm-10
- if @project.avatar? - if @project.avatar?
= project_icon(@project.to_param, alt: '', class: 'avatar s160') = project_icon(@project.to_param, alt: '', class: 'avatar s160')
- else
= project_icon(@project.to_param, alt: '', class: 'avatar s160', only_uploaded: true)
%p.light %p.light
- if @project.avatar_in_git - if @project.avatar_in_git
Project avatar in repository: #{ @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 @@ ...@@ -3,6 +3,17 @@
= render "home_panel" = 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 %div.git-empty
%fieldset %fieldset
%legend Git global setup %legend Git global setup
......
%h3.page-title Fork project %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 %hr
.fork-namespaces .fork-namespaces
......
...@@ -45,10 +45,17 @@ ...@@ -45,10 +45,17 @@
.automerge_widget.cannot_be_merged.hide .automerge_widget.cannot_be_merged.hide
%h4 %h4
This request can't be merged with GitLab. This request can't be merged with GitLab.
%p
You should do it manually with You should do it manually with
%strong %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 .automerge_widget.unchecked
%p %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 @@ ...@@ -17,7 +17,12 @@
%p 2. Set access level for them %p 2. Set access level for them
.form-group .form-group
= f.label :access_level, "Project Access", class: 'control-label' = 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 .form-actions
= f.submit 'Add users', class: "btn btn-create" = f.submit 'Add users', class: "btn btn-create"
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
= link_to title, '#' = link_to title, '#'
- if current_user && can_push_branch?(@project, @ref) - if current_user && can_push_branch?(@project, @ref)
%li %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 %small
%i.fa.fa-plus %i.fa.fa-plus
......
%h4 Calendar:
#cal-heatmap.calendar
:javascript
new calendar(
#{@timestamps.to_json},
#{@starting_year},
#{@starting_month}
);
...@@ -18,6 +18,11 @@ ...@@ -18,6 +18,11 @@
%h4 Groups: %h4 Groups:
= render 'groups', groups: @groups = render 'groups', groups: @groups
%hr %hr
.user-calendar
%h4.center.light
%i.fa.fa-spinner.fa-spin
%hr
%h4 %h4
User Activity: User Activity:
...@@ -32,3 +37,8 @@ ...@@ -32,3 +37,8 @@
= render 'profile', user: @user = render 'profile', user: @user
- if @projects.present? - if @projects.present?
= render 'projects', projects: @projects = render 'projects', projects: @projects
:coffeescript
$ ->
$(".user-calendar").load("#{user_calendar_path}")
...@@ -153,9 +153,9 @@ production: &base ...@@ -153,9 +153,9 @@ production: &base
label: 'LDAP' label: 'LDAP'
host: '_your_ldap_server' host: '_your_ldap_server'
port: 636 port: 389
uid: 'sAMAccountName' 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' bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
password: '_the_password_of_the_bind_user' password: '_the_password_of_the_bind_user'
......
...@@ -157,6 +157,9 @@ Gitlab::Application.routes.draw do ...@@ -157,6 +157,9 @@ Gitlab::Application.routes.draw do
end end
end end
get 'u/:username/calendar' => 'users#calendar', as: :user_calendar,
constraints: { username: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }
get '/u/:username' => 'users#show', as: :user, get '/u/:username' => 'users#show', as: :user,
constraints: { username: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } constraints: { username: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }
...@@ -211,17 +214,20 @@ Gitlab::Application.routes.draw do ...@@ -211,17 +214,20 @@ Gitlab::Application.routes.draw do
end end
scope module: :projects do 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 resources :blob, only: [:show, :destroy], constraints: { id: /.+/, format: false } do
get :diff, on: :member get :diff, on: :member
end end
resources :raw, only: [:show], constraints: {id: /.+/} resources :raw, only: [:show], constraints: {id: /.+/}
resources :tree, only: [:show], constraints: {id: /.+/, format: /(html|js)/ } 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] 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 :commit, only: [:show], constraints: {id: /[[:alnum:]]{6,40}/}
resources :commits, only: [:show], constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/} resources :commits, only: [:show], constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/}
resources :compare, only: [:index, :create] 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 ...@@ -435,6 +435,7 @@ ActiveRecord::Schema.define(version: 20150125163100) do
t.boolean "hide_no_ssh_key", default: false t.boolean "hide_no_ssh_key", default: false
t.string "website_url", default: "", null: false t.string "website_url", default: "", null: false
t.string "github_access_token" t.string "github_access_token"
t.string "gitlab_access_token"
end end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
......
...@@ -322,6 +322,31 @@ Parameters: ...@@ -322,6 +322,31 @@ Parameters:
- `title` (required) - new SSH Key's title - `title` (required) - new SSH Key's title
- `key` (required) - new SSH key - `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 ## Add SSH key for user
Create new key owned by specified user. Available only for admin 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 ...@@ -29,9 +29,9 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server
label: 'LDAP' label: 'LDAP'
host: '_your_ldap_server' host: '_your_ldap_server'
port: 636 port: 389
uid: 'sAMAccountName' 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' bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
password: '_the_password_of_the_bind_user' password: '_the_password_of_the_bind_user'
...@@ -76,6 +76,9 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server ...@@ -76,6 +76,9 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server
EOS 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`: If you are using a GitLab installation from source you can find the LDAP settings in `/home/git/gitlab/config/gitlab.yml`:
``` ```
......
# System hooks # 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. 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 ...@@ -50,6 +50,7 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser
"project_path": "storecloud", "project_path": "storecloud",
"user_email": "johnsmith@gmail.com", "user_email": "johnsmith@gmail.com",
"user_name": "John Smith", "user_name": "John Smith",
"user_id": 41,
"project_visibility": "private", "project_visibility": "private",
} }
``` ```
...@@ -66,6 +67,7 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser ...@@ -66,6 +67,7 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser
"project_path": "storecloud", "project_path": "storecloud",
"user_email": "johnsmith@gmail.com", "user_email": "johnsmith@gmail.com",
"user_name": "John Smith", "user_name": "John Smith",
"user_id": 41,
"project_visibility": "private", "project_visibility": "private",
} }
``` ```
...@@ -117,3 +119,62 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser ...@@ -117,3 +119,62 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser
"id": 4 "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 \ ...@@ -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 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. # If you run GitLab Enterprise Edition point it to a location where you have downloaded it.
RUN TMP_FILE=$(mktemp); \ 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 \ && dpkg -i $TMP_FILE \
&& rm -f $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 ...@@ -34,6 +34,19 @@ Feature: Project Source Browse Files
Then I am redirected to the new file Then I am redirected to the new file
And I should see its new content 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 @javascript
Scenario: If I enter an illegal file name I see an error message Scenario: If I enter an illegal file name I see an error message
Given I click on "new file" link in repo 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,8 +173,10 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -173,8 +173,10 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
merge!: true, merge!: true,
) )
within '.can_be_merged' do
click_button "Accept Merge Request" click_button "Accept Merge Request"
end end
end
step 'I should see merged request' do step 'I should see merged request' do
within '.issue-box' do within '.issue-box' do
......
...@@ -58,7 +58,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -58,7 +58,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step 'I can edit code' do step 'I can edit code' do
set_new_content set_new_content
evaluate_script('editor.getValue()').should == new_gitignore_content evaluate_script('blob.editor.getValue()').should == new_gitignore_content
end end
step 'I edit code' do step 'I edit code' do
...@@ -78,7 +78,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -78,7 +78,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end end
step 'I click link "Diff"' do step 'I click link "Diff"' do
click_link 'Diff' click_link 'Preview changes'
end end
step 'I click on "Commit Changes"' do step 'I click on "Commit Changes"' do
...@@ -103,7 +103,6 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -103,7 +103,6 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step 'I can see new file page' do step 'I can see new file page' do
page.should have_content "New file" page.should have_content "New file"
page.should have_content "File name"
page.should have_content "Commit message" page.should have_content "Commit message"
end end
...@@ -167,10 +166,21 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -167,10 +166,21 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
expect(page).to have_content('Your changes could not be committed') expect(page).to have_content('Your changes could not be committed')
end 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 private
def set_new_content def set_new_content
execute_script("editor.setValue('#{new_gitignore_content}')") execute_script("blob.editor.setValue('#{new_gitignore_content}')")
end end
# Content of the gitignore file on the seed repository. # Content of the gitignore file on the seed repository.
......
...@@ -284,11 +284,11 @@ module SharedPaths ...@@ -284,11 +284,11 @@ module SharedPaths
end end
step 'I am on the new file page' do 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 end
step 'I am on the ".gitignore" edit file page' do 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'))) @project, File.join(root_ref, '.gitignore')))
end end
......
...@@ -28,6 +28,10 @@ module SharedProject ...@@ -28,6 +28,10 @@ module SharedProject
@project.team << [@user, :master] @project.team << [@user, :master]
end 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 step 'project "Shop" has push event' do
@project = Project.find_by(name: "Shop") @project = Project.find_by(name: "Shop")
......
...@@ -58,11 +58,13 @@ module API ...@@ -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 # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used
# Example Request: # Example Request:
# GET /projects/:id/repository/tree # 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' ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
path = params[:path] || nil path = params[:path] || nil
commit = user_project.repository.commit(ref) commit = user_project.repository.commit(ref)
not_found!('Tree') unless commit
tree = user_project.repository.tree(commit.id, path) tree = user_project.repository.tree(commit.id, path)
present tree.sorted_entries, with: Entities::RepoTreeObject present tree.sorted_entries, with: Entities::RepoTreeObject
...@@ -100,14 +102,18 @@ module API ...@@ -100,14 +102,18 @@ module API
# sha (required) - The blob's sha # sha (required) - The blob's sha
# Example Request: # Example Request:
# GET /projects/:id/repository/raw_blobs/:sha # GET /projects/:id/repository/raw_blobs/:sha
get ":id/repository/raw_blobs/:sha" do get ':id/repository/raw_blobs/:sha' do
ref = params[:sha] ref = params[:sha]
repo = user_project.repository repo = user_project.repository
begin
blob = Gitlab::Git::Blob.raw(repo, ref) 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 env['api.format'] = :txt
...@@ -122,13 +128,23 @@ module API ...@@ -122,13 +128,23 @@ module API
# sha (optional) - the commit sha to download defaults to the tip of the default branch # sha (optional) - the commit sha to download defaults to the tip of the default branch
# Example Request: # Example Request:
# GET /projects/:id/repository/archive # 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 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) if file_path && File.exists?(file_path)
data = File.open(file_path, 'rb').read 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 content_type MIME::Types.type_for(file_path).first.content_type
env['api.format'] = :binary env['api.format'] = :binary
present data present data
...@@ -161,7 +177,12 @@ module API ...@@ -161,7 +177,12 @@ module API
get ':id/repository/contributors' do get ':id/repository/contributors' do
authorize! :download_code, user_project 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 end
end end
......
# Module providing methods for dealing with separating a tree-ish string and a # Module providing methods for dealing with separating a tree-ish string and a
# file path string when combined in a request parameter # file path string when combined in a request parameter
module ExtractsPath module ExtractsPath
extend ActiveSupport::Concern
# Raised when given an invalid file path # Raised when given an invalid file path
class InvalidPathError < StandardError; end 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 # 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. # a filesystem path joined by forward slashes, attempts to separate the two.
# #
......
...@@ -71,8 +71,20 @@ module Grack ...@@ -71,8 +71,20 @@ module Grack
false false
end 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) def authenticate_user(login, password)
user = Gitlab::Auth.new.find(login, password) user = Gitlab::Auth.new.find(login, password)
unless user
user = oauth_access_token_check(login, password)
end
return user if user.present? return user if user.present?
# At this point, we know the credentials were wrong. We let Rack::Attack # 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 ...@@ -73,7 +73,7 @@ module Gitlab
changes = changes.lines if changes.kind_of?(String) changes = changes.lines if changes.kind_of?(String)
# Iterate over all changes to find if user allowed all of them to be applied # 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) status = change_access_check(user, project, change)
unless status.allowed? unless status.allowed?
# If user does not have access to make at least one change - cancel all push # If user does not have access to make at least one change - cancel all push
......
...@@ -40,12 +40,16 @@ module Gitlab ...@@ -40,12 +40,16 @@ module Gitlab
def update_user_attributes def update_user_attributes
gl_user.email = auth_hash.email 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 gl_user
end end
def changed? def changed?
gl_user.changed? gl_user.changed? || gl_user.identities.any?(&:changed?)
end end
def needs_blocking? def needs_blocking?
......
...@@ -208,7 +208,7 @@ module Gitlab ...@@ -208,7 +208,7 @@ module Gitlab
end end
def reference_issue(identifier, project = @project, prefix_text = nil) 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 if project.issue_exists? identifier
url = url_for_issue(identifier, project) url = url_for_issue(identifier, project)
title = title_for_issue(identifier, project) title = title_for_issue(identifier, project)
...@@ -220,10 +220,8 @@ module Gitlab ...@@ -220,10 +220,8 @@ module Gitlab
link_to("#{prefix_text}##{identifier}", url, options) link_to("#{prefix_text}##{identifier}", url, options)
end end
else else
config = Gitlab.config if project.external_issue_tracker.present?
external_issue_tracker = config.issues_tracker[project.issues_tracker] reference_external_issue(identifier, project,
if external_issue_tracker.present?
reference_external_issue(identifier, external_issue_tracker, project,
prefix_text) prefix_text)
end end
end end
...@@ -267,10 +265,10 @@ module Gitlab ...@@ -267,10 +265,10 @@ module Gitlab
end end
end end
def reference_external_issue(identifier, issue_tracker, project = @project, def reference_external_issue(identifier, project = @project,
prefix_text = nil) prefix_text = nil)
url = url_for_issue(identifier, project) url = url_for_issue(identifier, project)
title = issue_tracker['title'] title = project.external_issue_tracker.title
options = html_options.merge( options = html_options.merge(
title: "Issue in #{title}", title: "Issue in #{title}",
......
...@@ -14,7 +14,14 @@ module Gitlab ...@@ -14,7 +14,14 @@ module Gitlab
prepare_satellite!(repo) prepare_satellite!(repo)
# create target branch in satellite at the corresponding commit from bare repo # create target branch in satellite at the corresponding commit from bare repo
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}") 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) file_path_in_satellite = File.join(repo.working_dir, file_path)
dir_name_in_satellite = File.dirname(file_path_in_satellite) dir_name_in_satellite = File.dirname(file_path_in_satellite)
...@@ -38,10 +45,9 @@ module Gitlab ...@@ -38,10 +45,9 @@ module Gitlab
# will raise CommandFailed when commit fails # will raise CommandFailed when commit fails
repo.git.commit(raise: true, timeout: true, a: true, m: commit_message) repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
# push commit back to bare repo # push commit back to bare repo
# will raise CommandFailed when push fails # 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 # everything worked
true 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 ...@@ -76,7 +76,19 @@ FactoryGirl.define do
end end
factory :redmine_project, parent: :project do factory :redmine_project, parent: :project do
issues_tracker { "redmine" } after :create do |project|
issues_tracker_id { "project_name_in_redmine" } 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
end end
...@@ -2,7 +2,7 @@ require 'spec_helper' ...@@ -2,7 +2,7 @@ require 'spec_helper'
describe "Dashboard Feed", feature: true do describe "Dashboard Feed", feature: true do
describe "GET /" do describe "GET /" do
let!(:user) { create(:user) } let!(:user) { create(:user, name: "Jonh") }
context "projects atom feed via private token" do context "projects atom feed via private token" do
it "should render projects atom feed" do it "should render projects atom feed" do
......
...@@ -23,6 +23,7 @@ describe GitlabMarkdownHelper do ...@@ -23,6 +23,7 @@ describe GitlabMarkdownHelper do
@project = project @project = project
@ref = 'markdown' @ref = 'markdown'
@repository = project.repository @repository = project.repository
@request.host = Gitlab.config.gitlab.host
end end
describe "#gfm" do describe "#gfm" do
...@@ -296,10 +297,13 @@ describe GitlabMarkdownHelper do ...@@ -296,10 +297,13 @@ describe GitlabMarkdownHelper do
let(:reference) { "JIRA-#{issue.iid}" } let(:reference) { "JIRA-#{issue.iid}" }
before do before do
issue_tracker_config = { "jira" => { "title" => "JIRA tracker", "issues_url" => "http://jira.example/browse/:id" } } jira = @project.create_jira_service if @project.jira_service.nil?
Gitlab.config.stub(:issues_tracker).and_return(issue_tracker_config) 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"}
@project.stub(:issues_tracker).and_return("jira") jira.update_attributes(properties: properties, active: true)
@project.stub(:issues_tracker_id).and_return("JIRA") end
after do
@project.jira_service.destroy! unless @project.jira_service.nil?
end end
it "should link using a valid id" do it "should link using a valid id" do
......
...@@ -24,7 +24,7 @@ describe IssuesHelper do ...@@ -24,7 +24,7 @@ describe IssuesHelper do
end end
describe :url_for_project_issues do 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 let(:ext_expected) do
project_url.gsub(':project_id', ext_project.id.to_s) project_url.gsub(':project_id', ext_project.id.to_s)
.gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s) .gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s)
...@@ -54,17 +54,16 @@ describe IssuesHelper do ...@@ -54,17 +54,16 @@ describe IssuesHelper do
Gitlab.config.stub(:issues_tracker).and_return(nil) Gitlab.config.stub(:issues_tracker).and_return(nil)
end end
it "should return path to internal tracker" do it "should return path to external tracker" do
url_for_project_issues.should match(polymorphic_path([@project])) url_for_project_issues.should match(ext_expected)
end end
end end
end end
describe :url_for_issue do describe :url_for_issue do
let(:issue_id) { 3 } let(:issues_url) { ext_project.external_issue_tracker.issues_url}
let(:issues_url) { Gitlab.config.issues_tracker.redmine.issues_url}
let(:ext_expected) do 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(':project_id', ext_project.id.to_s)
.gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s) .gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s)
end end
...@@ -78,7 +77,7 @@ describe IssuesHelper do ...@@ -78,7 +77,7 @@ describe IssuesHelper do
it "should return path to external tracker" do it "should return path to external tracker" do
@project = ext_project @project = ext_project
url_for_issue(issue_id).should match(ext_expected) url_for_issue(issue.iid).should match(ext_expected)
end end
it "should return empty string if project nil" do it "should return empty string if project nil" do
...@@ -93,14 +92,14 @@ describe IssuesHelper do ...@@ -93,14 +92,14 @@ describe IssuesHelper do
Gitlab.config.stub(:issues_tracker).and_return(nil) Gitlab.config.stub(:issues_tracker).and_return(nil)
end end
it "should return internal path" do it "should return external path" do
url_for_issue(issue.iid).should match(polymorphic_path([@project, issue])) url_for_issue(issue.iid).should match(ext_expected)
end end
end end
end end
describe :url_for_new_issue do 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 let(:ext_expected) do
issues_url.gsub(':project_id', ext_project.id.to_s) issues_url.gsub(':project_id', ext_project.id.to_s)
.gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s) .gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s)
...@@ -131,7 +130,7 @@ describe IssuesHelper do ...@@ -131,7 +130,7 @@ describe IssuesHelper do
end end
it "should return internal path" do 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 end
end end
......
require 'spec_helper' require 'spec_helper'
describe ProjectsHelper do 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 describe "#project_status_css_class" do
it "returns appropriate class" do it "returns appropriate class" do
project_status_css_class("started").should == "active" project_status_css_class("started").should == "active"
project_status_css_class("failed").should == "danger" project_status_css_class("failed").should == "danger"
project_status_css_class("finished").should == "success" project_status_css_class("finished").should == "success"
end end
end end
end end
...@@ -13,6 +13,23 @@ describe Gitlab::LDAP::User do ...@@ -13,6 +13,23 @@ describe Gitlab::LDAP::User do
double(uid: 'my-uid', provider: 'ldapmain', info: double(info)) double(uid: 'my-uid', provider: 'ldapmain', info: double(info))
end 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 describe :find_or_create do
it "finds the user if already existing" do it "finds the user if already existing" do
existing_user = create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain') existing_user = create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain')
......
...@@ -12,7 +12,6 @@ describe Gitlab::ReferenceExtractor do ...@@ -12,7 +12,6 @@ describe Gitlab::ReferenceExtractor do
end end
it 'extracts JIRA issue references' do 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.analyze('this one talks about issue JIRA-1234', nil)
subject.issues.should == [{ project: nil, id: 'JIRA-1234' }] subject.issues.should == [{ project: nil, id: 'JIRA-1234' }]
end 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 ...@@ -198,16 +198,16 @@ describe Project do
end end
end end
describe :used_default_issues_tracker? do describe :default_issues_tracker? do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:ext_project) { create(:redmine_project) } let(:ext_project) { create(:redmine_project) }
it 'should be true if used internal tracker' do it "should be true if used internal tracker" do
project.used_default_issues_tracker?.should be_true project.default_issues_tracker?.should be_true
end end
it 'should be false if used other tracker' do it "should be false if used other tracker" do
ext_project.used_default_issues_tracker?.should be_false ext_project.default_issues_tracker?.should be_false
end end
end end
......
...@@ -61,5 +61,40 @@ describe SystemHook do ...@@ -61,5 +61,40 @@ describe SystemHook do
project.project_members.destroy_all project.project_members.destroy_all
WebMock.should have_requested(:post, @system_hook.url).with(body: /user_remove_from_team/).once WebMock.should have_requested(:post, @system_hook.url).with(body: /user_remove_from_team/).once
end 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
end end
...@@ -101,6 +101,14 @@ describe API::API, api: true do ...@@ -101,6 +101,14 @@ describe API::API, api: true do
json_response.first['type'].should == 'tree' json_response.first['type'].should == 'tree'
json_response.first['mode'].should == '040000' json_response.first['mode'].should == '040000'
end 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 end
context "unauthorized user" do context "unauthorized user" do
...@@ -145,6 +153,14 @@ describe API::API, api: true do ...@@ -145,6 +153,14 @@ describe API::API, api: true do
get api("/projects/#{project.id}/repository/raw_blobs/#{sample_blob.oid}", user) get api("/projects/#{project.id}/repository/raw_blobs/#{sample_blob.oid}", user)
response.status.should == 200 response.status.should == 200
end 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 end
describe "GET /projects/:id/repository/archive(.:format)?:sha" do describe "GET /projects/:id/repository/archive(.:format)?:sha" do
......
...@@ -430,21 +430,17 @@ describe Projects::TreeController, 'routing' do ...@@ -430,21 +430,17 @@ describe Projects::TreeController, 'routing' do
end end
end end
describe Projects::EditTreeController, 'routing' do describe Projects::BlobController, 'routing' do
it 'to #show' do it 'to #edit' do
get('/gitlab/gitlabhq/edit/master/app/models/project.rb').should( 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', project_id: 'gitlab/gitlabhq',
id: 'master/app/models/project.rb')) 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 end
it 'to #preview' do it 'to #preview' do
post('/gitlab/gitlabhq/edit/master/app/models/project.rb/preview').should( post('/gitlab/gitlabhq/preview/master/app/models/project.rb').should(
route_to('projects/edit_tree#preview', route_to('projects/blob#preview',
project_id: 'gitlab/gitlabhq', project_id: 'gitlab/gitlabhq',
id: 'master/app/models/project.rb')) id: 'master/app/models/project.rb'))
end end
......
...@@ -22,6 +22,7 @@ describe Issues::UpdateService do ...@@ -22,6 +22,7 @@ describe Issues::UpdateService do
} }
@issue = Issues::UpdateService.new(project, user, opts).execute(issue) @issue = Issues::UpdateService.new(project, user, opts).execute(issue)
@issue.reload
end end
it { @issue.should be_valid } it { @issue.should be_valid }
......
...@@ -21,12 +21,14 @@ describe MergeRequests::UpdateService do ...@@ -21,12 +21,14 @@ describe MergeRequests::UpdateService do
state_event: 'close' state_event: 'close'
} }
end end
let(:service) { MergeRequests::UpdateService.new(project, user, opts) } let(:service) { MergeRequests::UpdateService.new(project, user, opts) }
before do before do
service.stub(:execute_hooks) service.stub(:execute_hooks)
@merge_request = service.execute(merge_request) @merge_request = service.execute(merge_request)
@merge_request.reload
end end
it { @merge_request.should be_valid } it { @merge_request.should be_valid }
......
...@@ -187,7 +187,7 @@ describe NotificationService do ...@@ -187,7 +187,7 @@ describe NotificationService do
end end
describe 'Issues' do describe 'Issues' do
let(:issue) { create :issue, assignee: create(:user) } let(:issue) { create :issue, assignee: create(:user), description: 'cc @participant' }
before do before do
build_team(issue.project) build_team(issue.project)
...@@ -197,6 +197,7 @@ describe NotificationService do ...@@ -197,6 +197,7 @@ describe NotificationService do
it do it do
should_email(issue.assignee_id) should_email(issue.assignee_id)
should_email(@u_watcher.id) should_email(@u_watcher.id)
should_email(@u_participant_mentioned.id)
should_not_email(@u_mentioned.id) should_not_email(@u_mentioned.id)
should_not_email(@u_participating.id) should_not_email(@u_participating.id)
should_not_email(@u_disabled.id) should_not_email(@u_disabled.id)
...@@ -222,6 +223,7 @@ describe NotificationService do ...@@ -222,6 +223,7 @@ describe NotificationService do
it 'should email new assignee' do it 'should email new assignee' do
should_email(issue.assignee_id) should_email(issue.assignee_id)
should_email(@u_watcher.id) should_email(@u_watcher.id)
should_email(@u_participant_mentioned.id)
should_not_email(@u_participating.id) should_not_email(@u_participating.id)
should_not_email(@u_disabled.id) should_not_email(@u_disabled.id)
...@@ -242,6 +244,7 @@ describe NotificationService do ...@@ -242,6 +244,7 @@ describe NotificationService do
should_email(issue.assignee_id) should_email(issue.assignee_id)
should_email(issue.author_id) should_email(issue.author_id)
should_email(@u_watcher.id) should_email(@u_watcher.id)
should_email(@u_participant_mentioned.id)
should_not_email(@u_participating.id) should_not_email(@u_participating.id)
should_not_email(@u_disabled.id) should_not_email(@u_disabled.id)
...@@ -262,6 +265,7 @@ describe NotificationService do ...@@ -262,6 +265,7 @@ describe NotificationService do
should_email(issue.assignee_id) should_email(issue.assignee_id)
should_email(issue.author_id) should_email(issue.author_id)
should_email(@u_watcher.id) should_email(@u_watcher.id)
should_email(@u_participant_mentioned.id)
should_not_email(@u_participating.id) should_not_email(@u_participating.id)
should_not_email(@u_disabled.id) should_not_email(@u_disabled.id)
...@@ -404,6 +408,7 @@ describe NotificationService do ...@@ -404,6 +408,7 @@ describe NotificationService do
def build_team(project) def build_team(project)
@u_watcher = create(:user, notification_level: Notification::N_WATCH) @u_watcher = create(:user, notification_level: Notification::N_WATCH)
@u_participating = create(:user, notification_level: Notification::N_PARTICIPATING) @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_disabled = create(:user, notification_level: Notification::N_DISABLED)
@u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_MENTION) @u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_MENTION)
@u_committer = create(:user, username: 'committer') @u_committer = create(:user, username: 'committer')
......
...@@ -5,6 +5,8 @@ describe SystemHooksService do ...@@ -5,6 +5,8 @@ describe SystemHooksService do
let (:project) { create :project } let (:project) { create :project }
let (:project_member) { create :project_member } let (:project_member) { create :project_member }
let (:key) { create(:key, user: user) } let (:key) { create(:key, user: user) }
let (:group) { create(:group) }
let (:group_member) { create(:group_member) }
context 'event data' do context 'event data' do
it { event_data(user, :create).should include(:event_name, :name, :created_at, :email, :user_id) } it { event_data(user, :create).should include(:event_name, :name, :created_at, :email, :user_id) }
...@@ -15,6 +17,31 @@ describe SystemHooksService do ...@@ -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(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, :create).should include(:username, :key, :id) }
it { event_data(key, :destroy).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 end
context 'event names' do context 'event names' do
...@@ -26,6 +53,10 @@ describe SystemHooksService do ...@@ -26,6 +53,10 @@ describe SystemHooksService do
it { event_name(project_member, :destroy).should eq "user_remove_from_team" } 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, :create).should eq 'key_create' }
it { event_name(key, :destroy).should eq 'key_destroy' } 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 end
def event_data(*args) 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