Commit 9b678226 authored by Jan-Willem van der Meer's avatar Jan-Willem van der Meer

Merge branch 'feature-multi-ldap-servers-merge-master' into feature-multi-ldap-servers-group-sync

Conflicts:
	db/schema.rb
	lib/api/ldap.rb
parents 9b9925bb 7b479156
...@@ -12,9 +12,21 @@ v 7.4.0 ...@@ -12,9 +12,21 @@ v 7.4.0
- API: Add support for forking a project via the API (Bernhard Kaindl) - API: Add support for forking a project via the API (Bernhard Kaindl)
- API: filter project issues by milestone (Julien Bianchi) - API: filter project issues by milestone (Julien Bianchi)
- Fail harder in the backup script - Fail harder in the backup script
- Changes to Slack service structure, only webhook url needed
- Zen mode for wiki and milestones (Robert Schilling) - Zen mode for wiki and milestones (Robert Schilling)
- Move Emoji parsing to html-pipeline-gitlab (Robert Schilling) - Move Emoji parsing to html-pipeline-gitlab (Robert Schilling)
- Font Awesome 4.2 integration (Sullivan Senechal) - Font Awesome 4.2 integration (Sullivan Senechal)
- Add Pushover service integration (Sullivan Senechal)
- Add select field type for services options (Sullivan Senechal)
- Add cross-project references to the Markdown parser (Vinnie Okada)
- Add task lists to issue and merge request descriptions (Vinnie Okada)
- Snippets can be public, internal or private
- Improve danger zone: ask project path to confirm data-loss action
- Raise exception on forgery
- Show build coverage in Merge Requests (requires GitLab CI v5.1)
- New milestone and label links on issue edit form
- Improved repository graphs
- Improve event note display in dashboard and project activity views (Vinnie Okada)
v 7.3.2 v 7.3.2
- Fix creating new file via web editor - Fix creating new file via web editor
......
...@@ -10,7 +10,7 @@ By submitting code as an individual you agree to the [individual contributor lic ...@@ -10,7 +10,7 @@ By submitting code as an individual you agree to the [individual contributor lic
## Security vulnerability disclosure ## Security vulnerability disclosure
Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](http://www.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](http://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities.
## Closing policy for issues and merge requests ## Closing policy for issues and merge requests
...@@ -22,7 +22,7 @@ Issues and merge requests should be in English and contain appropriate language ...@@ -22,7 +22,7 @@ Issues and merge requests should be in English and contain appropriate language
## Issue tracker ## Issue tracker
To get support for your particular problem please use the channels as detailed in the [getting help section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#getting-help). Professional [support subscriptions](http://www.gitlab.com/subscription/) and [consulting services](http://www.gitlab.com/consultancy/) are available from [GitLab.com](http://www.gitlab.com/). To get support for your particular problem please use the channels as detailed in the [getting help section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#getting-help). Professional [support subscriptions](http://about.gitlab.com/subscription/) and [consulting services](http://about.gitlab.com/consultancy/) are available from [GitLab.com](http://about.gitlab.com/).
The [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) is only for obvious errors in the latest [stable or development release of GitLab](MAINTENANCE.md). If something is wrong but it is not a regression compared to older versions of GitLab please do not open an issue but a feature request. When submitting an issue please conform to the issue submission guidelines listed below. Not all issues will be addressed and your issue is more likely to be addressed if you submit a merge request which partially or fully addresses the issue. The [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) is only for obvious errors in the latest [stable or development release of GitLab](MAINTENANCE.md). If something is wrong but it is not a regression compared to older versions of GitLab please do not open an issue but a feature request. When submitting an issue please conform to the issue submission guidelines listed below. Not all issues will be addressed and your issue is more likely to be addressed if you submit a merge request which partially or fully addresses the issue.
......
...@@ -31,7 +31,7 @@ gem 'omniauth-shibboleth' ...@@ -31,7 +31,7 @@ gem 'omniauth-shibboleth'
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
gem "gitlab_git", '7.0.0.rc8' gem "gitlab_git", '7.0.0.rc9'
# Ruby/Rack Git Smart-HTTP Server Handler # Ruby/Rack Git Smart-HTTP Server Handler
gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack' gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack'
......
...@@ -168,7 +168,7 @@ GEM ...@@ -168,7 +168,7 @@ GEM
multi_json multi_json
gitlab-grack (2.0.0.pre) gitlab-grack (2.0.0.pre)
rack (~> 1.5.1) rack (~> 1.5.1)
gitlab-grit (2.6.11) gitlab-grit (2.6.12)
charlock_holmes (~> 0.6) charlock_holmes (~> 0.6)
diff-lcs (~> 1.1) diff-lcs (~> 1.1)
mime-types (~> 1.15) mime-types (~> 1.15)
...@@ -179,7 +179,7 @@ GEM ...@@ -179,7 +179,7 @@ GEM
mime-types (~> 1.19) mime-types (~> 1.19)
gitlab_emoji (0.0.1.1) gitlab_emoji (0.0.1.1)
emoji (~> 1.0.1) emoji (~> 1.0.1)
gitlab_git (7.0.0.rc8) gitlab_git (7.0.0.rc9)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.6) charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0) gitlab-linguist (~> 3.0)
...@@ -241,9 +241,11 @@ GEM ...@@ -241,9 +241,11 @@ GEM
html-pipeline (1.11.0) html-pipeline (1.11.0)
activesupport (>= 2) activesupport (>= 2)
nokogiri (~> 1.4) nokogiri (~> 1.4)
html-pipeline-gitlab (0.1.0) html-pipeline-gitlab (0.1.5)
gitlab_emoji (~> 0.0.1.1) actionpack (~> 4)
gitlab_emoji (~> 0.0.1)
html-pipeline (~> 1.11.0) html-pipeline (~> 1.11.0)
sanitize (~> 2.1)
http_parser.rb (0.5.3) http_parser.rb (0.5.3)
httparty (0.13.0) httparty (0.13.0)
json (~> 1.8) json (~> 1.8)
...@@ -622,7 +624,7 @@ DEPENDENCIES ...@@ -622,7 +624,7 @@ DEPENDENCIES
gitlab-grack (~> 2.0.0.pre) gitlab-grack (~> 2.0.0.pre)
gitlab-linguist (~> 3.0.0) gitlab-linguist (~> 3.0.0)
gitlab_emoji (~> 0.0.1.1) gitlab_emoji (~> 0.0.1.1)
gitlab_git (= 7.0.0.rc8) gitlab_git (= 7.0.0.rc9)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (= 1.1.0) gitlab_omniauth-ldap (= 1.1.0)
gollum-lib (~> 3.0.0) gollum-lib (~> 3.0.0)
......
...@@ -18,7 +18,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co ...@@ -18,7 +18,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co
- Responds to merge requests the issue team mentions them in and monitors for new merge requests - Responds to merge requests the issue team mentions them in and monitors for new merge requests
- Provides feedback to the merge request submitter to improve the merge request (style, tests, etc.) - Provides feedback to the merge request submitter to improve the merge request (style, tests, etc.)
- Mark merge requests 'ready-for-merge' when they meet the contribution guidelines - Mark merge requests 'ready-for-merge' when they meet the contribution guidelines
- Mention developer(s) based on the [list of members and their specialities](https://www.gitlab.com/core-team/) - Mention developer(s) based on the [list of members and their specialities](https://about.gitlab.com/core-team/)
- Closes merge requests with no feedback from the reporter for two weeks - Closes merge requests with no feedback from the reporter for two weeks
## Priorities of the issue team ## Priorities of the issue team
...@@ -30,7 +30,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co ...@@ -30,7 +30,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co
## Mentioning people ## Mentioning people
The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](https://www.gitlab.com/core-team/). If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person. The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](https://about.gitlab.com/core-team/). If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person.
## Workflow labels ## Workflow labels
...@@ -79,7 +79,7 @@ Thanks for the issue report but we only support issues for the latest stable ver ...@@ -79,7 +79,7 @@ Thanks for the issue report but we only support issues for the latest stable ver
### Support requests and configuration questions ### Support requests and configuration questions
Thanks for your interest in GitLab. We don't use the issue tracker for support requests and configuration questions. Please use the \[support forum\]\(https://groups.google.com/forum/#!forum/gitlabhq), \[Stack Overflow\]\(http://stackoverflow.com/questions/tagged/gitlab), the #gitlab IRC channel on Freenode or the http://www.gitlab.com paid services for this purpose. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information. Thanks for your interest in GitLab. We don't use the issue tracker for support requests and configuration questions. Please use the \[support forum\]\(https://groups.google.com/forum/#!forum/gitlabhq), \[Stack Overflow\]\(http://stackoverflow.com/questions/tagged/gitlab), the #gitlab IRC channel on Freenode or the http://about.gitlab.com paid services for this purpose. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information.
### Code format ### Code format
......
...@@ -177,6 +177,13 @@ $ -> ...@@ -177,6 +177,13 @@ $ ->
$(@).closest(".diff-file").find(".notes_holder").toggle() $(@).closest(".diff-file").find(".notes_holder").toggle()
e.preventDefault() e.preventDefault()
$(document).on "click", '.js-confirm-danger', (e) ->
e.preventDefault()
btn = $(e.target)
text = btn.data("confirm-danger-message")
form = btn.closest("form")
new ConfirmDangerModal(form, text)
(($) -> (($) ->
# Disable an element and add the 'disabled' Bootstrap class # Disable an element and add the 'disabled' Bootstrap class
$.fn.extend disable: -> $.fn.extend disable: ->
......
window.updateTaskState = (taskableType) ->
objType = taskableType.data
isChecked = $(this).prop("checked")
if $(this).is(":checked")
stateEvent = "task_check"
else
stateEvent = "task_uncheck"
taskableUrl = $("form.edit-" + objType).first().attr("action")
taskableNum = taskableUrl.match(/\d+$/)
taskNum = 0
$("li.task-list-item input:checkbox").each( (index, e) =>
if e == this
taskNum = index + 1
)
$.ajax
type: "PATCH"
url: taskableUrl
data: objType + "[state_event]=" + stateEvent +
"&" + objType + "[task_num]=" + taskNum
class ConfirmDangerModal
constructor: (form, text) ->
@form = form
$('.js-confirm-text').text(text || '')
$('.js-confirm-danger-input').val('')
$('#modal-confirm-danger').modal('show')
project_path = $('.js-confirm-danger-match').text()
submit = $('.js-confirm-danger-submit')
submit.disable()
$('.js-confirm-danger-input').on 'input', ->
if rstrip($(@).val()) is project_path
submit.enable()
else
submit.disable()
$('.js-confirm-danger-submit').on 'click', =>
@form.submit()
@ConfirmDangerModal = ConfirmDangerModal
...@@ -6,4 +6,14 @@ class Issue ...@@ -6,4 +6,14 @@ class Issue
$(".issue-box .inline-update").on "change", "#issue_assignee_id", -> $(".issue-box .inline-update").on "change", "#issue_assignee_id", ->
$(this).submit() $(this).submit()
if $("a.btn-close").length
$("li.task-list-item input:checkbox").prop("disabled", false)
$(".task-list-item input:checkbox").on(
"click"
null
"issue"
updateTaskState
)
@Issue = Issue @Issue = Issue
...@@ -17,6 +17,8 @@ class MergeRequest ...@@ -17,6 +17,8 @@ class MergeRequest
disableButtonIfEmptyField '#commit_message', '.accept_merge_request' disableButtonIfEmptyField '#commit_message', '.accept_merge_request'
if $("a.btn-close").length
$("li.task-list-item input:checkbox").prop("disabled", false)
# Local jQuery finder # Local jQuery finder
$: (selector) -> $: (selector) ->
...@@ -72,6 +74,13 @@ class MergeRequest ...@@ -72,6 +74,13 @@ class MergeRequest
this.$('.remove_source_branch_in_progress').hide() this.$('.remove_source_branch_in_progress').hide()
this.$('.remove_source_branch_widget.failed').show() this.$('.remove_source_branch_widget.failed').show()
$(".task-list-item input:checkbox").on(
"click"
null
"merge_request"
updateTaskState
)
activateTab: (action) -> activateTab: (action) ->
this.$('.merge-request-tabs li').removeClass 'active' this.$('.merge-request-tabs li').removeClass 'active'
this.$('.tab-content').hide() this.$('.tab-content').hide()
...@@ -96,14 +105,6 @@ class MergeRequest ...@@ -96,14 +105,6 @@ class MergeRequest
else else
$('.ci_widget.ci-error').show() $('.ci_widget.ci-error').show()
switch state
when "success"
$('.mr-state-widget').addClass("panel-success")
when "failed"
$('.mr-state-widget').addClass("panel-danger")
when "running", "pending"
$('.mr-state-widget').addClass("panel-warning")
showCiCoverage: (coverage) -> showCiCoverage: (coverage) ->
cov_html = $('<span>') cov_html = $('<span>')
cov_html.addClass('ci-coverage') cov_html.addClass('ci-coverage')
......
...@@ -365,3 +365,6 @@ table { ...@@ -365,3 +365,6 @@ table {
font-size: 42px; font-size: 42px;
} }
.task-status {
margin-left: 10px;
}
...@@ -10,8 +10,7 @@ ...@@ -10,8 +10,7 @@
.issue-box { .issue-box {
color: #555; color: #555;
margin:20px 0; margin:20px 0;
background: #f9f9f9; background: $box_bg;
border-top-left-radius: 5px;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09)); @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
&.issue-box-closed { &.issue-box-closed {
...@@ -112,7 +111,6 @@ ...@@ -112,7 +111,6 @@
float: left; float: left;
font-weight: bold; font-weight: bold;
padding: 10px 15px; padding: 10px 15px;
border-top-left-radius: 5px;
} }
.creator { .creator {
......
...@@ -122,3 +122,7 @@ ul.bordered-list { ...@@ -122,3 +122,7 @@ ul.bordered-list {
} }
} }
} }
li.task-list-item {
list-style-type: none;
}
...@@ -233,8 +233,8 @@ $list-group-active-bg: $bg_primary; ...@@ -233,8 +233,8 @@ $list-group-active-bg: $bg_primary;
} }
.form-actions { .form-actions {
margin-bottom: 0; margin: -15px;
background: #FFF; margin-top: 18px;
} }
} }
...@@ -262,53 +262,33 @@ $list-group-active-bg: $bg_primary; ...@@ -262,53 +262,33 @@ $list-group-active-bg: $bg_primary;
} }
.panel-danger { .panel-danger {
border-color: $border_danger; @include panel-colored;
.panel-heading { .panel-heading {
color: #ffffff; color: $border_danger;
background-color: $bg_danger;
border-color: $border_danger; border-color: $border_danger;
a {
color: #FFF;
text-decoration: underline;
}
} }
} }
.panel-success { .panel-success {
border-color: $border_success; @include panel-colored;
.panel-heading { .panel-heading {
color: #ffffff; color: $border_success;
background-color: $bg_success;
border-color: $border_success; border-color: $border_success;
a {
color: #FFF;
text-decoration: underline;
}
} }
} }
.panel-primary { .panel-primary {
border-color: $border_primary; @include panel-colored;
.panel-heading { .panel-heading {
color: #ffffff; color: $border_primary;
background-color: $bg_primary;
border-color: $border_primary; border-color: $border_primary;
a {
color: #FFF;
text-decoration: underline;
}
} }
} }
.panel-warning { .panel-warning {
border-color: $border_warning; @include panel-colored;
.panel-heading { .panel-heading {
color: #ffffff; color: $border_warning;
background-color: $bg_warning;
border-color: $border_warning; border-color: $border_warning;
a {
color: #FFF;
text-decoration: underline;
}
} }
} }
...@@ -132,3 +132,14 @@ ...@@ -132,3 +132,14 @@
white-space: nowrap; white-space: nowrap;
max-width: $max_width; max-width: $max_width;
} }
@mixin panel-colored {
border: none;
background: $box_bg;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
.panel-heading {
font-weight: bold;
background-color: $box_bg;
}
}
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
*/ */
$style_color: #474D57; $style_color: #474D57;
$hover: #FFECDB; $hover: #FFECDB;
$box_bg: #F9F9F9;
/* /*
* Link colors * Link colors
......
...@@ -104,7 +104,44 @@ ...@@ -104,7 +104,44 @@
} }
.mr-state-widget { .mr-state-widget {
.panel-body { background: $box_bg;
margin-bottom: 20px;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
.ci_widget {
padding: 10px 15px;
font-size: 15px;
border-bottom: 1px dashed #AAA;
&.ci-success {
color: $bg_success;
border-color: $border_success;
}
&.ci-pending {
color: #548;
border-color: #548;
}
&.ci-running {
color: $bg_warning;
border-color: $border_warning;
}
&.ci-failed {
color: $bg_danger;
border-color: $border_danger;
}
&.ci-error {
color: $bg_danger;
border-color: $border_danger;
}
}
.mr-widget-body {
padding: 10px 15px;
h4 { h4 {
margin-top: 0px; margin-top: 0px;
} }
...@@ -114,6 +151,11 @@ ...@@ -114,6 +151,11 @@
} }
} }
.mr-widget-footer {
padding: 10px 15px;
border-top: 1px solid #EEE;
}
.ci-coverage { .ci-coverage {
float: right; float: right;
} }
......
...@@ -8,8 +8,6 @@ ...@@ -8,8 +8,6 @@
ul { ul {
padding: 0; padding: 0;
margin: auto; margin: auto;
height: 40px;
overflow: hidden;
.count { .count {
font-weight: normal; font-weight: normal;
display: inline-block; display: inline-block;
...@@ -37,53 +35,28 @@ ...@@ -37,53 +35,28 @@
a { a {
color: $link_color; color: $link_color;
font-weight: bold; font-weight: bold;
&:after { border-bottom: 3px solid $link_color;
content: '';
display: block;
position: relative;
bottom: -1px;
border-color: $link_color;
border-style: solid;
border-width: 2px;
}
} }
} }
&:hover { &:hover {
a { a {
color: $link_hover_color; color: $link_hover_color;
&:after { border-bottom: 3px solid $link_hover_color;
content: '';
display: block;
position: relative;
bottom: -1px;
border-color: $link_hover_color;
border-style: solid;
border-width: 2px;
}
}
}
&.home {
a {
i {
font-size: 20px;
position: relative;
top: 4px;
}
} }
} }
} }
a { a {
display: block; display: block;
text-align: center; text-align: center;
font-weight: 500; font-weight: bold;
height: 38px; height: 42px;
line-height: 34px; line-height: 39px;
color: #777; color: #777;
text-shadow: 0 1px 1px white; text-shadow: 0 1px 1px white;
text-decoration: none; text-decoration: none;
padding-top: 2px; overflow: hidden;
margin-bottom: -1px;
} }
} }
......
...@@ -4,6 +4,7 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -4,6 +4,7 @@ class Admin::UsersController < Admin::ApplicationController
def index def index
@users = User.filter(params[:filter]) @users = User.filter(params[:filter])
@users = @users.search(params[:name]) if params[:name].present? @users = @users.search(params[:name]) if params[:name].present?
@users = @users.sort(@sort = params[:sort])
@users = @users.alphabetically.page(params[:page]) @users = @users.alphabetically.page(params[:page])
end end
......
...@@ -13,7 +13,7 @@ class ApplicationController < ActionController::Base ...@@ -13,7 +13,7 @@ class ApplicationController < ActionController::Base
before_filter :configure_permitted_parameters, if: :devise_controller? before_filter :configure_permitted_parameters, if: :devise_controller?
before_filter :require_email, unless: :devise_controller? before_filter :require_email, unless: :devise_controller?
protect_from_forgery protect_from_forgery with: :exception
helper_method :abilities, :can? helper_method :abilities, :can?
......
...@@ -32,10 +32,6 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -32,10 +32,6 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end end
end end
Gitlab.config.ldap.servers.each do |server|
alias_method server.provider_name, :ldap
end
def omniauth_error def omniauth_error
@provider = params[:provider] @provider = params[:provider]
@error = params[:error] @error = params[:error]
......
...@@ -17,7 +17,7 @@ class Projects::CommitsController < Projects::ApplicationController ...@@ -17,7 +17,7 @@ class Projects::CommitsController < Projects::ApplicationController
group(:commit_id).count group(:commit_id).count
respond_to do |format| respond_to do |format|
format.html # index.html.erb format.html
format.json { pager_json("projects/commits/_commits", @commits.size) } format.json { pager_json("projects/commits/_commits", @commits.size) }
format.atom { render layout: false } format.atom { render layout: false }
end end
......
...@@ -152,7 +152,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -152,7 +152,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue_params def issue_params
params.require(:issue).permit( params.require(:issue).permit(
:title, :assignee_id, :position, :description, :title, :assignee_id, :position, :description,
:milestone_id, :state_event, label_ids: [] :milestone_id, :state_event, :task_num, label_ids: []
) )
end end
end end
...@@ -250,7 +250,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -250,7 +250,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
params.require(:merge_request).permit( params.require(:merge_request).permit(
:title, :assignee_id, :source_project_id, :source_branch, :title, :assignee_id, :source_project_id, :source_branch,
:target_project_id, :target_branch, :milestone_id, :target_project_id, :target_branch, :milestone_id,
:state_event, :description, label_ids: [] :state_event, :description, :task_num, label_ids: []
) )
end end
end end
...@@ -40,7 +40,8 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -40,7 +40,8 @@ class Projects::ServicesController < Projects::ApplicationController
def service_params def service_params
params.require(:service).permit( params.require(:service).permit(
:title, :token, :type, :active, :api_key, :subdomain, :title, :token, :type, :active, :api_key, :subdomain,
:room, :recipients, :project_url, :username, :password, :api_version :room, :recipients, :project_url, :webhook, :username, :password, :api_version,
:user_key, :device, :priority, :sound
) )
end end
end end
...@@ -17,7 +17,10 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -17,7 +17,10 @@ class Projects::SnippetsController < Projects::ApplicationController
respond_to :html respond_to :html
def index def index
@snippets = @project.snippets.fresh.non_expired @snippets = SnippetsFinder.new.execute(current_user, {
filter: :by_project,
project: @project
})
end end
def new def new
...@@ -88,6 +91,6 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -88,6 +91,6 @@ class Projects::SnippetsController < Projects::ApplicationController
end end
def snippet_params def snippet_params
params.require(:project_snippet).permit(:title, :content, :file_name, :private) params.require(:project_snippet).permit(:title, :content, :file_name, :private, :visibility_level)
end end
end end
...@@ -19,7 +19,7 @@ class SessionsController < Devise::SessionsController ...@@ -19,7 +19,7 @@ class SessionsController < Devise::SessionsController
end end
if Gitlab.config.ldap.enabled if Gitlab.config.ldap.enabled
@ldap_servers = Gitlab.config.ldap.servers @ldap_servers = Gitlab::LDAP::Config.servers
end end
super super
......
...@@ -9,12 +9,14 @@ class SnippetsController < ApplicationController ...@@ -9,12 +9,14 @@ class SnippetsController < ApplicationController
before_filter :set_title before_filter :set_title
skip_before_filter :authenticate_user!, only: [:index, :user_index]
respond_to :html respond_to :html
layout 'navless' layout :determine_layout
def index def index
@snippets = Snippet.are_public.fresh.non_expired.page(params[:page]).per(20) @snippets = SnippetsFinder.new.execute(current_user, filter: :all).page(params[:page]).per(20)
end end
def user_index def user_index
...@@ -22,22 +24,11 @@ class SnippetsController < ApplicationController ...@@ -22,22 +24,11 @@ class SnippetsController < ApplicationController
render_404 and return unless @user render_404 and return unless @user
@snippets = @user.snippets.fresh.non_expired @snippets = SnippetsFinder.new.execute(current_user, {
filter: :by_user,
if @user == current_user user: @user,
@snippets = case params[:scope] scope: params[:scope]}).
when 'are_public' then page(params[:page]).per(20)
@snippets.are_public
when 'are_private' then
@snippets.are_private
else
@snippets
end
else
@snippets = @snippets.are_public
end
@snippets = @snippets.page(params[:page]).per(20)
if @user == current_user if @user == current_user
render 'current_user_index' render 'current_user_index'
...@@ -95,7 +86,14 @@ class SnippetsController < ApplicationController ...@@ -95,7 +86,14 @@ class SnippetsController < ApplicationController
protected protected
def snippet def snippet
@snippet ||= PersonalSnippet.where('author_id = :user_id or private is false', user_id: current_user.id).find(params[:id]) @snippet ||= if current_user
PersonalSnippet.where("author_id = ? OR visibility_level IN (?)",
current_user.id,
[Snippet::PUBLIC, Snippet::INTERNAL]).
find(params[:id])
else
PersonalSnippet.are_public.find(params[:id])
end
end end
def authorize_modify_snippet! def authorize_modify_snippet!
...@@ -111,6 +109,10 @@ class SnippetsController < ApplicationController ...@@ -111,6 +109,10 @@ class SnippetsController < ApplicationController
end end
def snippet_params def snippet_params
params.require(:personal_snippet).permit(:title, :content, :file_name, :private) params.require(:personal_snippet).permit(:title, :content, :file_name, :private, :visibility_level)
end
def determine_layout
current_user ? 'navless' : 'public_users'
end end
end end
# Finders # Finders
This type of classes responsible for collectiong items based on different conditions. This type of classes responsible for collection items based on different conditions.
To prevent lookup methods in models like this: To prevent lookup methods in models like this:
```ruby ```ruby
...@@ -19,4 +19,4 @@ Better use this: ...@@ -19,4 +19,4 @@ Better use this:
issues = IssuesFinder.new.execute(project, user, filter) issues = IssuesFinder.new.execute(project, user, filter)
``` ```
It will help keep models thiner It will help keep models thiner.
class SnippetsFinder
def execute(current_user, params = {})
filter = params[:filter]
case filter
when :all then
snippets(current_user).fresh.non_expired
when :by_user then
by_user(current_user, params[:user], params[:scope])
when :by_project
by_project(current_user, params[:project])
end
end
private
def snippets(current_user)
if current_user
Snippet.public_and_internal
else
# Not authenticated
#
# Return only:
# public snippets
Snippet.are_public
end
end
def by_user(current_user, user, scope)
snippets = user.snippets.fresh.non_expired
if user == current_user
case scope
when 'are_internal' then
snippets.are_internal
when 'are_private' then
snippets.are_private
when 'are_public' then
snippets.are_public
else
snippets
end
else
snippets.public_and_internal
end
end
def by_project(current_user, project)
snippets = project.snippets.fresh.non_expired
if current_user
if project.team.member?(current_user.id)
snippets
else
snippets.public_and_internal
end
else
snippets.are_public
end
end
end
...@@ -259,4 +259,16 @@ module ApplicationHelper ...@@ -259,4 +259,16 @@ module ApplicationHelper
super super
end end
def escaped_autolink(text)
auto_link ERB::Util.html_escape(text), link: :urls
end
def promo_host
'about.gitlab.com'
end
def promo_url
'https://' + promo_host
end
end end
...@@ -120,4 +120,8 @@ module CommitsHelper ...@@ -120,4 +120,8 @@ module CommitsHelper
class: 'commit-short-id') class: 'commit-short-id')
end end
end end
def truncate_sha(sha)
Commit.truncate_sha(sha)
end
end end
...@@ -37,40 +37,31 @@ module DashboardHelper ...@@ -37,40 +37,31 @@ module DashboardHelper
end end
def assigned_entities_count(current_user, entity, scope = nil) def assigned_entities_count(current_user, entity, scope = nil)
items = current_user.send("assigned_" + entity.pluralize).opened items = current_user.send('assigned_' + entity.pluralize)
get_count(items, scope)
if scope.kind_of?(Group)
items = items.of_group(scope)
elsif scope.kind_of?(Project)
items = items.of_projects(scope)
end
items.count
end end
def authored_entities_count(current_user, entity, scope = nil) def authored_entities_count(current_user, entity, scope = nil)
items = current_user.send(entity.pluralize).opened items = current_user.send(entity.pluralize)
get_count(items, scope)
if scope.kind_of?(Group)
items = items.of_group(scope)
elsif scope.kind_of?(Project)
items = items.of_projects(scope)
end end
items.count def authorized_entities_count(current_user, entity, scope = nil)
items = entity.classify.constantize
get_count(items, scope, true, current_user)
end end
def authorized_entities_count(current_user, entity, scope = nil) protected
items = entity.classify.constantize.opened
def get_count(items, scope, get_authorized = false, current_user = nil)
items = items.opened
if scope.kind_of?(Group) if scope.kind_of?(Group)
items = items.of_group(scope) items = items.of_group(scope)
elsif scope.kind_of?(Project) elsif scope.kind_of?(Project)
items = items.of_projects(scope) items = items.of_projects(scope)
else elsif get_authorized
items = items.of_projects(current_user.authorized_projects) items = items.of_projects(current_user.authorized_projects)
end end
items.count items.count
end end
end end
...@@ -136,9 +136,8 @@ module EventsHelper ...@@ -136,9 +136,8 @@ module EventsHelper
end end
def event_note(text) def event_note(text)
text = first_line_in_markdown(text) text = first_line_in_markdown(text, 150)
text = truncate(text, length: 150) sanitize(text, tags: %w(a img b pre code p))
sanitize(markdown(text), tags: %w(a img b pre p))
end end
def event_commit_title(message) def event_commit_title(message)
......
...@@ -51,12 +51,14 @@ module GitlabMarkdownHelper ...@@ -51,12 +51,14 @@ module GitlabMarkdownHelper
@markdown.render(text).html_safe @markdown.render(text).html_safe
end end
def first_line_in_markdown(text) # Return the first line of +text+, up to +max_chars+, after parsing the line
line = text.split("\n").detect do |i| # as Markdown. HTML tags in the parsed output are not counted toward the
i.present? && markdown(i).present? # +max_chars+ limit. If the length limit falls within a tag's contents, then
end # the tag contents are truncated without removing the closing tag.
line += '...' unless line.nil? def first_line_in_markdown(text, max_chars = nil)
line md = markdown(text).strip
truncate_visible(md, max_chars || md.length) if md.present?
end end
def render_wiki_content(wiki_page) def render_wiki_content(wiki_page)
...@@ -204,4 +206,52 @@ module GitlabMarkdownHelper ...@@ -204,4 +206,52 @@ module GitlabMarkdownHelper
def correct_ref def correct_ref
@ref ? @ref : "master" @ref ? @ref : "master"
end end
private
# Return +text+, truncated to +max_chars+ characters, excluding any HTML
# tags.
def truncate_visible(text, max_chars)
doc = Nokogiri::HTML.fragment(text)
content_length = 0
truncated = false
doc.traverse do |node|
if node.text? || node.content.empty?
if truncated
node.remove
next
end
# Handle line breaks within a node
if node.content.strip.lines.length > 1
node.content = "#{node.content.lines.first.chomp}..."
truncated = true
end
num_remaining = max_chars - content_length
if node.content.length > num_remaining
node.content = node.content.truncate(num_remaining)
truncated = true
end
content_length += node.content.length
end
truncated = truncate_if_block(node, truncated)
end
doc.to_html
end
# Used by #truncate_visible. If +node+ is the first block element, and the
# text hasn't already been truncated, then append "..." to the node contents
# and return true. Otherwise return false.
def truncate_if_block(node, truncated)
if node.element? && node.description.block? && !truncated
node.content = "#{node.content}..." if node.next_sibling
true
else
truncated
end
end
end end
...@@ -19,8 +19,7 @@ module MergeRequestsHelper ...@@ -19,8 +19,7 @@ module MergeRequestsHelper
source_project_id: event.project.id, source_project_id: event.project.id,
target_project_id: target_project.id, target_project_id: target_project.id,
source_branch: event.branch_name, source_branch: event.branch_name,
target_branch: target_project.repository.root_ref, target_branch: target_project.repository.root_ref
title: event.branch_name.titleize.humanize
} }
end end
......
...@@ -56,6 +56,10 @@ module ProjectsHelper ...@@ -56,6 +56,10 @@ module ProjectsHelper
"You are going to remove #{project.name_with_namespace}.\n Removed project CANNOT be restored!\n Are you ABSOLUTELY sure?" "You are going to remove #{project.name_with_namespace}.\n Removed project CANNOT be restored!\n Are you ABSOLUTELY sure?"
end end
def transfer_project_message(project)
"You are going to transfer #{project.name_with_namespace} to another owner. Are you ABSOLUTELY sure?"
end
def project_nav_tabs def project_nav_tabs
@nav_tabs ||= get_project_nav_tabs(@project, current_user) @nav_tabs ||= get_project_nav_tabs(@project, current_user)
end end
...@@ -128,9 +132,9 @@ module ProjectsHelper ...@@ -128,9 +132,9 @@ module ProjectsHelper
toggle_html = content_tag('span', class: 'toggle') do toggle_html = content_tag('span', class: 'toggle') do
toggle_text = if starred toggle_text = if starred
'Unstar' ' Unstar'
else else
'Star' ' Star'
end end
content_tag('i', ' ', class: 'fa fa-star') + toggle_text content_tag('i', ' ', class: 'fa fa-star') + toggle_text
......
...@@ -90,7 +90,7 @@ module TreeHelper ...@@ -90,7 +90,7 @@ module TreeHelper
end end
def editing_preview_title(filename) def editing_preview_title(filename)
if gitlab_markdown?(filename) || markup?(filename) if Gitlab::MarkdownHelper.previewable?(filename)
'Preview' 'Preview'
else else
'Diff' 'Diff'
......
...@@ -28,6 +28,23 @@ module VisibilityLevelHelper ...@@ -28,6 +28,23 @@ module VisibilityLevelHelper
end end
end end
def snippet_visibility_level_description(level)
capture_haml do
haml_tag :span do
case level
when Gitlab::VisibilityLevel::PRIVATE
haml_concat "The snippet is visible only for me"
when Gitlab::VisibilityLevel::INTERNAL
haml_concat "The snippet is visible for any logged in user."
when Gitlab::VisibilityLevel::PUBLIC
haml_concat "The snippet can be accessed"
haml_concat "without any"
haml_concat "authentication."
end
end
end
end
def visibility_level_icon(level) def visibility_level_icon(level)
case level case level
when Gitlab::VisibilityLevel::PRIVATE when Gitlab::VisibilityLevel::PRIVATE
......
...@@ -19,13 +19,24 @@ class Commit ...@@ -19,13 +19,24 @@ class Commit
class << self class << self
def decorate(commits) def decorate(commits)
commits.map { |c| self.new(c) } commits.map do |commit|
if commit.kind_of?(Commit)
commit
else
self.new(commit)
end
end
end end
# Calculate number of lines to render for diffs # Calculate number of lines to render for diffs
def diff_line_count(diffs) def diff_line_count(diffs)
diffs.reduce(0) { |sum, d| sum + d.diff.lines.count } diffs.reduce(0) { |sum, d| sum + d.diff.lines.count }
end end
# Truncate sha to 8 characters
def truncate_sha(sha)
sha[0..7]
end
end end
attr_accessor :raw attr_accessor :raw
...@@ -111,7 +122,7 @@ class Commit ...@@ -111,7 +122,7 @@ class Commit
# Mentionable override. # Mentionable override.
def gfm_reference def gfm_reference
"commit #{sha[0..5]}" "commit #{id}"
end end
def method_missing(m, *args, &block) def method_missing(m, *args, &block)
...@@ -124,6 +135,11 @@ class Commit ...@@ -124,6 +135,11 @@ class Commit
super super
end end
# Truncate sha to 8 characters
def short_id
@raw.short_id(7)
end
def parents def parents
@parents ||= Commit.decorate(super) @parents ||= Commit.decorate(super)
end end
......
...@@ -67,9 +67,10 @@ module Mentionable ...@@ -67,9 +67,10 @@ module Mentionable
def references(p = project, text = mentionable_text) def references(p = project, text = mentionable_text)
return [] if text.blank? return [] if text.blank?
ext = Gitlab::ReferenceExtractor.new ext = Gitlab::ReferenceExtractor.new
ext.analyze(text) ext.analyze(text, p)
issues = ext.issues_for(p).select { |issue| issue.is_a?(Issue) } (ext.issues_for +
(issues + ext.merge_requests_for(p) + ext.commits_for(p)).uniq - [local_reference] ext.merge_requests_for +
ext.commits_for).uniq - [local_reference]
end end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+. # Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
......
# Contains functionality for objects that can have task lists in their
# descriptions. Task list items can be added with Markdown like "* [x] Fix
# bugs".
#
# Used by MergeRequest and Issue
module Taskable
TASK_PATTERN_MD = /^(?<bullet> *[*-] *)\[(?<checked>[ xX])\]/.freeze
TASK_PATTERN_HTML = /^<li>\[(?<checked>[ xX])\]/.freeze
# Change the state of a task list item for this Taskable. Edit the object's
# description by finding the nth task item and changing its checkbox
# placeholder to "[x]" if +checked+ is true, or "[ ]" if it's false.
# Note: task numbering starts with 1
def update_nth_task(n, checked)
index = 0
check_char = checked ? 'x' : ' '
# Do this instead of using #gsub! so that ActiveRecord detects that a field
# has changed.
self.description = self.description.gsub(TASK_PATTERN_MD) do |match|
index += 1
case index
when n then "#{$LAST_MATCH_INFO[:bullet]}[#{check_char}]"
else match
end
end
save
end
# Return true if this object's description has any task list items.
def tasks?
description && description.match(TASK_PATTERN_MD)
end
# Return a string that describes the current state of this Taskable's task
# list items, e.g. "20 tasks (12 done, 8 unfinished)"
def task_status
return nil unless description
num_tasks = 0
num_done = 0
description.scan(TASK_PATTERN_MD) do
num_tasks += 1
num_done += 1 unless $LAST_MATCH_INFO[:checked] == ' '
end
"#{num_tasks} tasks (#{num_done} done, #{num_tasks - num_done} unfinished)"
end
end
...@@ -266,7 +266,7 @@ class Event < ActiveRecord::Base ...@@ -266,7 +266,7 @@ class Event < ActiveRecord::Base
end end
def note_short_commit_id def note_short_commit_id
note_commit_id[0..8] Commit.truncate_sha(note_commit_id)
end end
def note_commit? def note_commit?
......
...@@ -23,6 +23,7 @@ require 'file_size_validator' ...@@ -23,6 +23,7 @@ require 'file_size_validator'
class Issue < ActiveRecord::Base class Issue < ActiveRecord::Base
include Issuable include Issuable
include InternalId include InternalId
include Taskable
ActsAsTaggableOn.strict_case_match = true ActsAsTaggableOn.strict_case_match = true
......
# == Schema Information
#
# Table name: members
#
# id :integer not null, primary key
# access_level :integer not null
# source_id :integer not null
# source_type :string(255) not null
# user_id :integer not null
# notification_level :integer not null
# type :string(255)
# created_at :datetime
# updated_at :datetime
#
class Member < ActiveRecord::Base class Member < ActiveRecord::Base
include Notifiable include Notifiable
include Gitlab::Access include Gitlab::Access
......
# == Schema Information
#
# Table name: members
#
# id :integer not null, primary key
# access_level :integer not null
# source_id :integer not null
# source_type :string(255) not null
# user_id :integer not null
# notification_level :integer not null
# type :string(255)
# created_at :datetime
# updated_at :datetime
#
class GroupMember < Member class GroupMember < Member
SOURCE_TYPE = 'Namespace' SOURCE_TYPE = 'Namespace'
......
# == Schema Information
#
# Table name: members
#
# id :integer not null, primary key
# access_level :integer not null
# source_id :integer not null
# source_type :string(255) not null
# user_id :integer not null
# notification_level :integer not null
# type :string(255)
# created_at :datetime
# updated_at :datetime
#
class ProjectMember < Member class ProjectMember < Member
SOURCE_TYPE = 'Project' SOURCE_TYPE = 'Project'
......
...@@ -25,6 +25,7 @@ require Rails.root.join("lib/static_model") ...@@ -25,6 +25,7 @@ require Rails.root.join("lib/static_model")
class MergeRequest < ActiveRecord::Base class MergeRequest < ActiveRecord::Base
include Issuable include Issuable
include Taskable
include InternalId include InternalId
belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project" belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project"
......
...@@ -55,7 +55,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -55,7 +55,7 @@ class MergeRequestDiff < ActiveRecord::Base
end end
def last_commit_short_sha def last_commit_short_sha
@last_commit_short_sha ||= last_commit.sha[0..10] @last_commit_short_sha ||= last_commit.short_id
end end
private private
......
...@@ -47,7 +47,7 @@ class Note < ActiveRecord::Base ...@@ -47,7 +47,7 @@ class Note < ActiveRecord::Base
scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) } scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
scope :inline, ->{ where("line_code IS NOT NULL") } scope :inline, ->{ where("line_code IS NOT NULL") }
scope :not_inline, ->{ where(line_code: [nil, '']) } scope :not_inline, ->{ where(line_code: [nil, '']) }
scope :system, ->{ where(system: true) }
scope :common, ->{ where(noteable_type: ["", nil]) } scope :common, ->{ where(noteable_type: ["", nil]) }
scope :fresh, ->{ order("created_at ASC, id ASC") } scope :fresh, ->{ order("created_at ASC, id ASC") }
scope :inc_author_project, ->{ includes(:project, :author) } scope :inc_author_project, ->{ includes(:project, :author) }
...@@ -70,13 +70,17 @@ class Note < ActiveRecord::Base ...@@ -70,13 +70,17 @@ class Note < ActiveRecord::Base
) )
end end
# +noteable+ was referenced from +mentioner+, by including GFM in either +mentioner+'s description or an associated Note. # +noteable+ was referenced from +mentioner+, by including GFM in either
# Create a system Note associated with +noteable+ with a GFM back-reference to +mentioner+. # +mentioner+'s description or an associated Note.
# Create a system Note associated with +noteable+ with a GFM back-reference
# to +mentioner+.
def create_cross_reference_note(noteable, mentioner, author, project) def create_cross_reference_note(noteable, mentioner, author, project)
gfm_reference = mentioner_gfm_ref(noteable, mentioner, project)
note_options = { note_options = {
project: project, project: project,
author: author, author: author,
note: "_mentioned in #{mentioner.gfm_reference}_", note: "_mentioned in #{gfm_reference}_",
system: true system: true
} }
...@@ -163,12 +167,78 @@ class Note < ActiveRecord::Base ...@@ -163,12 +167,78 @@ class Note < ActiveRecord::Base
# Determine whether or not a cross-reference note already exists. # Determine whether or not a cross-reference note already exists.
def cross_reference_exists?(noteable, mentioner) def cross_reference_exists?(noteable, mentioner)
where(noteable_id: noteable.id, system: true, note: "_mentioned in #{mentioner.gfm_reference}_").any? gfm_reference = mentioner_gfm_ref(noteable, mentioner)
notes = if noteable.is_a?(Commit)
where(commit_id: noteable.id)
else
where(noteable_id: noteable.id)
end
notes.where('note like ?', "_mentioned in #{gfm_reference}_").
system.any?
end end
def search(query) def search(query)
where("note like :query", query: "%#{query}%") where("note like :query", query: "%#{query}%")
end end
private
# Prepend the mentioner's namespaced project path to the GFM reference for
# cross-project references. For same-project references, return the
# unmodified GFM reference.
def mentioner_gfm_ref(noteable, mentioner, project = nil)
if mentioner.is_a?(Commit)
if project.nil?
return mentioner.gfm_reference.sub('commit ', 'commit %')
else
mentioning_project = project
end
else
mentioning_project = mentioner.project
end
noteable_project_id = noteable_project_id(noteable, mentioning_project)
full_gfm_reference(mentioning_project, noteable_project_id, mentioner)
end
# Return the ID of the project that +noteable+ belongs to, or nil if
# +noteable+ is a commit and is not part of the project that owns
# +mentioner+.
def noteable_project_id(noteable, mentioning_project)
if noteable.is_a?(Commit)
if mentioning_project.repository.commit(noteable.id)
# The noteable commit belongs to the mentioner's project
mentioning_project.id
else
nil
end
else
noteable.project.id
end
end
# Return the +mentioner+ GFM reference. If the mentioner and noteable
# projects are not the same, add the mentioning project's path to the
# returned value.
def full_gfm_reference(mentioning_project, noteable_project_id, mentioner)
if mentioning_project.id == noteable_project_id
mentioner.gfm_reference
else
if mentioner.is_a?(Commit)
mentioner.gfm_reference.sub(
/(commit )/,
"\\1#{mentioning_project.path_with_namespace}@"
)
else
mentioner.gfm_reference.sub(
/(issue |merge request )/,
"\\1#{mentioning_project.path_with_namespace}"
)
end
end
end
end end
def commit_author def commit_author
......
...@@ -11,8 +11,8 @@ ...@@ -11,8 +11,8 @@
# updated_at :datetime # updated_at :datetime
# file_name :string(255) # file_name :string(255)
# expires_at :datetime # expires_at :datetime
# private :boolean default(TRUE), not null
# type :string(255) # type :string(255)
# visibility_level :integer default(0), not null
# #
class PersonalSnippet < Snippet class PersonalSnippet < Snippet
......
...@@ -67,6 +67,8 @@ class Project < ActiveRecord::Base ...@@ -67,6 +67,8 @@ class Project < ActiveRecord::Base
has_one :slack_service, dependent: :destroy has_one :slack_service, dependent: :destroy
has_one :jira_service, dependent: :destroy has_one :jira_service, dependent: :destroy
has_one :jenkins_service, dependent: :destroy has_one :jenkins_service, dependent: :destroy
has_one :buildbox_service, dependent: :destroy
has_one :pushover_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" 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
...@@ -318,7 +320,7 @@ class Project < ActiveRecord::Base ...@@ -318,7 +320,7 @@ class Project < ActiveRecord::Base
end end
def available_services_names def available_services_names
%w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack jira jenkins) %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack jira jenkins pushover buildbox)
end end
def gitlab_ci? def gitlab_ci?
......
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
class BuildboxService < CiService
prop_accessor :project_url, :token
validates :project_url, presence: true, if: :activated?
validates :token, presence: true, if: :activated?
after_save :compose_service_hook, if: :activated?
def webhook_url
"#{buildbox_endpoint('webhook')}/deliver/#{webhook_token}"
end
def compose_service_hook
hook = service_hook || build_service_hook
hook.url = webhook_url
hook.save
end
def execute(data)
service_hook.execute(data)
end
def commit_status(sha)
response = HTTParty.get(commit_status_path(sha), verify: false)
if response.code == 200 && response['status']
response['status']
else
:error
end
end
def commit_status_path(sha)
"#{buildbox_endpoint('gitlab')}/status/#{status_token}.json?commit=#{sha}"
end
def build_page(sha)
"#{project_url}/builds?commit=#{sha}"
end
def builds_path
"#{project_url}/builds?branch=#{project.default_branch}"
end
def status_img_path
"#{buildbox_endpoint('badge')}/#{status_token}.svg"
end
def title
'Buildbox'
end
def description
'Continuous integration and deployments'
end
def to_param
'buildbox'
end
def fields
[
{ type: 'text',
name: 'token',
placeholder: 'Buildbox project GitLab token' },
{ type: 'text',
name: 'project_url',
placeholder: 'https://buildbox.io/example/project' }
]
end
private
def webhook_token
token_parts.first
end
def status_token
token_parts.second
end
def token_parts
if token.present?
token.split(':')
else
[]
end
end
def buildbox_endpoint(subdomain = nil)
endpoint = 'https://buildbox.io'
if subdomain.present?
uri = Addressable::URI.parse(endpoint)
new_endpoint = "#{uri.scheme || 'http'}://#{subdomain}.#{uri.host}"
if uri.port.present?
"#{new_endpoint}:#{uri.port}"
else
new_endpoint
end
else
endpoint
end
end
end
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# property :text # properties :text
# #
class GitlabCiService < CiService class GitlabCiService < CiService
......
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
class PushoverService < Service
include HTTParty
base_uri 'https://api.pushover.net/1'
prop_accessor :api_key, :user_key, :device, :priority, :sound
validates :api_key, :user_key, :priority, presence: true, if: :activated?
def title
'Pushover'
end
def description
'Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop.'
end
def to_param
'pushover'
end
def fields
[
{ type: 'text', name: 'api_key', placeholder: 'Your application key' },
{ type: 'text', name: 'user_key', placeholder: 'Your user key' },
{ type: 'text', name: 'device', placeholder: 'Leave blank for all active devices' },
{ type: 'select', name: 'priority', choices:
[
['Lowest Priority', -2],
['Low Priority', -1],
['Normal Priority', 0],
['High Priority', 1]
],
default_choice: 0
},
{ type: 'select', name: 'sound', choices:
[
['Device default sound', nil],
['Pushover (default)', 'pushover'],
['Bike', 'bike'],
['Bugle', 'bugle'],
['Cash Register', 'cashregister'],
['Classical', 'classical'],
['Cosmic', 'cosmic'],
['Falling', 'falling'],
['Gamelan', 'gamelan'],
['Incoming', 'incoming'],
['Intermission', 'intermission'],
['Magic', 'magic'],
['Mechanical', 'mechanical'],
['Piano Bar', 'pianobar'],
['Siren', 'siren'],
['Space Alarm', 'spacealarm'],
['Tug Boat', 'tugboat'],
['Alien Alarm (long)', 'alien'],
['Climb (long)', 'climb'],
['Persistent (long)', 'persistent'],
['Pushover Echo (long)', 'echo'],
['Up Down (long)', 'updown'],
['None (silent)', 'none']
]
},
]
end
def execute(push_data)
ref = push_data[:ref].gsub('refs/heads/', '')
before = push_data[:before]
after = push_data[:after]
if before =~ /000000/
message = "#{push_data[:user_name]} pushed new branch \"#{ref}\"."
elsif after =~ /000000/
message = "#{push_data[:user_name]} deleted branch \"#{ref}\"."
else
message = "#{push_data[:user_name]} push to branch \"#{ref}\"."
end
if push_data[:total_commits_count] > 0
message << "\nTotal commits count: #{push_data[:total_commits_count]}"
end
pushover_data = {
token: api_key,
user: user_key,
device: device,
priority: priority,
title: "#{project.name_with_namespace}",
message: message,
url: push_data[:repository][:homepage],
url_title: "See project #{project.name_with_namespace}"
}
# Sound parameter MUST NOT be sent to API if not selected
if sound
pushover_data.merge!(sound: sound)
end
PushoverService.post('/messages.json', body: pushover_data)
end
end
...@@ -13,10 +13,8 @@ ...@@ -13,10 +13,8 @@
# #
class SlackService < Service class SlackService < Service
prop_accessor :room, :subdomain, :token prop_accessor :webhook
validates :room, presence: true, if: :activated? validates :webhook, presence: true, if: :activated?
validates :subdomain, presence: true, if: :activated?
validates :token, presence: true, if: :activated?
def title def title
'Slack' 'Slack'
...@@ -32,9 +30,7 @@ class SlackService < Service ...@@ -32,9 +30,7 @@ class SlackService < Service
def fields def fields
[ [
{ type: 'text', name: 'subdomain', placeholder: '' }, { type: 'text', name: 'webhook', placeholder: '' }
{ type: 'text', name: 'token', placeholder: '' },
{ type: 'text', name: 'room', placeholder: 'Ex. #general' },
] ]
end end
...@@ -44,11 +40,14 @@ class SlackService < Service ...@@ -44,11 +40,14 @@ class SlackService < Service
project_name: project_name project_name: project_name
)) ))
credentials = webhook.match(/(\w*).slack.com.*services\/(.*)/)
if credentials.present?
subdomain = credentials[1]
token = credentials[2].split("token=").last
notifier = Slack::Notifier.new(subdomain, token) notifier = Slack::Notifier.new(subdomain, token)
notifier.channel = room
notifier.username = 'GitLab'
notifier.ping(message.pretext, attachments: message.attachments) notifier.ping(message.pretext, attachments: message.attachments)
end end
end
private private
......
...@@ -11,8 +11,8 @@ ...@@ -11,8 +11,8 @@
# updated_at :datetime # updated_at :datetime
# file_name :string(255) # file_name :string(255)
# expires_at :datetime # expires_at :datetime
# private :boolean default(TRUE), not null
# type :string(255) # type :string(255)
# visibility_level :integer default(0), not null
# #
class ProjectSnippet < Snippet class ProjectSnippet < Snippet
......
...@@ -133,6 +133,10 @@ class ProjectTeam ...@@ -133,6 +133,10 @@ class ProjectTeam
max_tm_access(user.id) == Gitlab::Access::MASTER max_tm_access(user.id) == Gitlab::Access::MASTER
end end
def member?(user_id)
!!find_tm(user_id)
end
def max_tm_access(user_id) def max_tm_access(user_id)
access = [] access = []
access << project.project_members.find_by(user_id: user_id).try(:access_field) access << project.project_members.find_by(user_id: user_id).try(:access_field)
......
...@@ -30,6 +30,8 @@ class Repository ...@@ -30,6 +30,8 @@ class Repository
commit = Gitlab::Git::Commit.find(raw_repository, id) commit = Gitlab::Git::Commit.find(raw_repository, id)
commit = Commit.new(commit) if commit commit = Commit.new(commit) if commit
commit commit
rescue Rugged::OdbError => ex
nil
end end
def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false) def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false)
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
#
# To add new service you should build a class inherited from Service # To add new service you should build a class inherited from Service
# and implement a set of methods # and implement a set of methods
......
...@@ -11,14 +11,15 @@ ...@@ -11,14 +11,15 @@
# updated_at :datetime # updated_at :datetime
# file_name :string(255) # file_name :string(255)
# expires_at :datetime # expires_at :datetime
# private :boolean default(TRUE), not null
# type :string(255) # type :string(255)
# visibility_level :integer default(0), not null
# #
class Snippet < ActiveRecord::Base class Snippet < ActiveRecord::Base
include Linguist::BlobHelper include Linguist::BlobHelper
include Gitlab::VisibilityLevel
default_value_for :private, true default_value_for :visibility_level, Snippet::PRIVATE
belongs_to :author, class_name: "User" belongs_to :author, class_name: "User"
...@@ -30,10 +31,13 @@ class Snippet < ActiveRecord::Base ...@@ -30,10 +31,13 @@ class Snippet < ActiveRecord::Base
validates :title, presence: true, length: { within: 0..255 } validates :title, presence: true, length: { within: 0..255 }
validates :file_name, presence: true, length: { within: 0..255 } validates :file_name, presence: true, length: { within: 0..255 }
validates :content, presence: true validates :content, presence: true
validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values }
# Scopes # Scopes
scope :are_public, -> { where(private: false) } scope :are_internal, -> { where(visibility_level: Snippet::INTERNAL) }
scope :are_private, -> { where(private: true) } scope :are_private, -> { where(visibility_level: Snippet::PRIVATE) }
scope :are_public, -> { where(visibility_level: Snippet::PUBLIC) }
scope :public_and_internal, -> { where(visibility_level: [Snippet::PUBLIC, Snippet::INTERNAL]) }
scope :fresh, -> { order("created_at DESC") } scope :fresh, -> { order("created_at DESC") }
scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) } scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) }
scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) } scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) }
...@@ -66,6 +70,10 @@ class Snippet < ActiveRecord::Base ...@@ -66,6 +70,10 @@ class Snippet < ActiveRecord::Base
expires_at && expires_at < Time.current expires_at && expires_at < Time.current
end end
def visibility_level_field
visibility_level
end
class << self class << self
def search(query) def search(query)
where('(title LIKE :query OR file_name LIKE :query)', query: "%#{query}%") where('(title LIKE :query OR file_name LIKE :query)', query: "%#{query}%")
...@@ -76,7 +84,7 @@ class Snippet < ActiveRecord::Base ...@@ -76,7 +84,7 @@ class Snippet < ActiveRecord::Base
end end
def accessible_to(user) def accessible_to(user)
where('private = ? OR author_id = ?', false, user) where('visibility_level IN (?) OR author_id = ?', [Snippet::INTERNAL, Snippet::PUBLIC], user)
end end
end end
end end
...@@ -15,7 +15,7 @@ class Tree ...@@ -15,7 +15,7 @@ class Tree
# by markup renderer. # by markup renderer.
if available_readmes.length > 1 if available_readmes.length > 1
supported_readmes = available_readmes.select do |readme| supported_readmes = available_readmes.select do |readme|
gitlab_markdown?(readme.name) || markup?(readme.name) previewable?(readme.name)
end end
# Take the first supported readme, or the first available readme, if we # Take the first supported readme, or the first available readme, if we
......
...@@ -178,9 +178,8 @@ class User < ActiveRecord::Base ...@@ -178,9 +178,8 @@ class User < ActiveRecord::Base
scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) } scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all } scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') } scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') }
scope :ldap, -> { where('provider LIKE ?', 'ldap%') }
scope :subscribed_for_admin_email, -> { where(admin_email_unsubscribed_at: nil) } scope :subscribed_for_admin_email, -> { where(admin_email_unsubscribed_at: nil) }
scope :ldap, -> { where('provider LIKE ?', 'ldap%') }
scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active } scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active }
# #
...@@ -197,6 +196,16 @@ class User < ActiveRecord::Base ...@@ -197,6 +196,16 @@ class User < ActiveRecord::Base
end end
end end
def sort(method)
case method.to_s
when 'recent_sign_in' then reorder('users.last_sign_in_at DESC')
when 'oldest_sign_in' then reorder('users.last_sign_in_at ASC')
when 'recently_created' then reorder('users.created_at DESC')
when 'late_created' then reorder('users.created_at ASC')
else reorder("users.name ASC")
end
end
def find_for_commit(email, name) def find_for_commit(email, name)
# Prefer email match over name match # Prefer email match over name match
User.where(email: email).first || User.where(email: email).first ||
......
class IssuableBaseService < BaseService
private
def create_assignee_note(issuable)
Note.create_assignee_change_note(
issuable, issuable.project, current_user, issuable.assignee)
end
def create_milestone_note(issuable)
Note.create_milestone_change_note(
issuable, issuable.project, current_user, issuable.milestone)
end
end
module Issues module Issues
class BaseService < ::BaseService class BaseService < ::IssuableBaseService
private private
def create_assignee_note(issue)
Note.create_assignee_change_note(issue, issue.project, current_user, issue.assignee)
end
def execute_hooks(issue, action = 'open') def execute_hooks(issue, action = 'open')
issue_data = issue.to_hook_data issue_data = issue.to_hook_data
issue_url = Gitlab::UrlBuilder.new(:issue).build(issue.id) issue_url = Gitlab::UrlBuilder.new(:issue).build(issue.id)
issue_data[:object_attributes].merge!(url: issue_url, action: action) issue_data[:object_attributes].merge!(url: issue_url, action: action)
issue.project.execute_hooks(issue_data, :issue_hooks) issue.project.execute_hooks(issue_data, :issue_hooks)
end end
def create_milestone_note(issue)
Note.create_milestone_change_note(issue, issue.project, current_user, issue.milestone)
end
end end
end end
...@@ -8,9 +8,14 @@ module Issues ...@@ -8,9 +8,14 @@ module Issues
Issues::ReopenService.new(project, current_user, {}).execute(issue) Issues::ReopenService.new(project, current_user, {}).execute(issue)
when 'close' when 'close'
Issues::CloseService.new(project, current_user, {}).execute(issue) Issues::CloseService.new(project, current_user, {}).execute(issue)
when 'task_check'
issue.update_nth_task(params[:task_num].to_i, true)
when 'task_uncheck'
issue.update_nth_task(params[:task_num].to_i, false)
end end
if params.present? && issue.update_attributes(params.except(:state_event)) if params.present? && issue.update_attributes(params.except(:state_event,
:task_num))
issue.reset_events_cache issue.reset_events_cache
if issue.previous_changes.include?('milestone_id') if issue.previous_changes.include?('milestone_id')
......
module MergeRequests module MergeRequests
class BaseService < ::BaseService class BaseService < ::IssuableBaseService
private
def create_assignee_note(merge_request)
Note.create_assignee_change_note(merge_request, merge_request.project, current_user, merge_request.assignee)
end
def create_note(merge_request) def create_note(merge_request)
Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil) Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
...@@ -16,9 +10,5 @@ module MergeRequests ...@@ -16,9 +10,5 @@ module MergeRequests
merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks) merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks)
end end
end end
def create_milestone_note(merge_request)
Note.create_milestone_change_note(merge_request, merge_request.project, current_user, merge_request.milestone)
end
end end
end end
...@@ -17,9 +17,15 @@ module MergeRequests ...@@ -17,9 +17,15 @@ module MergeRequests
MergeRequests::ReopenService.new(project, current_user, {}).execute(merge_request) MergeRequests::ReopenService.new(project, current_user, {}).execute(merge_request)
when 'close' when 'close'
MergeRequests::CloseService.new(project, current_user, {}).execute(merge_request) MergeRequests::CloseService.new(project, current_user, {}).execute(merge_request)
when 'task_check'
merge_request.update_nth_task(params[:task_num].to_i, true)
when 'task_uncheck'
merge_request.update_nth_task(params[:task_num].to_i, false)
end end
if params.present? && merge_request.update_attributes(params.except(:state_event)) if params.present? && merge_request.update_attributes(
params.except(:state_event, :task_num)
)
merge_request.reset_events_cache merge_request.reset_events_cache
if merge_request.previous_changes.include?('milestone_id') if merge_request.previous_changes.include?('milestone_id')
......
...@@ -33,6 +33,26 @@ ...@@ -33,6 +33,26 @@
Users (#{@users.total_count}) Users (#{@users.total_count})
.panel-head-actions .panel-head-actions
= link_to 'Send email to users', admin_email_path, class: 'btn btn-info' = link_to 'Send email to users', admin_email_path, class: 'btn btn-info'
.dropdown.inline
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%span.light sort:
- if @sort.present?
= @sort.humanize
- else
Name
%b.caret
%ul.dropdown-menu
%li
= link_to admin_users_path(sort: nil) do
Name
= link_to admin_users_path(sort: 'recent_sign_in') do
Recent sign in
= link_to admin_users_path(sort: 'oldest_sign_in') do
Oldest sign in
= link_to admin_users_path(sort: 'recently_created') do
Recently created
= link_to admin_users_path(sort: 'late_created') do
Late created
= link_to 'New User', new_admin_user_path, class: "btn btn-new" = link_to 'New User', new_admin_user_path, class: "btn btn-new"
%ul.well-list %ul.well-list
- @users.each do |user| - @users.each do |user|
......
...@@ -6,13 +6,13 @@ ...@@ -6,13 +6,13 @@
%ul.nav.nav-tabs %ul.nav.nav-tabs
- @ldap_servers.each_with_index do |server, i| - @ldap_servers.each_with_index do |server, i|
%li{class: (:active if i==0)} %li{class: (:active if i==0)}
= link_to server['label'], "#tab-#{server.provider_name}", 'data-toggle' => 'tab' = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab'
%li %li
= link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab'
.tab-content .tab-content
- @ldap_servers.each_with_index do |server,i| - @ldap_servers.each_with_index do |server,i|
%div.tab-pane{id: "tab-#{server.provider_name}", class: (:active if i==0)} %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i==0)}
= render 'devise/sessions/new_ldap', provider: server.provider_name = render 'devise/sessions/new_ldap', provider: server['provider_name']
%div#tab-signin.tab-pane %div#tab-signin.tab-pane
= render 'devise/sessions/new_base' = render 'devise/sessions/new_base'
......
%li.commit %li.commit
.commit-row-title .commit-row-title
= link_to commit[:id][0..8], project_commit_path(project, commit[:id]), class: "commit_short_id", alt: '' = link_to truncate_sha(commit[:id]), project_commit_path(project, commit[:id]), class: "commit_short_id", alt: ''
&nbsp; &nbsp;
= gfm event_commit_title(commit[:message]), project = gfm event_commit_title(commit[:message]), project
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- event.commits.first(15).each do |commit| - event.commits.first(15).each do |commit|
%p %p
%strong= commit[:author][:name] %strong= commit[:author][:name]
= link_to "(##{commit[:id][0...8]})", project_commit_path(event.project, id: commit[:id]) = link_to "(##{truncate_sha(commit[:id])})", project_commit_path(event.project, id: commit[:id])
%i %i
at at
= commit[:timestamp].to_time.to_s(:short) = commit[:timestamp].to_time.to_s(:short)
......
...@@ -22,4 +22,4 @@ ...@@ -22,4 +22,4 @@
- if event.commits_count > 2 - if event.commits_count > 2
%span ... and #{event.commits_count - 2} more commits. %span ... and #{event.commits_count - 2} more commits.
= link_to project_compare_path(event.project, from: event.commit_from, to: event.commit_to) do = link_to project_compare_path(event.project, from: event.commit_from, to: event.commit_to) do
%strong Compare &rarr; #{event.commit_from[0..7]}...#{event.commit_to[0..7]} %strong Compare &rarr; #{truncate_sha(event.commit_from)}...#{truncate_sha(event.commit_to)}
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
= @group.name = @group.name
- if @group.description.present? - if @group.description.present?
%p %p
= auto_link @group.description, link: :urls = escaped_autolink(@group.description)
= render "projects", projects: @projects = render "projects", projects: @projects
%br %br
= render "shared_projects", projects: @shared_projects = render "shared_projects", projects: @shared_projects
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
%br %br
Used by more than 100,000 organizations, GitLab is the most popular solution to manage git repositories on-premises. Used by more than 100,000 organizations, GitLab is the most popular solution to manage git repositories on-premises.
%br %br
Read more about GitLab at #{link_to "www.gitlab.com", "https://www.gitlab.com/", target: "_blank"}. Read more about GitLab at #{link_to promo_host, promo_url, target: '_blank'}.
%hr %hr
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
%ul.well-list %ul.well-list
%li %li
See our website for See our website for
= link_to "getting help", "https://www.gitlab.com/getting-help/" = link_to 'getting help', promo_url + '/getting-help/'
%li %li
Use the Use the
= link_to 'search bar', '#', onclick: 'Shortcuts.focusSearch(event)' = link_to 'search bar', '#', onclick: 'Shortcuts.focusSearch(event)'
......
...@@ -31,12 +31,12 @@ ...@@ -31,12 +31,12 @@
.clearfix .clearfix
%hr %hr
%p
You can also specify notification level per group or per project
%br
By default all projects and groups uses notification level set above
.row.all-notifications .row.all-notifications
.col-md-6 .col-md-6
%p
You can also specify notification level per group or per project.
%br
By default all projects and groups uses notification level set above.
%h4 Groups: %h4 Groups:
%ul.bordered-list %ul.bordered-list
- @group_members.each do |users_group| - @group_members.each do |users_group|
...@@ -44,6 +44,10 @@ ...@@ -44,6 +44,10 @@
= render 'settings', type: 'group', membership: users_group, notification: notification = render 'settings', type: 'group', membership: users_group, notification: notification
.col-md-6 .col-md-6
%p
To specify notification level per project of a group you belong to,
%br
you need to be a member of the project itself, not only its group.
%h4 Projects: %h4 Projects:
%ul.bordered-list %ul.bordered-list
- @project_members.each do |project_member| - @project_members.each do |project_member|
......
.form-actions
.commit-button-annotation
= button_tag 'Commit Changes',
class: 'btn commit-btn js-commit-button btn-create'
.message
to branch
%strong= ref
= link_to 'Cancel', cancel_path,
class: 'btn btn-cancel', data: {confirm: leave_edit_message}
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
.project-home-row .project-home-row
.project-home-desc .project-home-desc
- if @project.description.present? - if @project.description.present?
= auto_link ERB::Util.html_escape(@project.description), link: :urls = escaped_autolink(@project.description)
- if can?(current_user, :admin_project, @project) - if can?(current_user, :admin_project, @project)
&ndash; &ndash;
= link_to 'Edit', edit_project_path = link_to 'Edit', edit_project_path
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
%tr %tr
%td.blame-commit %td.blame-commit
%span.commit %span.commit
= link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id" = link_to commit.short_id, project_commit_path(@project, commit), class: "commit_short_id"
&nbsp; &nbsp;
= commit_author_link(commit, avatar: true, size: 16) = commit_author_link(commit, avatar: true, size: 16)
&nbsp; &nbsp;
......
...@@ -2,6 +2,6 @@ ...@@ -2,6 +2,6 @@
.center .center
= link_to project_raw_path(@project, @id) do = link_to project_raw_path(@project, @id) do
%h1.light %h1.light
%i.fa.fa-arrow-circle-o-down-alt %i.fa.fa-download
%h4 %h4
Download (#{number_to_human_size blob.size}) Download (#{number_to_human_size blob.size})
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
= @notes_count = @notes_count
.pull-left.btn-group .pull-left.btn-group
%a.btn.btn-grouped.dropdown-toggle{ data: {toggle: :dropdown} } %a.btn.btn-grouped.dropdown-toggle{ data: {toggle: :dropdown} }
%i.fa.fa-arrow-circle-o-down-alt %i.fa.fa-download
Download as Download as
%span.caret %span.caret
%ul.dropdown-menu %ul.dropdown-menu
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
.commit-info-row .commit-info-row
%span.cgray= pluralize(@commit.parents.count, "parent") %span.cgray= pluralize(@commit.parents.count, "parent")
- @commit.parents.each do |parent| - @commit.parents.each do |parent|
= link_to parent.id[0...10], project_commit_path(@project, parent) = link_to parent.short_id, project_commit_path(@project, parent)
- if @branches.any? - if @branches.any?
.commit-info-row .commit-info-row
......
%li.commit.js-toggle-container %li.commit.js-toggle-container
.commit-row-title .commit-row-title
= link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id" = link_to commit.short_id, project_commit_path(project, commit), class: "commit_short_id"
&nbsp; &nbsp;
%span.str-truncated %span.str-truncated
= link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message" = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message"
......
%li.commit.inline-commit %li.commit.inline-commit
.commit-row-title .commit-row-title
= link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id" = link_to commit.short_id, project_commit_path(project, commit), class: "commit_short_id"
&nbsp; &nbsp;
%span.str-truncated %span.str-truncated
= link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message" = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message"
......
...@@ -93,27 +93,13 @@ ...@@ -93,27 +93,13 @@
.danger-settings.js-toggle-container .danger-settings
.centered-light-block
%h3
%i.fa.fa-exclamation-triangle
Dangerous settings
%p Project settings below may result in data loss!
= link_to '#', class: 'btn js-toggle-button' do
Show them to me
%i.fa.fa-chevron-down
.js-toggle-content.hide
- if can? current_user, :archive_project, @project - if can? current_user, :archive_project, @project
.panel.panel-default.panel.panel-warning
.panel-heading
- if @project.archived? - if @project.archived?
.panel.panel-success
.panel-heading
Unarchive project Unarchive project
- else
Archive project
.panel-body .panel-body
- if @project.archived?
%p %p
Unarchiving the project will mark its repository as active. Unarchiving the project will mark its repository as active.
%br %br
...@@ -122,8 +108,12 @@ ...@@ -122,8 +108,12 @@
%strong Once active this project shows up in the search and on the dashboard. %strong Once active this project shows up in the search and on the dashboard.
= link_to 'Unarchive', unarchive_project_path(@project), = link_to 'Unarchive', unarchive_project_path(@project),
data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." }, data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." },
method: :post, class: "btn btn-remove" method: :post, class: "btn btn-success"
- else - else
.panel.panel-warning
.panel-heading
Archive project
.panel-body
%p %p
Archiving the project will mark its repository as read-only. Archiving the project will mark its repository as read-only.
%br %br
...@@ -172,7 +162,7 @@ ...@@ -172,7 +162,7 @@
%li You can only transfer the project to namespaces you manage. %li You can only transfer the project to namespaces you manage.
%li You will need to update your local repositories to point to the new location. %li You will need to update your local repositories to point to the new location.
.form-actions .form-actions
= f.submit 'Transfer', class: "btn btn-remove" = f.submit 'Transfer', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
- else - else
.nothing-here-block Only the project owner can transfer a project .nothing-here-block Only the project owner can transfer a project
...@@ -180,12 +170,13 @@ ...@@ -180,12 +170,13 @@
.panel.panel-default.panel.panel-danger .panel.panel-default.panel.panel-danger
.panel-heading Remove project .panel-heading Remove project
.panel-body .panel-body
= form_tag(project_path(@project), method: :delete, html: { class: 'form-horizontal'}) do
%p %p
Removing the project will delete its repository and all related resources including issues, merge requests etc. Removing the project will delete its repository and all related resources including issues, merge requests etc.
%br %br
%strong Removed projects cannot be restored! %strong Removed projects cannot be restored!
= link_to 'Remove project', @project, data: { confirm: remove_project_message(@project) }, method: :delete, class: "btn btn-remove" = link_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
- else - else
.nothing-here-block Only project owner can remove a project .nothing-here-block Only project owner can remove a project
...@@ -195,3 +186,6 @@ ...@@ -195,3 +186,6 @@
%i.fa.fa-spinner.fa-spin %i.fa.fa-spinner.fa-spin
Saving project. Saving project.
%p Please wait a moment, this page will automatically refresh when ready. %p Please wait a moment, this page will automatically refresh when ready.
= render 'shared/confirm_modal', phrase: @project.path
...@@ -23,16 +23,11 @@ ...@@ -23,16 +23,11 @@
%i.fa.fa-spinner.fa-spin %i.fa.fa-spinner.fa-spin
= render 'shared/commit_message_container', params: params, = render 'shared/commit_message_container', params: params,
placeholder: "Update #{@blob.name}" placeholder: "Update #{@blob.name}"
.form-actions
= hidden_field_tag 'last_commit', @last_commit = hidden_field_tag 'last_commit', @last_commit
= hidden_field_tag 'content', '', id: "file-content" = hidden_field_tag 'content', '', id: "file-content"
= hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
.commit-button-annotation = render 'projects/commit_button', ref: @ref,
= button_tag "Commit changes", class: 'btn commit-btn js-commit-button btn-primary' cancel_path: @after_edit_path
.message
to branch
%strong= @ref
= link_to "Cancel", @after_edit_path, class: "btn btn-cancel", data: { confirm: leave_edit_message}
:javascript :javascript
ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace") ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace")
......
...@@ -26,6 +26,10 @@ ...@@ -26,6 +26,10 @@
%span %span
%i.fa.fa-clock-o %i.fa.fa-clock-o
= issue.milestone.title = issue.milestone.title
- if issue.tasks?
%span.task-status
= issue.task_status
.pull-right .pull-right
%small updated #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_update_ago')} %small updated #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_update_ago')}
......
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
.description .description
.wiki .wiki
= preserve do = preserve do
= markdown @issue.description = markdown(@issue.description, parse_tasks: true)
.context .context
%cite.cgray %cite.cgray
= render partial: 'issue_context', locals: { issue: @issue } = render partial: 'issue_context', locals: { issue: @issue }
...@@ -62,7 +62,8 @@ ...@@ -62,7 +62,8 @@
= link_to 'Close Issue', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close js-note-target-close", title: "Close Issue" = link_to 'Close Issue', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close js-note-target-close", title: "Close Issue"
.participants .participants
%cite.cgray #{@issue.participants.count} participants %cite.cgray
= pluralize(@issue.participants.count, 'participant')
- @issue.participants.each do |participant| - @issue.participants.each do |participant|
= link_to_member(@project, participant, name: false, size: 24) = link_to_member(@project, participant, name: false, size: 24)
......
...@@ -27,7 +27,9 @@ ...@@ -27,7 +27,9 @@
%span %span
%i.fa.fa-clock-o %i.fa.fa-clock-o
= merge_request.milestone.title = merge_request.milestone.title
- if merge_request.tasks?
%span.task-status
= merge_request.task_status
.pull-right .pull-right
%small updated #{time_ago_with_tooltip(merge_request.updated_at, 'bottom', 'merge_request_updated_ago')} %small updated #{time_ago_with_tooltip(merge_request.updated_at, 'bottom', 'merge_request_updated_ago')}
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
.description .description
.wiki .wiki
= preserve do = preserve do
= markdown @merge_request.description = markdown(@merge_request.description, parse_tasks: true)
.context .context
%cite.cgray %cite.cgray
......
...@@ -20,7 +20,6 @@ ...@@ -20,7 +20,6 @@
= link_to "Build page", ci_build_details_path(@merge_request) = link_to "Build page", ci_build_details_path(@merge_request)
.ci_widget .ci_widget
%strong
%i.fa.fa-spinner %i.fa.fa-spinner
Checking for CI status for #{@merge_request.last_commit_short_sha} Checking for CI status for #{@merge_request.last_commit_short_sha}
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
- if @merge_request.open? - if @merge_request.open?
.btn-group.pull-left .btn-group.pull-left
%a.btn.btn-grouped.dropdown-toggle{ data: {toggle: :dropdown} } %a.btn.btn-grouped.dropdown-toggle{ data: {toggle: :dropdown} }
%i.fa.fa-arrow-circle-o-down-alt %i.fa.fa-download
Download as Download as
%span.caret %span.caret
%ul.dropdown-menu %ul.dropdown-menu
......
.panel.mr-state-widget.panel-default .mr-state-widget
- if @merge_request.source_project.ci_service && @commits.any? - if @merge_request.source_project.ci_service && @commits.any?
.panel-heading .mr-widget-heading
= render "projects/merge_requests/show/mr_ci" = render "projects/merge_requests/show/mr_ci"
.panel-body .mr-widget-body
- if @merge_request.open? - if @merge_request.open?
- if @merge_request.source_branch_exists? && @merge_request.target_branch_exists? - if @merge_request.source_branch_exists? && @merge_request.target_branch_exists?
= render "projects/merge_requests/show/mr_accept" = render "projects/merge_requests/show/mr_accept"
...@@ -31,8 +31,8 @@ ...@@ -31,8 +31,8 @@
%br %br
Try to use different branches or push new code. Try to use different branches or push new code.
- if !@closes_issues.empty? && @merge_request.open? - if @closes_issues.present? && @merge_request.open?
.panel-footer .mr-widget-footer
%span %span
%i.fa.fa-check %i.fa.fa-check
Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'} Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'}
......
...@@ -27,14 +27,9 @@ ...@@ -27,14 +27,9 @@
.file-content.code .file-content.code
%pre#editor= params[:content] %pre#editor= params[:content]
.form-actions = hidden_field_tag 'content', '', id: 'file-content'
= hidden_field_tag 'content', '', id: "file-content" = render 'projects/commit_button', ref: @ref,
.commit-button-annotation cancel_path: project_tree_path(@project, @id)
= button_tag "Commit changes", class: 'btn commit-btn js-commit-button btn-create'
.message
to branch
%strong= @ref
= link_to "Cancel", project_tree_path(@project, @id), class: "btn btn-cancel", data: { confirm: leave_edit_message}
:javascript :javascript
ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace-src-noconflict") ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace-src-noconflict")
......
%h3.page-title Protected branches %h3.page-title Protected branches
%p.light This ability keeps stable branches secure and forces developers to use code reviews %p.light Keep stable branches secure and force developers to use Merge Requests
%hr %hr
.bs-callout.bs-callout-info .bs-callout.bs-callout-info
%p Protected branches are designed to %p Protected branches are designed to
%ul %ul
%li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"} %li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"}
%li prevents anyone from force pushing to the branch %li prevent anyone from force pushing to the branch
%li prevents anyone from deleting the branch %li prevent anyone from deleting the branch
%p Read more about #{link_to "project permissions", help_page_path("permissions", "permissions"), class: "underlined-link"} %p Read more about #{link_to "project permissions", help_page_path("permissions", "permissions"), class: "underlined-link"}
- if can? current_user, :admin_project, @project - if can? current_user, :admin_project, @project
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
- if split_button == true - if split_button == true
%span.btn-group{class: btn_class} %span.btn-group{class: btn_class}
= link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
%i.fa.fa-arrow-circle-o-down-alt %i.fa.fa-download
%span Download zip %span Download zip
%a.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' } %a.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' }
%span.caret %span.caret
...@@ -13,25 +13,25 @@ ...@@ -13,25 +13,25 @@
%ul.dropdown-menu{ role: 'menu' } %ul.dropdown-menu{ role: 'menu' }
%li %li
= link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), rel: 'nofollow' do = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), rel: 'nofollow' do
%i.fa.fa-arrow-circle-o-down-alt %i.fa.fa-download
%span Download zip %span Download zip
%li %li
= link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
%i.fa.fa-arrow-circle-o-down-alt %i.fa.fa-download
%span Download tar.gz %span Download tar.gz
%li %li
= link_to archive_project_repository_path(@project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do
%i.fa.fa-arrow-circle-o-down-alt %i.fa.fa-download
%span Download tar.bz2 %span Download tar.bz2
%li %li
= link_to archive_project_repository_path(@project, ref: ref, format: 'tar'), rel: 'nofollow' do = link_to archive_project_repository_path(@project, ref: ref, format: 'tar'), rel: 'nofollow' do
%i.fa.fa-arrow-circle-o-down-alt %i.fa.fa-download
%span Download tar %span Download tar
- else - else
%span.btn-group{class: btn_class} %span.btn-group{class: btn_class}
= link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
%i.fa.fa-arrow-circle-o-down-alt %i.fa.fa-download
%span zip %span zip
= link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz'), class: 'btn', rel: 'nofollow' do = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz'), class: 'btn', rel: 'nofollow' do
%i.fa.fa-arrow-circle-o-down-alt %i.fa.fa-download
%span tar.gz %span tar.gz
...@@ -32,8 +32,11 @@ ...@@ -32,8 +32,11 @@
- @service.fields.each do |field| - @service.fields.each do |field|
- name = field[:name] - name = field[:name]
- value = @service.send(name)
- type = field[:type] - type = field[:type]
- placeholder = field[:placeholder] - placeholder = field[:placeholder]
- choices = field[:choices]
- default_choice = field[:default_choice]
.form-group .form-group
= f.label name, class: "control-label" = f.label name, class: "control-label"
...@@ -46,6 +49,8 @@ ...@@ -46,6 +49,8 @@
= f.check_box name = f.check_box name
- elsif type == 'password' - elsif type == 'password'
= f.password_field name, class: "form-control" = f.password_field name, class: "form-control"
- elsif type == 'select'
= f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
.form-actions .form-actions
= f.submit 'Save', class: 'btn btn-save' = f.submit 'Save', class: 'btn btn-save'
......
...@@ -62,11 +62,14 @@ ...@@ -62,11 +62,14 @@
- else - else
#{link_to @project.owner_name, @project.owner} #{link_to @project.owner_name, @project.owner}
- @project.ci_services.each do |ci_service|
- if @project.gitlab_ci? - if ci_service.active? && ci_service.respond_to?(:builds_path)
%hr - if ci_service.respond_to?(:status_img_path)
= link_to @project.gitlab_ci_service.builds_path do = link_to ci_service.builds_path do
= image_tag @project.gitlab_ci_service.status_img_path, alt: "build status" = image_tag ci_service.status_img_path, alt: "build status"
- else
%span.light CI provided by
= link_to ci_service.title, ci_service.builds_path
- if readme - if readme
.tab-pane#tab-readme .tab-pane#tab-readme
......
...@@ -7,8 +7,8 @@ ...@@ -7,8 +7,8 @@
@ @
%span.monospace %span.monospace
- if commit.nil? - if commit.nil?
#{submodule_item.id[0..10]} #{truncate_sha(submodule_item.id)}
- else - else
= link_to "#{submodule_item.id[0..10]}", commit = link_to "#{truncate_sha(submodule_item.id)}", commit
%td %td
%td.hidden-xs %td.hidden-xs
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
= nav_link(path: 'wikis#git_access') do = nav_link(path: 'wikis#git_access') do
= link_to git_access_project_wikis_path(@project) do = link_to git_access_project_wikis_path(@project) do
%i.fa.fa-arrow-circle-o-down-alt %i.fa.fa-download
Git Access Git Access
- if can?(current_user, :write_wiki, @project) - if can?(current_user, :write_wiki, @project)
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
%tr %tr
%td %td
= link_to project_wiki_path(@project, @page, version_id: commit.id) do = link_to project_wiki_path(@project, @page, version_id: commit.id) do
= commit.id[0..10] = truncate_sha(commit.id)
%td %td
= commit.author.name = commit.author.name
%td %td
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
= project.name_with_namespace = project.name_with_namespace
&middot; &middot;
= link_to project_commit_path(project, note.commit_id, anchor: dom_id(note)) do = link_to project_commit_path(project, note.commit_id, anchor: dom_id(note)) do
Commit #{note.commit_id[0..8]} Commit #{truncate_sha(note.commit_id)}
- else - else
= link_to project do = link_to project do
= project.name_with_namespace = project.name_with_namespace
......
#modal-confirm-danger.modal.hide{tabindex: -1}
.modal-dialog
.modal-content
.modal-header
%a.close{href: "#", "data-dismiss" => "modal"} ×
%h4 Confirmation required
.modal-body
%p.cred.lead.js-confirm-text
%p
This action can lead to data loss.
To prevent accidental actions we ask you to confirm your intention.
%br
Please type
%code.js-confirm-danger-match #{phrase}
to proceed or close this modal to cancel
.form-group
= text_field_tag 'confirm_name_input', '', class: 'form-control js-confirm-danger-input'
.form-group
= submit_tag 'Confirm', class: "btn btn-danger js-confirm-danger-submit"
.gitlab-promo .gitlab-promo
= link_to "Homepage", "https://www.gitlab.com/" = link_to 'Homepage', promo_url
= link_to "Blog", "https://www.gitlab.com/blog/" = link_to "Blog", promo_url + '/blog/'
= link_to "@gitlabhq", "https://twitter.com/gitlabhq" = link_to "@gitlabhq", "https://twitter.com/gitlabhq"
= link_to "Requests", "http://feedback.gitlab.com/" = link_to "Requests", "http://feedback.gitlab.com/"
...@@ -10,21 +10,7 @@ ...@@ -10,21 +10,7 @@
= f.label :title, class: 'control-label' = f.label :title, class: 'control-label'
.col-sm-10= f.text_field :title, placeholder: "Example Snippet", class: 'form-control', required: true .col-sm-10= f.text_field :title, placeholder: "Example Snippet", class: 'form-control', required: true
- unless @snippet.respond_to?(:project) = render "shared/snippets/visibility_level", f: f, visibility_level: gitlab_config.default_projects_features.visibility_level, can_change_visibility_level: true
.form-group
= f.label "Access", class: 'control-label'
.col-sm-10
= f.label :private_true, class: 'radio-label' do
= f.radio_button :private, true
%span
%strong Private
(only you can see this snippet)
%br
= f.label :private_false, class: 'radio-label' do
= f.radio_button :private, false
%span
%strong Public
(GitLab users can see this snippet)
.form-group .form-group
.file-editor .file-editor
......
.form-group.project-visibility-level-holder
= f.label :visibility_level, class: 'control-label' do
Visibility Level
= link_to "(?)", help_page_path("public_access", "public_access")
.col-sm-10
- if can_change_visibility_level
- Gitlab::VisibilityLevel.values.each do |level|
.radio
- restricted = restricted_visibility_levels.include?(level)
= f.radio_button :visibility_level, level, disabled: restricted
= label "#{dom_class(@snippet)}_visibility_level", level do
= visibility_level_icon(level)
.option-title
= visibility_level_label(level)
.option-descr
= snippet_visibility_level_description(level)
- unless restricted_visibility_levels.empty?
.col-sm-10
%span.info
Some visibility level settings have been restricted by the administrator.
- else
.col-sm-10
%span.info
= visibility_level_icon(visibility_level)
%strong
= visibility_level_label(visibility_level)
.light= visibility_level_description(visibility_level)
...@@ -23,6 +23,11 @@ ...@@ -23,6 +23,11 @@
Private Private
%span.pull-right %span.pull-right
= @user.snippets.are_private.count = @user.snippets.are_private.count
= nav_tab :scope, 'are_internal' do
= link_to user_snippets_path(@user, scope: 'are_internal') do
Internal
%span.pull-right
= @user.snippets.are_internal.count
= nav_tab :scope, 'are_public' do = nav_tab :scope, 'are_public' do
= link_to user_snippets_path(@user, scope: 'are_public') do = link_to user_snippets_path(@user, scope: 'are_public') do
Public Public
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
Public snippets Public snippets
.pull-right .pull-right
- if current_user
= link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do
Add new snippet Add new snippet
= link_to user_snippets_path(current_user), class: "btn btn-grouped" do = link_to user_snippets_path(current_user), class: "btn btn-grouped" do
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
%span %span
\/ \/
Snippets Snippets
- if current_user
= link_to new_snippet_path, class: "btn btn-small add_new pull-right", title: "New Snippet" do = link_to new_snippet_path, class: "btn btn-small add_new pull-right", title: "New Snippet" do
Add new snippet Add new snippet
......
...@@ -2,7 +2,7 @@ require File.expand_path('../boot', __FILE__) ...@@ -2,7 +2,7 @@ require File.expand_path('../boot', __FILE__)
require 'rails/all' require 'rails/all'
require 'devise' require 'devise'
I18n.config.enforce_available_locales = false
Bundler.require(:default, Rails.env) Bundler.require(:default, Rails.env)
module Gitlab module Gitlab
......
...@@ -119,6 +119,7 @@ production: &base ...@@ -119,6 +119,7 @@ production: &base
# new_issue_url: "http://jira.sample/secure/CreateIssue.jspa" # new_issue_url: "http://jira.sample/secure/CreateIssue.jspa"
## Gravatar ## Gravatar
## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html
gravatar: gravatar:
enabled: true # Use user avatar image from Gravatar.com (default: true) enabled: true # Use user avatar image from Gravatar.com (default: true)
# gravatar urls: possible placeholders: %{hash} %{size} %{email} # gravatar urls: possible placeholders: %{hash} %{size} %{email}
...@@ -135,26 +136,13 @@ production: &base ...@@ -135,26 +136,13 @@ production: &base
ldap: ldap:
enabled: false enabled: false
servers: servers:
- main: # 'main' is the GitLab 'provider ID' of this LDAP server
## provider_id
#
# This identifier is used by GitLab to keep track of which LDAP server each
# GitLab user belongs to. Each LDAP server known to GitLab should have a unique
# provider_id. This identifier cannot be changed once users from the LDAP server
# have started logging in to GitLab.
#
# Format: one word, using a-z (lower case) and 0-9
# Example: 'paris' or 'uswest2'
provider_id: main
## label ## label
# #
# A human-friendly name for your LDAP server. It is OK to change the label later, # A human-friendly name for your LDAP server. It is OK to change the label later,
# for instance if you find out it is too large to fit on the web page. # for instance if you find out it is too large to fit on the web page.
# #
# Example: 'Paris' or 'Acme, Ltd.' # Example: 'Paris' or 'Acme, Ltd.'
label: 'LDAP' label: 'LDAP'
host: '_your_ldap_server' host: '_your_ldap_server'
...@@ -164,14 +152,6 @@ production: &base ...@@ -164,14 +152,6 @@ production: &base
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'
# This setting controls the amount of time between LDAP permission checks for each user.
# After this time has expired for a given user, their next interaction with GitLab (a click in the web UI, a git pull etc.) will be slower because the LDAP permission check is being performed.
# How much slower depends on your LDAP setup, but it is not uncommon for this check to add seconds of waiting time.
# The default value is to have a 'slow click' once every 3600 seconds, i.e. once per hour.
#
# Warning: if you set this value too low, every click in GitLab will be a 'slow click' for all of your LDAP users.
# sync_time: 3600
# This setting specifies if LDAP server is Active Directory LDAP server. # This setting specifies if LDAP server is Active Directory LDAP server.
# For non AD servers it skips the AD specific queries. # For non AD servers it skips the AD specific queries.
# If your LDAP server is not AD, set this to false. # If your LDAP server is not AD, set this to false.
...@@ -203,6 +183,14 @@ production: &base ...@@ -203,6 +183,14 @@ production: &base
# #
user_filter: '' user_filter: ''
# This setting controls the amount of time between LDAP permission checks for each user.
# After this time has expired for a given user, their next interaction with GitLab (a click in the web UI, a git pull etc.) will be slower because the LDAP permission check is being performed.
# How much slower depends on your LDAP setup, but it is not uncommon for this check to add seconds of waiting time.
# The default value is to have a 'slow click' once every 3600 seconds, i.e. once per hour.
#
# Warning: if you set this value too low, every click in GitLab will be a 'slow click' for all of your LDAP users.
# sync_time: 3600
# Base where we can search for groups # Base where we can search for groups
# #
# Ex. ou=Groups,dc=gitlab,dc=example # Ex. ou=Groups,dc=gitlab,dc=example
...@@ -222,6 +210,14 @@ production: &base ...@@ -222,6 +210,14 @@ production: &base
# #
sync_ssh_keys: false sync_ssh_keys: false
# GitLab EE only: add more LDAP servers
# Choose an ID made of a-z and 0-9 . This ID will be stored in the database
# so that GitLab can remember which LDAP server a user belongs to.
# uswest2:
# label:
# host:
# ....
## OmniAuth settings ## OmniAuth settings
omniauth: omniauth:
# Allow login via Twitter, Google, etc. using OmniAuth providers # Allow login via Twitter, Google, etc. using OmniAuth providers
...@@ -351,8 +347,7 @@ test: ...@@ -351,8 +347,7 @@ test:
ldap: ldap:
enabled: false enabled: false
servers: servers:
- main:
provider_id: main
label: ldap label: ldap
host: 127.0.0.1 host: 127.0.0.1
port: 3890 port: 3890
...@@ -363,5 +358,6 @@ test: ...@@ -363,5 +358,6 @@ test:
group_base: 'ou=groups,dc=example,dc=com' group_base: 'ou=groups,dc=example,dc=com'
admin_group: '' admin_group: ''
sync_ssh_keys: false sync_ssh_keys: false
staging: staging:
<<: *base <<: *base
...@@ -62,17 +62,21 @@ if Settings.ldap['enabled'] || Rails.env.test? ...@@ -62,17 +62,21 @@ if Settings.ldap['enabled'] || Rails.env.test?
if Settings.ldap['host'].present? if Settings.ldap['host'].present?
server = Settings.ldap.except('sync_time') server = Settings.ldap.except('sync_time')
server['label'] = 'LDAP' server['label'] = 'LDAP'
server['provider_id'] = 'main' server['provider_name'] = 'ldap'
Settings.ldap['servers'] = [server] Settings.ldap['servers'] = {
'ldap' => server
}
end end
Settings.ldap['servers'].each do |server| Settings.ldap['servers'].each do |key, server|
server['allow_username_or_email_login'] = false if server['allow_username_or_email_login'].nil? server['allow_username_or_email_login'] = false if server['allow_username_or_email_login'].nil?
server['active_directory'] = true if server['active_directory'].nil? server['active_directory'] = true if server['active_directory'].nil?
server['provider_name'] = "ldap#{server['provider_id']}".downcase server['provider_name'] ||= "ldap#{key}".downcase
server['sync_time'] = 3600 if server['sync_time'].nil?
server['provider_class'] = OmniAuth::Utils.camelize(server['provider_name']) server['provider_class'] = OmniAuth::Utils.camelize(server['provider_name'])
end end
end end
Settings['omniauth'] ||= Settingslogic.new({}) Settings['omniauth'] ||= Settingslogic.new({})
Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil? Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil?
Settings.omniauth['providers'] ||= [] Settings.omniauth['providers'] ||= []
......
module OmniAuth::Strategies module OmniAuth::Strategies
Gitlab.config.ldap.servers.each_with_index do |server| Gitlab::LDAP::Config.servers.each do |server|
const_set(server.provider_class, Class.new(LDAP)) # do not redeclare LDAP
next if server['provider_name'] == 'ldap'
const_set(server['provider_class'], Class.new(LDAP))
end
end
OmniauthCallbacksController.class_eval do
Gitlab::LDAP::Config.servers.each do |server|
alias_method server['provider_name'], :ldap
end end
end end
...@@ -205,14 +205,14 @@ Devise.setup do |config| ...@@ -205,14 +205,14 @@ Devise.setup do |config|
# end # end
if Gitlab.config.ldap.enabled if Gitlab.config.ldap.enabled
Gitlab.config.ldap.servers.each do |server| Gitlab::LDAP::Config.servers.each do |server|
if server['allow_username_or_email_login'] if server['allow_username_or_email_login']
email_stripping_proc = ->(name) {name.gsub(/@.*$/,'')} email_stripping_proc = ->(name) {name.gsub(/@.*$/,'')}
else else
email_stripping_proc = ->(name) {name} email_stripping_proc = ->(name) {name}
end end
config.omniauth server.provider_name, config.omniauth server['provider_name'],
host: server['host'], host: server['host'],
base: server['base'], base: server['base'],
uid: server['uid'], uid: server['uid'],
......
...@@ -21,7 +21,7 @@ admin.save! ...@@ -21,7 +21,7 @@ admin.save!
admin.confirm! admin.confirm!
if admin.valid? if admin.valid?
puts %q[ puts %Q[
Administrator account created: Administrator account created:
login.........root login.........root
......
class MoveSlackServiceToWebhook < ActiveRecord::Migration
def change
SlackService.all.each do |slack_service|
if ["token", "subdomain"].all? { |property| slack_service.properties.key? property }
token = slack_service.properties['token']
subdomain = slack_service.properties['subdomain']
webhook = "https://#{subdomain}.slack.com/services/hooks/incoming-webhook?token=#{token}"
slack_service.properties['webhook'] = webhook
slack_service.properties.delete('token')
slack_service.properties.delete('subdomain')
# Room is configured on the Slack side
slack_service.properties.delete('room')
slack_service.save!
end
end
end
end
class AddVisibilityLevelToSnippet < ActiveRecord::Migration
def up
add_column :snippets, :visibility_level, :integer, :default => 0, :null => false
Snippet.where(private: true).update_all(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
Snippet.where(private: false).update_all(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
add_index :snippets, :visibility_level
remove_column :snippets, :private
end
def down
add_column :snippets, :private, :boolean, :default => false, :null => false
Snippet.where(visibility_level: Gitlab::VisibilityLevel::INTERNAL).update_all(private: false)
Snippet.where(visibility_level: Gitlab::VisibilityLevel::PRIVATE).update_all(private: true)
remove_column :snippets, :visibility_level
end
end
...@@ -338,14 +338,15 @@ ActiveRecord::Schema.define(version: 20141010132608) do ...@@ -338,14 +338,15 @@ ActiveRecord::Schema.define(version: 20141010132608) do
t.datetime "updated_at" t.datetime "updated_at"
t.string "file_name" t.string "file_name"
t.datetime "expires_at" t.datetime "expires_at"
t.boolean "private", default: true, null: false
t.string "type" t.string "type"
t.integer "visibility_level", default: 0, null: false
end end
add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree
add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree
add_index "snippets", ["expires_at"], name: "index_snippets_on_expires_at", using: :btree add_index "snippets", ["expires_at"], name: "index_snippets_on_expires_at", using: :btree
add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree
add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree
create_table "taggings", force: true do |t| create_table "taggings", force: true do |t|
t.integer "tag_id" t.integer "tag_id"
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
- [Email](tools/email.md) Email GitLab users from GitLab - [Email](tools/email.md) Email GitLab users from GitLab
- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page. - [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page.
- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages. - [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
## Contributor documentation ## Contributor documentation
......
# Use Libravatar service with GitLab
GitLab by default supports [Gravatar](gravatar.com) avatar service.
Libravatar is a service which delivers your avatar (profile picture) to other websites and their API is
[heavily based on gravatar](http://wiki.libravatar.org/api/).
This means that it is not complicated to switch to Libravatar avatar service or even self hosted Libravatar server.
# Configuration
In [gitlab.yml gravatar section](https://gitlab.com/gitlab-org/gitlab-ce/blob/672bd3902d86b78d730cea809fce312ec49d39d7/config/gitlab.yml.example#L122) set
the configuration options as follows:
## For HTTP
```yml
gravatar:
enabled: true
# gravatar urls: possible placeholders: %{hash} %{size} %{email}
plain_url: "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
```
## For HTTPS
```yml
gravatar:
enabled: true
# gravatar urls: possible placeholders: %{hash} %{size} %{email}
ssl_url: "https://seccdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
```
## Self-hosted
If you are [running your own libravatar service](http://wiki.libravatar.org/running_your_own/) the url will be different in the configuration
but the important part is to provide the same placeholders so GitLab can parse the url correctly.
For example, you host a service on `http://libravatar.example.com` the `plain_url` you need to supply in `gitlab.yml` is
`http://libravatar.example.com/avatar/%{hash}?s=%{size}&d=identicon`
## Omnibus-gitlab example
In `/etc/gitlab/gitlab.rb`:
#### For http
```ruby
gitlab_rails['gravatar_enabled'] = true
gitlab_rails['gravatar_plain_url'] = "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
```
#### For https
```ruby
gitlab_rails['gravatar_enabled'] = true
gitlab_rails['gravatar_ssl_url'] = "https://seccdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
```
Run `sudo gitlab-ctl reconfigure` for changes to take effect.
## Default URL for missing images
[Libravatar supports different sets](http://wiki.libravatar.org/api/) of `missing images` for emails not found on the Libravatar service.
In order to use a different set other than `identicon`, replace `&d=identicon` portion of the url with another supported set.
For example, you can use `retro` set in which case url would look like: `plain_url: "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=retro"`
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
## Software delivery ## Software delivery
There are two editions of GitLab: [Enterprise Edition](https://www.gitlab.com/gitlab-ee/) (EE) and [Community Edition](https://www.gitlab.com/gitlab-ce/) (CE). GitLab CE is delivered via git from the [gitlabhq repository](https://gitlab.com/gitlab-org/gitlab-ce/tree/master). New versions of GitLab are released in stable branches and the master branch is for bleeding edge development. There are two editions of GitLab: [Enterprise Edition](https://about.gitlab.com/gitlab-ee/) (EE) and [Community Edition](https://about.gitlab.com/gitlab-ce/) (CE). GitLab CE is delivered via git from the [gitlabhq repository](https://gitlab.com/gitlab-org/gitlab-ce/tree/master). New versions of GitLab are released in stable branches and the master branch is for bleeding edge development.
EE releases are available not long after CE releases. To obtain the GitLab EE there is a [repository at gitlab.com](https://gitlab.com/subscribers/gitlab-ee). For more information about the release process see the section 'New versions and upgrading' in the readme. EE releases are available not long after CE releases. To obtain the GitLab EE there is a [repository at gitlab.com](https://gitlab.com/subscribers/gitlab-ee). For more information about the release process see the section 'New versions and upgrading' in the readme.
......
...@@ -6,13 +6,13 @@ Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitla ...@@ -6,13 +6,13 @@ Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitla
![Select latest branch](https://i.imgur.com/Lrdxk1k.png) ![Select latest branch](https://i.imgur.com/Lrdxk1k.png)
If the highest number stable branch is unclear please check the [GitLab Blog](https://www.gitlab.com/blog/) for installation guide links by version. If the highest number stable branch is unclear please check the [GitLab Blog](https://about.gitlab.com/blog/) for installation guide links by version.
## Important Notes ## Important Notes
This guide is long because it covers many cases and includes all commands you need, this is [one of the few installation scripts that actually works out of the box](https://twitter.com/robinvdvleuten/status/424163226532986880). This guide is long because it covers many cases and includes all commands you need, this is [one of the few installation scripts that actually works out of the box](https://twitter.com/robinvdvleuten/status/424163226532986880).
This installation guide was created for and tested on **Debian/Ubuntu** operating systems. Please read [doc/install/requirements.md](./requirements.md) for hardware and operating system requirements. If you want to install on RHEL/CentOS we recommend using the [Omnibus packages](https://www.gitlab.com/downloads/). This installation guide was created for and tested on **Debian/Ubuntu** operating systems. Please read [doc/install/requirements.md](./requirements.md) for hardware and operating system requirements. If you want to install on RHEL/CentOS we recommend using the [Omnibus packages](https://about.gitlab.com/downloads/).
This is the official installation guide to set up a production server. To set up a **development installation** or for many other installation options please see [the installation section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#installation). This is the official installation guide to set up a production server. To set up a **development installation** or for many other installation options please see [the installation section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#installation).
......
...@@ -85,9 +85,9 @@ Redis stores all user sessions and the background task queue. ...@@ -85,9 +85,9 @@ Redis stores all user sessions and the background task queue.
The storage requirements for Redis are minimal, about 25kB per user. The storage requirements for Redis are minimal, about 25kB per user.
Sidekiq processes the background jobs with a multithreaded process. Sidekiq processes the background jobs with a multithreaded process.
This process starts with the entire Rails stack (200MB+) but it can grow over time due to memory leaks. This process starts with the entire Rails stack (200MB+) but it can grow over time due to memory leaks.
On a very active server (10.000 active users) the Sidekiq process can use 1GB+ of memory. On a very active server (10,000 active users) the Sidekiq process can use 1GB+ of memory.
## Supported webbrowsers ## Supported web browsers
- Chrome (Latest stable version) - Chrome (Latest stable version)
- Firefox (Latest released version) - Firefox (Latest released version)
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
* [Code and Syntax Highlighting](#code-and-syntax-highlighting) * [Code and Syntax Highlighting](#code-and-syntax-highlighting)
* [Emoji](#emoji) * [Emoji](#emoji)
* [Special GitLab references](#special-gitlab-references) * [Special GitLab references](#special-gitlab-references)
* [Task lists](#task-lists)
**[Standard Markdown](#standard-markdown)** **[Standard Markdown](#standard-markdown)**
...@@ -177,6 +178,24 @@ GFM will recognize the following: ...@@ -177,6 +178,24 @@ GFM will recognize the following:
- 1234567 : for commits - 1234567 : for commits
- \[file\](path/to/file) : for file references - \[file\](path/to/file) : for file references
GFM also recognizes references to commits, issues, and merge requests in other projects:
- namespace/project#123 : for issues
- namespace/project!123 : for merge requests
- namespace/project@1234567 : for commits
## Task Lists
You can add task lists to merge request and issue descriptions to keep track of to-do items. To create a task, add an unordered list to the description in an issue or merge request, formatted like so:
```no-highlight
* [x] Completed task
* [ ] Unfinished task
* [x] Nested task
```
Task lists can only be created in descriptions, not in titles or comments. Task item state can be managed by editing the description's Markdown or by clicking the rendered checkboxes.
# Standard Markdown # Standard Markdown
## Headers ## Headers
......
...@@ -8,7 +8,7 @@ Do a security release when there is a critical issue that needs to be addresses ...@@ -8,7 +8,7 @@ Do a security release when there is a critical issue that needs to be addresses
## Security vulnerability disclosure ## Security vulnerability disclosure
Please report suspected security vulnerabilities in private to <support@gitlab.com>, also see the [disclosure section on the GitLab.com website](http://www.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. Please report suspected security vulnerabilities in private to <support@gitlab.com>, also see the [disclosure section on the GitLab.com website](http://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities.
## Release Procedure ## Release Procedure
...@@ -21,7 +21,7 @@ Please report suspected security vulnerabilities in private to <support@gitlab.c ...@@ -21,7 +21,7 @@ Please report suspected security vulnerabilities in private to <support@gitlab.c
1. Send out an email to the 'GitLab Newsletter' mailing list on MailChimp (or the 'Subscribers' list if the security fix is for EE only) 1. Send out an email to the 'GitLab Newsletter' mailing list on MailChimp (or the 'Subscribers' list if the security fix is for EE only)
1. Send out an email to [the community google mailing list](https://groups.google.com/forum/#!forum/gitlabhq) 1. Send out an email to [the community google mailing list](https://groups.google.com/forum/#!forum/gitlabhq)
1. Post a signed copy of our complete announcement to [oss-security](http://www.openwall.com/lists/oss-security/) and request a CVE number 1. Post a signed copy of our complete announcement to [oss-security](http://www.openwall.com/lists/oss-security/) and request a CVE number
1. Add the security researcher to the [Security Researcher Acknowledgments list](http://www.gitlab.com/vulnerability-acknowledgements/) 1. Add the security researcher to the [Security Researcher Acknowledgments list](http://about.gitlab.com/vulnerability-acknowledgements/)
1. Thank the security researcher in an email for their cooperation 1. Thank the security researcher in an email for their cooperation
1. Update the blog post and the CHANGELOG when we receive the CVE number 1. Update the blog post and the CHANGELOG when we receive the CVE number
......
...@@ -64,12 +64,12 @@ sudo gem install bundler --no-ri --no-rdoc ...@@ -64,12 +64,12 @@ sudo gem install bundler --no-ri --no-rdoc
```bash ```bash
cd /home/git/gitlab cd /home/git/gitlab
sudo -u git -H git fetch --all sudo -u git -H git fetch --all
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
``` ```
For GitLab Community Edition: For GitLab Community Edition:
```bash ```bash
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
sudo -u git -H git checkout 7-3-stable sudo -u git -H git checkout 7-3-stable
``` ```
...@@ -78,7 +78,6 @@ OR ...@@ -78,7 +78,6 @@ OR
For GitLab Enterprise Edition: For GitLab Enterprise Edition:
```bash ```bash
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
sudo -u git -H git checkout 7-3-stable-ee sudo -u git -H git checkout 7-3-stable-ee
``` ```
......
...@@ -18,12 +18,12 @@ sudo service gitlab stop ...@@ -18,12 +18,12 @@ sudo service gitlab stop
```bash ```bash
cd /home/git/gitlab cd /home/git/gitlab
sudo -u git -H git fetch --all sudo -u git -H git fetch --all
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
``` ```
For GitLab Community Edition: For GitLab Community Edition:
```bash ```bash
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
sudo -u git -H git checkout 7-3-stable sudo -u git -H git checkout 7-3-stable
``` ```
...@@ -32,7 +32,6 @@ OR ...@@ -32,7 +32,6 @@ OR
For GitLab Enterprise Edition: For GitLab Enterprise Edition:
```bash ```bash
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
sudo -u git -H git checkout 7-3-stable-ee sudo -u git -H git checkout 7-3-stable-ee
``` ```
......
# From 7.3 to 7.4
## GitLab 7.4 has not been released yet!
This document currently just serves as a place to keep track of updates that will be needed for the 7.4 update.
## Update config files
* Add `collation: utf8_general_ci` to config/database.yml as seen in [config/database.yml.mysql](config/database.yml.mysql)
## Optional optimizations for GitLab setups with MySQL databases
Only applies if running MySQL database created with GitLab 6.7 or earlier. If you are not experiencing any issues you may not need the following instructions however following them will bring your database in line with the latest recommended installation configuration and help avoid future issues. Be sure to follow these directions exactly. These directions should be safe for any MySQL instance but to be sure make a current MySQL database backup beforehand.
```
# Secure your MySQL installation (added in GitLab 6.2)
sudo mysql_secure_installation
# Login to MySQL
mysql -u root -p
# do not type the 'mysql>', this is part of the prompt
# Convert all tables to use the InnoDB storage engine (added in GitLab 6.8)
SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' ENGINE=InnoDB;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `ENGINE` <> 'InnoDB' AND `TABLE_TYPE` = 'BASE TABLE';
# If previous query returned results, copy & run all outputed SQL statements
# Find MySQL users
mysql> SELECT user FROM mysql.user WHERE user LIKE '%git%';
# If git user exists and gitlab user does not exist
# you are done with the database cleanup tasks
mysql> \q
# If both users exist skip to Delete gitlab user
# Create new user for GitLab (changed in GitLab 6.4)
# change $password in the command below to a real password you pick
mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password';
# Grant the git user necessary permissions on the database
mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON `gitlabhq_production`.* TO 'git'@'localhost';
# Delete the old gitlab user
mysql> DELETE FROM mysql.user WHERE user='gitlab';
# Quit the database session
mysql> \q
# Try connecting to the new database with the new user
sudo -u git -H mysql -u git -p -D gitlabhq_production
# Type the password you replaced $password with earlier
# You should now see a 'mysql>' prompt
# Quit the database session
mysql> \q
# Update database configuration details
# See config/database.yml.mysql for latest recommended configuration details
# Remove the reaping_frequency setting line if it exists (removed in GitLab 6.8)
# Set production -> pool: 10 (updated in GitLab 5.3)
# Set production -> username: git
# Set production -> password: the password your replaced $password with earlier
sudo -u git -H editor /home/git/gitlab/config/database.yml
```
...@@ -26,7 +26,7 @@ After getting used to these three steps the branching model becomes the challeng ...@@ -26,7 +26,7 @@ After getting used to these three steps the branching model becomes the challeng
Since many organizations new to git have no conventions how to work with it, it can quickly become a mess. Since many organizations new to git have no conventions how to work with it, it can quickly become a mess.
The biggest problem they run into is that many long running branches that each contain part of the changes are around. The biggest problem they run into is that many long running branches that each contain part of the changes are around.
People have a hard time figuring out which branch they should develop on or deploy to production. People have a hard time figuring out which branch they should develop on or deploy to production.
Frequently the reaction to this problem is to adopt a standardized pattern such as [git flow](http://nvie.com/posts/a-successful-git-branching-model/) and [GitHub flow](https://guides.github.com/introduction/flow/index.html) Frequently the reaction to this problem is to adopt a standardized pattern such as [git flow](http://nvie.com/posts/a-successful-git-branching-model/) and [GitHub flow](http://scottchacon.com/2011/08/31/github-flow.html)
We think there is still room for improvement and will detail a set of practices we call GitLab flow. We think there is still room for improvement and will detail a set of practices we call GitLab flow.
# Git flow and its problems # Git flow and its problems
...@@ -309,3 +309,8 @@ If you need to merge in another branch after starting explain the reason in the ...@@ -309,3 +309,8 @@ If you need to merge in another branch after starting explain the reason in the
If you have not pushed your commits to a shared location yet you can also rebase on master or another feature branch. If you have not pushed your commits to a shared location yet you can also rebase on master or another feature branch.
Do not merge in upstream if your code will work and merge cleanly without doing so, Linus even says that [you should never merge in upstream at random points, only at major releases](http://lwn.net/Articles/328438/). Do not merge in upstream if your code will work and merge cleanly without doing so, Linus even says that [you should never merge in upstream at random points, only at major releases](http://lwn.net/Articles/328438/).
Merging only when needed prevents creating merge commits in your feature branch that later end up littering the master history. Merging only when needed prevents creating merge commits in your feature branch that later end up littering the master history.
### References
- [Sketch file](https://www.dropbox.com/s/58dvsj5votbwrzv/git_flows.sketch?dl=0) with vectors of images in this article
- [Git Flow by Vincent Driessen](http://nvie.com/posts/a-successful-git-branching-model/)
@admin @admin
Feature: Admin active tab Feature: Admin Active Tab
Background: Background:
Given I sign in as an admin Given I sign in as an admin
......
@dashboard @dashboard
Feature: Dashboard active tab Feature: Dashboard Active Tab
Background: Background:
Given I sign in as a user Given I sign in as a user
......
@dashboard @dashboard
Feature: Dashboard with archived projects Feature: Dashboard Archived Projects
Background: Background:
Given I sign in as a user Given I sign in as a user
And I own project "Shop" And I own project "Shop"
......
@dashboard @dashboard
Feature: Event filters Feature: Event Filters
Background: Background:
Given I sign in as a user Given I sign in as a user
And I own a project And I own a project
......
@dashboard @dashboard
Feature: Help Feature: Dashboard Help
Background: Background:
Given I sign in as a user Given I sign in as a user
And I visit the "Rake Tasks" help page And I visit the "Rake Tasks" help page
......
@dashboard @dashboard
Feature: Dashboard projects Feature: Dashboard Projects
Background: Background:
Given I sign in as a user Given I sign in as a user
And I own project "Shop" And I own project "Shop"
......
@dashboard @dashboard
Feature: Dashboard shortcuts Feature: Dashboard Shortcuts
Background: Background:
Given I sign in as a user Given I sign in as a user
And I visit dashboard page And I visit dashboard page
......
@public @public
Feature: Explore Groups Feature Feature: Explore Groups
Background: Background:
Given group "TestGroup" has private project "Enterprise" Given group "TestGroup" has private project "Enterprise"
......
@public @public
Feature: Explore Projects Feature Feature: Explore Projects
Background: Background:
Given public project "Community" Given public project "Community"
And internal project "Internal" And internal project "Internal"
......
@profile @profile
Feature: Profile active tab Feature: Profile Active Tab
Background: Background:
Given I sign in as a user Given I sign in as a user
......
Feature: Project active tab Feature: Project Active Tab
Background: Background:
Given I sign in as a user Given I sign in as a user
And I own a project And I own a project
......
Feature: Project Browse branches Feature: Project Commits Branches
Background: Background:
Given I sign in as a user Given I sign in as a user
And I own project "Shop" And I own project "Shop"
......
Feature: Comments on commits Feature: Project Commits Comments
Background: Background:
Given I sign in as a user Given I sign in as a user
And I own project "Shop" And I own project "Shop"
......
Feature: Project Browse commits Feature: Project Commits
Background: Background:
Given I sign in as a user Given I sign in as a user
And I own a project And I own a project
......
Feature: Comments on commit diffs Feature: Project Commits Diff Comments
Background: Background:
Given I sign in as a user Given I sign in as a user
And I own project "Shop" And I own project "Shop"
......
Feature: Project Browse tags Feature: Project Commits Tags
Background: Background:
Given I sign in as a user Given I sign in as a user
And I own project "Shop" And I own project "Shop"
......
Feature: Project Browse Commits User Lookup Feature: Project Commits User Lookup
Background: Background:
Given I sign in as a user Given I sign in as a user
And I own a project And I own a project
......
Feature: Create Project Feature: Project Create
In order to get access to project sections In order to get access to project sections
A user with ability to create a project A user with ability to create a project
Should be able to create a new one Should be able to create a new one
......
Feature: Fork Project Feature: Project Fork
Background: Background:
Given I sign in as a user Given I sign in as a user
And I am a member of project "Shop" And I am a member of project "Shop"
......
Feature: Project Filter Labels Feature: Project Issues Filter Labels
Background: Background:
Given I sign in as a user Given I sign in as a user
And I own project "Shop" And I own project "Shop"
......
...@@ -126,3 +126,36 @@ Feature: Project Issues ...@@ -126,3 +126,36 @@ Feature: Project Issues
When I click label 'bug' When I click label 'bug'
And I should see "Release 0.4" in issues And I should see "Release 0.4" in issues
And I should not see "Tweet control" in issues And I should not see "Tweet control" in issues
Scenario: Issue description should render task checkboxes
Given project "Shop" has "Tasks-open" open issue with task markdown
When I visit issue page "Tasks-open"
Then I should see task checkboxes in the description
@javascript
Scenario: Issue notes should not render task checkboxes
Given project "Shop" has "Tasks-open" open issue with task markdown
When I visit issue page "Tasks-open"
And I leave a comment with task markdown
Then I should not see task checkboxes in the comment
# Task status in issues list
Scenario: Issues list should display task status
Given project "Shop" has "Tasks-open" open issue with task markdown
When I visit project "Shop" issues page
Then I should see the task status for the Taskable
# Toggling task items
@javascript
Scenario: Task checkboxes should be enabled for an open issue
Given project "Shop" has "Tasks-open" open issue with task markdown
When I visit issue page "Tasks-open"
Then Task checkboxes should be enabled
@javascript
Scenario: Task checkboxes should be disabled for a closed issue
Given project "Shop" has "Tasks-closed" closed issue with task markdown
When I visit issue page "Tasks-closed"
Then Task checkboxes should be disabled
Feature: Project Labels Feature: Project Issues Labels
Background: Background:
Given I sign in as a user Given I sign in as a user
And I own project "Shop" And I own project "Shop"
......
Feature: Project Milestones Feature: Project Issues Milestones
Background: Background:
Given I sign in as a user Given I sign in as a user
And I own project "Shop" And I own project "Shop"
......
...@@ -96,6 +96,16 @@ Feature: Project Merge Requests ...@@ -96,6 +96,16 @@ Feature: Project Merge Requests
And I leave a comment with a header containing "Comment with a header" And I leave a comment with a header containing "Comment with a header"
Then The comment with the header should not have an ID Then The comment with the header should not have an ID
Scenario: Merge request description should render task checkboxes
Given project "Shop" has "MR-task-open" open MR with task markdown
When I visit merge request page "MR-task-open"
Then I should see task checkboxes in the description
Scenario: Merge request notes should not render task checkboxes
Given project "Shop" has "MR-task-open" open MR with task markdown
When I visit merge request page "MR-task-open"
Then I should not see task checkboxes in the comment
# Toggling inline comments # Toggling inline comments
@javascript @javascript
...@@ -105,7 +115,7 @@ Feature: Project Merge Requests ...@@ -105,7 +115,7 @@ Feature: Project Merge Requests
And I switch to the diff tab And I switch to the diff tab
And I leave a comment like "Line is wrong" on line 39 of the second file And I leave a comment like "Line is wrong" on line 39 of the second file
And I click link "Hide inline discussion" of the second file And I click link "Hide inline discussion" of the second file
Then I should not see a comment like "Line is wrong" in the second file Then I should not see a comment like "Line is wrong here" in the second file
@javascript @javascript
Scenario: I show comments on a merge request diff with comments in a single file Scenario: I show comments on a merge request diff with comments in a single file
...@@ -113,8 +123,6 @@ Feature: Project Merge Requests ...@@ -113,8 +123,6 @@ Feature: Project Merge Requests
And I visit merge request page "Bug NS-05" And I visit merge request page "Bug NS-05"
And I switch to the diff tab And I switch to the diff tab
And I leave a comment like "Line is wrong" on line 39 of the second file And I leave a comment like "Line is wrong" on line 39 of the second file
And I click link "Hide inline discussion" of the second file
And I click link "Show inline discussion" of the second file
Then I should see a comment like "Line is wrong" in the second file Then I should see a comment like "Line is wrong" in the second file
@javascript @javascript
...@@ -125,7 +133,7 @@ Feature: Project Merge Requests ...@@ -125,7 +133,7 @@ Feature: Project Merge Requests
And I leave a comment like "Line is correct" on line 12 of the first file And I leave a comment like "Line is correct" on line 12 of the first file
And I leave a comment like "Line is wrong" on line 39 of the second file And I leave a comment like "Line is wrong" on line 39 of the second file
And I click link "Hide inline discussion" of the second file And I click link "Hide inline discussion" of the second file
Then I should not see a comment like "Line is wrong" in the second file Then I should not see a comment like "Line is wrong here" in the second file
And I should still see a comment like "Line is correct" in the first file And I should still see a comment like "Line is correct" in the first file
@javascript @javascript
...@@ -162,3 +170,25 @@ Feature: Project Merge Requests ...@@ -162,3 +170,25 @@ Feature: Project Merge Requests
And I leave a comment like "Line is wrong" on line 39 of the second file And I leave a comment like "Line is wrong" on line 39 of the second file
And I click Side-by-side Diff tab And I click Side-by-side Diff tab
Then I should see comments on the side-by-side diff page Then I should see comments on the side-by-side diff page
# Task status in issues list
Scenario: Merge requests list should display task status
Given project "Shop" has "MR-task-open" open MR with task markdown
When I visit project "Shop" merge requests page
Then I should see the task status for the Taskable
# Toggling task items
@javascript
Scenario: Task checkboxes should be enabled for an open merge request
Given project "Shop" has "MR-task-open" open MR with task markdown
When I visit merge request page "MR-task-open"
Then Task checkboxes should be enabled
@javascript
Scenario: Task checkboxes should be disabled for a closed merge request
Given project "Shop" has "MR-task-open" open MR with task markdown
And I visit merge request page "MR-task-open"
And I click link "Close"
Then Task checkboxes should be disabled
Feature: Project Feature Feature: Project
Background: Background:
Given I sign in as a user Given I sign in as a user
And I own project "Shop" And I own project "Shop"
......
...@@ -43,6 +43,12 @@ Feature: Project Services ...@@ -43,6 +43,12 @@ Feature: Project Services
And I fill Slack settings And I fill Slack settings
Then I should see Slack service settings saved Then I should see Slack service settings saved
Scenario: Activate Pushover service
When I visit project "Shop" services page
And I click Pushover service link
And I fill Pushover settings
Then I should see Pushover service settings saved
Scenario: Activate email on push service Scenario: Activate email on push service
When I visit project "Shop" services page When I visit project "Shop" services page
And I click email on push service link And I click email on push service link
......
@dashboard @dashboard
Feature: Project shortcuts Feature: Project Shortcuts
Background: Background:
Given I sign in as a user Given I sign in as a user
And I own a project And I own a project
......
Feature: Project Browse files Feature: Project Source Browse Files
Background: Background:
Given I sign in as a user Given I sign in as a user
And I own project "Shop" And I own project "Shop"
...@@ -30,7 +30,7 @@ Feature: Project Browse files ...@@ -30,7 +30,7 @@ Feature: Project Browse files
And I edit code And I edit code
And I fill the new file name And I fill the new file name
And I fill the commit message And I fill the commit message
And I click on "Commit changes" And I click on "Commit Changes"
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
...@@ -46,7 +46,7 @@ Feature: Project Browse files ...@@ -46,7 +46,7 @@ Feature: Project Browse files
And I click button "Edit" And I click button "Edit"
And I edit code And I edit code
And I fill the commit message And I fill the commit message
And I click on "Commit changes" And I click on "Commit Changes"
Then I am redirected to the ".gitignore" Then I am redirected to the ".gitignore"
And I should see its new content And I should see its new content
......
Feature: Project Browse git repo Feature: Project Source Git Blame
Background: Background:
Given I sign in as a user Given I sign in as a user
And I own project "Shop" And I own project "Shop"
......
Feature: Project markdown render Feature: Project Source Markdown Render
Background: Background:
Given I sign in as a user Given I sign in as a user
And I own project "Delta" And I own project "Delta"
......
Feature: Project Multiselect Blob Feature: Project Source Multiselect Blob
Background: Background:
Given I sign in as a user Given I sign in as a user
And I own project "Shop" And I own project "Shop"
......
Feature: Project Search code Feature: Project Source Search Code
Background: Background:
Given I sign in as a user Given I sign in as a user
......
Feature: Project Team management Feature: Project Team Management
Background: Background:
Given I sign in as a user Given I sign in as a user
And I own project "Shop" And I own project "Shop"
......
@snippets @snippets
Feature: Discover Snippets Feature: Snippets Discover
Background: Background:
Given I sign in as a user Given I sign in as a user
And I have public "Personal snippet one" snippet And I have public "Personal snippet one" snippet
And I have private "Personal snippet private" snippet And I have private "Personal snippet private" snippet
And I have internal "Personal snippet internal" snippet
Scenario: I should see snippets Scenario: I should see snippets
Given I visit snippets page Given I visit snippets page
Then I should see "Personal snippet one" in snippets Then I should see "Personal snippet one" in snippets
And I should see "Personal snippet internal" in snippets
And I should not see "Personal snippet private" in snippets And I should not see "Personal snippet private" in snippets
@snippets @snippets
Feature: Snippets Feature Feature: Snippets
Background: Background:
Given I sign in as a user Given I sign in as a user
And I have public "Personal snippet one" snippet And I have public "Personal snippet one" snippet
......
@snippets @snippets
Feature: User Snippets Feature: Snippets User
Background: Background:
Given I sign in as a user Given I sign in as a user
And I have public "Personal snippet one" snippet And I have public "Personal snippet one" snippet
And I have private "Personal snippet private" snippet And I have private "Personal snippet private" snippet
And I have internal "Personal snippet internal" snippet
Scenario: I should see all my snippets Scenario: I should see all my snippets
Given I visit my snippets page Given I visit my snippets page
Then I should see "Personal snippet one" in snippets Then I should see "Personal snippet one" in snippets
And I should see "Personal snippet private" in snippets And I should see "Personal snippet private" in snippets
And I should see "Personal snippet internal" in snippets
Scenario: I can see only my private snippets Scenario: I can see only my private snippets
Given I visit my snippets page Given I visit my snippets page
And I click "Private" filter And I click "Private" filter
Then I should not see "Personal snippet one" in snippets Then I should not see "Personal snippet one" in snippets
And I should not see "Personal snippet internal" in snippets
And I should see "Personal snippet private" in snippets And I should see "Personal snippet private" in snippets
Scenario: I can see only my public snippets Scenario: I can see only my public snippets
...@@ -21,3 +24,11 @@ Feature: User Snippets ...@@ -21,3 +24,11 @@ Feature: User Snippets
And I click "Public" filter And I click "Public" filter
Then I should see "Personal snippet one" in snippets Then I should see "Personal snippet one" in snippets
And I should not see "Personal snippet private" in snippets And I should not see "Personal snippet private" in snippets
And I should not see "Personal snippet internal" in snippets
Scenario: I can see only my internal snippets
Given I visit my snippets page
And I click "Internal" filter
Then I should see "Personal snippet internal" in snippets
And I should not see "Personal snippet private" in snippets
And I should not see "Personal snippet one" in snippets
class Spinach::Features::DashboardWithArchivedProjects < Spinach::FeatureSteps class Spinach::Features::DashboardArchivedProjects < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedPaths include SharedPaths
include SharedProject include SharedProject
......
class Spinach::Features::Help < Spinach::FeatureSteps class Spinach::Features::DashboardHelp < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedPaths include SharedPaths
include SharedMarkdown include SharedMarkdown
......
class Spinach::Features::ExploreGroupsFeature < Spinach::FeatureSteps class Spinach::Features::ExploreGroups < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedPaths include SharedPaths
include SharedGroup include SharedGroup
......
class Spinach::Features::ExploreProjectsFeature < Spinach::FeatureSteps class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedPaths include SharedPaths
include SharedProject include SharedProject
......
class Spinach::Features::ProjectBrowseBranches < Spinach::FeatureSteps class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedProject include SharedProject
include SharedPaths include SharedPaths
......
class Spinach::Features::CommentsOnCommits < Spinach::FeatureSteps class Spinach::Features::ProjectCommitsComments < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedNote include SharedNote
include SharedPaths include SharedPaths
......
class Spinach::Features::ProjectBrowseCommits < Spinach::FeatureSteps class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedProject include SharedProject
include SharedPaths include SharedPaths
...@@ -8,7 +8,7 @@ class Spinach::Features::ProjectBrowseCommits < Spinach::FeatureSteps ...@@ -8,7 +8,7 @@ class Spinach::Features::ProjectBrowseCommits < Spinach::FeatureSteps
commit = @project.repository.commit commit = @project.repository.commit
page.should have_content(@project.name) page.should have_content(@project.name)
page.should have_content(commit.message[0..20]) page.should have_content(commit.message[0..20])
page.should have_content(commit.id.to_s[0..5]) page.should have_content(commit.short_id)
end end
step 'I click atom feed link' do step 'I click atom feed link' do
......
class Spinach::Features::CommentsOnCommitDiffs < Spinach::FeatureSteps class Spinach::Features::ProjectCommitsDiffComments < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedDiffNote include SharedDiffNote
include SharedPaths include SharedPaths
......
class Spinach::Features::ProjectBrowseTags < Spinach::FeatureSteps class Spinach::Features::ProjectCommitsTags < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedProject include SharedProject
include SharedPaths include SharedPaths
......
class Spinach::Features::ProjectBrowseCommitsUserLookup < Spinach::FeatureSteps class Spinach::Features::ProjectCommitsUserLookup < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedProject include SharedProject
include SharedPaths include SharedPaths
......
class Spinach::Features::CreateProject < Spinach::FeatureSteps class Spinach::Features::ProjectCreate < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedPaths include SharedPaths
......
class Spinach::Features::ForkProject < Spinach::FeatureSteps class Spinach::Features::ProjectFork < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedPaths include SharedPaths
include SharedProject include SharedProject
......
class Spinach::Features::ProjectFilterLabels < Spinach::FeatureSteps class Spinach::Features::ProjectIssuesFilterLabels < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedProject include SharedProject
include SharedPaths include SharedPaths
......
...@@ -153,6 +153,14 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps ...@@ -153,6 +153,14 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
author: project.users.first) author: project.users.first)
end end
step 'project "Shop" has "Tasks-open" open issue with task markdown' do
create_taskable(:issue, 'Tasks-open')
end
step 'project "Shop" has "Tasks-closed" closed issue with task markdown' do
create_taskable(:closed_issue, 'Tasks-closed')
end
step 'empty project "Empty Project"' do step 'empty project "Empty Project"' do
create :empty_project, name: 'Empty Project', namespace: @user.namespace create :empty_project, name: 'Empty Project', namespace: @user.namespace
end end
......
class Spinach::Features::ProjectLabels < Spinach::FeatureSteps class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedProject include SharedProject
include SharedPaths include SharedPaths
......
class Spinach::Features::ProjectMilestones < Spinach::FeatureSteps class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedProject include SharedProject
include SharedPaths include SharedPaths
......
...@@ -97,6 +97,10 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -97,6 +97,10 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
author: project.users.first) author: project.users.first)
end end
step 'project "Shop" has "MR-task-open" open MR with task markdown' do
create_taskable(:merge_request, 'MR-task-open')
end
step 'I switch to the diff tab' do step 'I switch to the diff tab' do
visit diffs_project_merge_request_path(project, merge_request) visit diffs_project_merge_request_path(project, merge_request)
end end
...@@ -107,7 +111,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -107,7 +111,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I click on the commit in the merge request' do step 'I click on the commit in the merge request' do
within '.mr-commits' do within '.mr-commits' do
click_link sample_commit.id[0..8] click_link Commit.truncate_sha(sample_commit.id)
end end
end end
...@@ -211,6 +215,18 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -211,6 +215,18 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end end
end end
step 'I should not see a comment like "Line is wrong here" in the second file' do
within '.files [id^=diff]:nth-child(2)' do
page.should_not have_visible_content "Line is wrong here"
end
end
step 'I should see a comment like "Line is wrong here" in the second file' do
within '.files [id^=diff]:nth-child(2) .note-text' do
page.should have_visible_content "Line is wrong here"
end
end
step 'I leave a comment like "Line is correct" on line 12 of the first file' do step 'I leave a comment like "Line is correct" on line 12 of the first file' do
init_diff_note_first_file init_diff_note_first_file
...@@ -228,13 +244,9 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -228,13 +244,9 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
init_diff_note_second_file init_diff_note_second_file
within(".js-discussion-note-form") do within(".js-discussion-note-form") do
fill_in "note_note", with: "Line is wrong" fill_in "note_note", with: "Line is wrong on here"
click_button "Add Comment" click_button "Add Comment"
end end
within ".files [id^=diff]:nth-child(2) .note-text" do
page.should have_content "Line is wrong"
end
end end
step 'I should still see a comment like "Line is correct" in the first file' do step 'I should still see a comment like "Line is correct" in the first file' do
......
class Spinach::Features::ProjectFeature < Spinach::FeatureSteps class Spinach::Features::Project < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedProject include SharedProject
include SharedPaths include SharedPaths
......
...@@ -13,6 +13,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps ...@@ -13,6 +13,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
page.should have_content 'Hipchat' page.should have_content 'Hipchat'
page.should have_content 'GitLab CI' page.should have_content 'GitLab CI'
page.should have_content 'Assembla' page.should have_content 'Assembla'
page.should have_content 'Pushover'
end end
step 'I click gitlab-ci service link' do step 'I click gitlab-ci service link' do
...@@ -107,16 +108,34 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps ...@@ -107,16 +108,34 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
step 'I fill Slack settings' do step 'I fill Slack settings' do
check 'Active' check 'Active'
fill_in 'Subdomain', with: 'gitlab' fill_in 'Webhook', with: 'https://gitlabhq.slack.com/services/hooks?token=cdIj4r4LfXUOySDUjp0tk3OI'
fill_in 'Room', with: '#gitlab'
fill_in 'Token', with: 'verySecret'
click_button 'Save' click_button 'Save'
end end
step 'I should see Slack service settings saved' do step 'I should see Slack service settings saved' do
find_field('Subdomain').value.should == 'gitlab' find_field('Webhook').value.should == 'https://gitlabhq.slack.com/services/hooks?token=cdIj4r4LfXUOySDUjp0tk3OI'
find_field('Room').value.should == '#gitlab' end
find_field('Token').value.should == 'verySecret'
step 'I click Pushover service link' do
click_link 'Pushover'
end
step 'I fill Pushover settings' do
check 'Active'
fill_in 'Api key', with: 'verySecret'
fill_in 'User key', with: 'verySecret'
fill_in 'Device', with: 'myDevice'
select 'High Priority', from: 'Priority'
select 'Bike', from: 'Sound'
click_button 'Save'
end
step 'I should see Pushover service settings saved' do
find_field('Api key').value.should == 'verySecret'
find_field('User key').value.should == 'verySecret'
find_field('Device').value.should == 'myDevice'
find_field('Priority').find('option[selected]').value.should == '1'
find_field('Sound').find('option[selected]').value.should == 'bike'
end end
step 'I click jira service link' do step 'I click jira service link' do
......
class Spinach::Features::ProjectBrowseFiles < Spinach::FeatureSteps class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedProject include SharedProject
include SharedPaths include SharedPaths
...@@ -69,8 +69,8 @@ class Spinach::Features::ProjectBrowseFiles < Spinach::FeatureSteps ...@@ -69,8 +69,8 @@ class Spinach::Features::ProjectBrowseFiles < Spinach::FeatureSteps
click_link 'Diff' click_link 'Diff'
end end
step 'I click on "Commit changes"' do step 'I click on "Commit Changes"' do
click_button 'Commit changes' click_button 'Commit Changes'
end end
step 'I click on "Remove"' do step 'I click on "Remove"' do
......
class Spinach::Features::ProjectBrowseGitRepo < Spinach::FeatureSteps class Spinach::Features::ProjectSourceGitBlame < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedProject include SharedProject
include SharedPaths include SharedPaths
......
# If you need to modify the existing seed repository for your tests, # If you need to modify the existing seed repository for your tests,
# it is recommended that you make the changes on the `markdown` branch of the seed project repository, # it is recommended that you make the changes on the `markdown` branch of the seed project repository,
# which should only be used by tests in this file. See `/spec/factories.rb#project` for more info. # which should only be used by tests in this file. See `/spec/factories.rb#project` for more info.
class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedPaths include SharedPaths
include SharedMarkdown include SharedMarkdown
......
class Spinach::Features::ProjectMultiselectBlob < Spinach::FeatureSteps class Spinach::Features::ProjectSourceMultiselectBlob < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedProject include SharedProject
include SharedPaths include SharedPaths
......
class Spinach::Features::ProjectSearchCode < Spinach::FeatureSteps class Spinach::Features::ProjectSourceSearchCode < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedProject include SharedProject
include SharedPaths include SharedPaths
......
...@@ -6,7 +6,52 @@ module SharedMarkdown ...@@ -6,7 +6,52 @@ module SharedMarkdown
find(:css, "#{parent} h#{level}##{id} > :last-child")[:href].should =~ /##{id}$/ find(:css, "#{parent} h#{level}##{id} > :last-child")[:href].should =~ /##{id}$/
end end
def create_taskable(type, title)
desc_text = <<EOT.gsub(/^ {6}/, '')
* [ ] Task 1
* [x] Task 2
EOT
case type
when :issue, :closed_issue
options = { project: project }
when :merge_request
options = { source_project: project, target_project: project }
end
create(
type,
options.merge(title: title,
author: project.users.first,
description: desc_text)
)
end
step 'Header "Description header" should have correct id and link' do step 'Header "Description header" should have correct id and link' do
header_should_have_correct_id_and_link(1, 'Description header', 'description-header') header_should_have_correct_id_and_link(1, 'Description header', 'description-header')
end end
step 'I should see task checkboxes in the description' do
expect(page).to have_selector(
'div.description li.task-list-item input[type="checkbox"]'
)
end
step 'I should see the task status for the Taskable' do
expect(find(:css, 'span.task-status').text).to eq(
'2 tasks (1 done, 1 unfinished)'
)
end
step 'Task checkboxes should be enabled' do
expect(page).to have_selector(
'div.description li.task-list-item input[type="checkbox"]:enabled'
)
end
step 'Task checkboxes should be disabled' do
expect(page).to have_selector(
'div.description li.task-list-item input[type="checkbox"]:disabled'
)
end
end end
...@@ -119,4 +119,18 @@ module SharedNote ...@@ -119,4 +119,18 @@ module SharedNote
page.should_not have_css("#comment-with-a-header") page.should_not have_css("#comment-with-a-header")
end end
end end
step 'I leave a comment with task markdown' do
within('.js-main-target-form') do
fill_in 'note[note]', with: '* [x] Task item'
click_button 'Add Comment'
sleep 0.05
end
end
step 'I should not see task checkboxes in the comment' do
expect(page).not_to have_selector(
'li.note div.timeline-content input[type="checkbox"]'
)
end
end end
...@@ -307,6 +307,16 @@ module SharedPaths ...@@ -307,6 +307,16 @@ module SharedPaths
visit project_issue_path(issue.project, issue) visit project_issue_path(issue.project, issue)
end end
step 'I visit issue page "Tasks-open"' do
issue = Issue.find_by(title: 'Tasks-open')
visit project_issue_path(issue.project, issue)
end
step 'I visit issue page "Tasks-closed"' do
issue = Issue.find_by(title: 'Tasks-closed')
visit project_issue_path(issue.project, issue)
end
step 'I visit project "Shop" labels page' do step 'I visit project "Shop" labels page' do
project = Project.find_by(name: 'Shop') project = Project.find_by(name: 'Shop')
visit project_labels_path(project) visit project_labels_path(project)
...@@ -337,6 +347,16 @@ module SharedPaths ...@@ -337,6 +347,16 @@ module SharedPaths
visit project_merge_request_path(mr.target_project, mr) visit project_merge_request_path(mr.target_project, mr)
end end
step 'I visit merge request page "MR-task-open"' do
mr = MergeRequest.find_by(title: 'MR-task-open')
visit project_merge_request_path(mr.target_project, mr)
end
step 'I visit merge request page "MR-task-closed"' do
mr = MergeRequest.find_by(title: 'MR-task-closed')
visit project_merge_request_path(mr.target_project, mr)
end
step 'I visit project "Shop" merge requests page' do step 'I visit project "Shop" merge requests page' do
visit project_merge_requests_path(project) visit project_merge_requests_path(project)
end end
......
...@@ -6,7 +6,7 @@ module SharedSnippet ...@@ -6,7 +6,7 @@ module SharedSnippet
title: "Personal snippet one", title: "Personal snippet one",
content: "Test content", content: "Test content",
file_name: "snippet.rb", file_name: "snippet.rb",
private: false, visibility_level: Snippet::PUBLIC,
author: current_user) author: current_user)
end end
...@@ -15,9 +15,19 @@ module SharedSnippet ...@@ -15,9 +15,19 @@ module SharedSnippet
title: "Personal snippet private", title: "Personal snippet private",
content: "Provate content", content: "Provate content",
file_name: "private_snippet.rb", file_name: "private_snippet.rb",
private: true, visibility_level: Snippet::PRIVATE,
author: current_user) author: current_user)
end end
step 'I have internal "Personal snippet internal" snippet' do
create(:personal_snippet,
title: "Personal snippet internal",
content: "Provate content",
file_name: "internal_snippet.rb",
visibility_level: Snippet::INTERNAL,
author: current_user)
end
step 'I have a public many lined snippet' do step 'I have a public many lined snippet' do
create(:personal_snippet, create(:personal_snippet,
title: 'Many lined snippet', title: 'Many lined snippet',
...@@ -38,7 +48,7 @@ module SharedSnippet ...@@ -38,7 +48,7 @@ module SharedSnippet
|line fourteen |line fourteen
END END
file_name: 'many_lined_snippet.rb', file_name: 'many_lined_snippet.rb',
private: true, visibility_level: Snippet::PUBLIC,
author: current_user) author: current_user)
end end
end end
class Spinach::Features::DiscoverSnippets < Spinach::FeatureSteps class Spinach::Features::SnippetsDiscover < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedPaths include SharedPaths
include SharedSnippet include SharedSnippet
...@@ -7,6 +7,10 @@ class Spinach::Features::DiscoverSnippets < Spinach::FeatureSteps ...@@ -7,6 +7,10 @@ class Spinach::Features::DiscoverSnippets < Spinach::FeatureSteps
page.should have_content "Personal snippet one" page.should have_content "Personal snippet one"
end end
step 'I should see "Personal snippet internal" in snippets' do
page.should have_content "Personal snippet internal"
end
step 'I should not see "Personal snippet private" in snippets' do step 'I should not see "Personal snippet private" in snippets' do
page.should_not have_content "Personal snippet private" page.should_not have_content "Personal snippet private"
end end
......
class Spinach::Features::SnippetsFeature < Spinach::FeatureSteps class Spinach::Features::Snippets < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedPaths include SharedPaths
include SharedProject include SharedProject
...@@ -46,7 +46,7 @@ class Spinach::Features::SnippetsFeature < Spinach::FeatureSteps ...@@ -46,7 +46,7 @@ class Spinach::Features::SnippetsFeature < Spinach::FeatureSteps
end end
step 'I uncheck "Private" checkbox' do step 'I uncheck "Private" checkbox' do
choose "Public" choose "Internal"
click_button "Save" click_button "Save"
end end
......
class Spinach::Features::UserSnippets < Spinach::FeatureSteps class Spinach::Features::SnippetsUser < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedPaths include SharedPaths
include SharedSnippet include SharedSnippet
...@@ -15,6 +15,10 @@ class Spinach::Features::UserSnippets < Spinach::FeatureSteps ...@@ -15,6 +15,10 @@ class Spinach::Features::UserSnippets < Spinach::FeatureSteps
page.should have_content "Personal snippet private" page.should have_content "Personal snippet private"
end end
step 'I should see "Personal snippet internal" in snippets' do
page.should have_content "Personal snippet internal"
end
step 'I should not see "Personal snippet one" in snippets' do step 'I should not see "Personal snippet one" in snippets' do
page.should_not have_content "Personal snippet one" page.should_not have_content "Personal snippet one"
end end
...@@ -23,9 +27,13 @@ class Spinach::Features::UserSnippets < Spinach::FeatureSteps ...@@ -23,9 +27,13 @@ class Spinach::Features::UserSnippets < Spinach::FeatureSteps
page.should_not have_content "Personal snippet private" page.should_not have_content "Personal snippet private"
end end
step 'I click "Public" filter' do step 'I should not see "Personal snippet internal" in snippets' do
page.should_not have_content "Personal snippet internal"
end
step 'I click "Internal" filter' do
within('.nav-stacked') do within('.nav-stacked') do
click_link "Public" click_link "Internal"
end end
end end
...@@ -35,6 +43,12 @@ class Spinach::Features::UserSnippets < Spinach::FeatureSteps ...@@ -35,6 +43,12 @@ class Spinach::Features::UserSnippets < Spinach::FeatureSteps
end end
end end
step 'I click "Public" filter' do
within('.nav-stacked') do
click_link "Public"
end
end
def snippet def snippet
@snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one") @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one")
end end
......
...@@ -14,13 +14,20 @@ module API ...@@ -14,13 +14,20 @@ module API
# #
post "/allowed" do post "/allowed" do
status 200 status 200
project_path = params[:project]
# Check for *.wiki repositories. # Check for *.wiki repositories.
# Strip out the .wiki from the pathname before finding the # Strip out the .wiki from the pathname before finding the
# project. This applies the correct project permissions to # project. This applies the correct project permissions to
# the wiki repository as well. # the wiki repository as well.
project_path = params[:project] access =
project_path.gsub!(/\.wiki/,'') if project_path =~ /\.wiki/ if project_path =~ /\.wiki\Z/
project_path.sub!(/\.wiki\Z/, '')
Gitlab::GitAccessWiki.new
else
Gitlab::GitAccess.new
end
project = Project.find_with_namespace(project_path) project = Project.find_with_namespace(project_path)
return false unless project return false unless project
...@@ -32,7 +39,7 @@ module API ...@@ -32,7 +39,7 @@ module API
return false unless actor return false unless actor
Gitlab::GitAccess.new.allowed?( access.allowed?(
actor, actor,
params[:action], params[:action],
project, project,
......
...@@ -16,8 +16,8 @@ module API ...@@ -16,8 +16,8 @@ module API
# Example Request: # Example Request:
# GET /ldap/groups # GET /ldap/groups
get 'groups' do get 'groups' do
provider = Gitlab::LDAP::Config.servers.first.provider_name provider = Gitlab::LDAP::Config.servers.first['provider_name']
@groups = get_group_list(provider, params[:search]) @groups = Gitlab::LDAP::Adapter.new(provider).groups("#{params[:search]}*", 20)
present @groups, with: Entities::LdapGroup present @groups, with: Entities::LdapGroup
end end
...@@ -28,6 +28,9 @@ module API ...@@ -28,6 +28,9 @@ module API
# GET /ldap/ldapmain/groups # GET /ldap/ldapmain/groups
get ':provider/groups' do get ':provider/groups' do
@groups = get_group_list(params[:provider], params[:search]) @groups = get_group_list(params[:provider], params[:search])
# NOTE: this should be deprecated in favour of /ldap/PROVIDER_NAME/groups
# for now we just select the first LDAP server
present @groups, with: Entities::LdapGroup present @groups, with: Entities::LdapGroup
end end
end end
......
...@@ -9,18 +9,10 @@ module Gitlab ...@@ -9,18 +9,10 @@ module Gitlab
# Second chance - try LDAP authentication # Second chance - try LDAP authentication
return nil unless Gitlab::LDAP::Config.enabled? return nil unless Gitlab::LDAP::Config.enabled?
Gitlab::LDAP::User.authenticate(login, password) Gitlab::LDAP::Authentication.login(login, password)
else else
user if user.valid_password?(password) user if user.valid_password?(password)
end end
end end
def log
Gitlab::AppLogger
end
def ldap_conf
@ldap_conf ||= Gitlab.config.ldap
end
end end
end end
...@@ -6,7 +6,7 @@ module Gitlab ...@@ -6,7 +6,7 @@ module Gitlab
md = ISSUE_CLOSING_REGEX.match(message) md = ISSUE_CLOSING_REGEX.match(message)
if md if md
extractor = Gitlab::ReferenceExtractor.new extractor = Gitlab::ReferenceExtractor.new
extractor.analyze(md[0]) extractor.analyze(md[0], project)
extractor.issues_for(project) extractor.issues_for(project)
else else
[] []
......
...@@ -49,6 +49,17 @@ module Gitlab ...@@ -49,6 +49,17 @@ module Gitlab
# 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.each do |change|
unless change_allowed?(user, project, change)
# If user does not have access to make at least one change - cancel all push
return false
end
end
# If user has access to make all changes
true
end
def change_allowed?(user, project, change)
oldrev, newrev, ref = change.split(' ') oldrev, newrev, ref = change.split(' ')
action = if project.protected_branch?(branch_name(ref)) action = if project.protected_branch?(branch_name(ref))
...@@ -67,15 +78,9 @@ module Gitlab ...@@ -67,15 +78,9 @@ module Gitlab
else else
:push_code :push_code
end end
unless user.can?(action, project) &&
pass_git_hooks?(user, project, ref, oldrev, newrev)
# If user does not have access to make at least one change - cancel all push
return false
end
end
# If user has access to make all changes user.can?(action, project) &&
true pass_git_hooks?(user, project, ref, oldrev, newrev)
end end
def forced_push?(project, oldrev, newrev) def forced_push?(project, oldrev, newrev)
......
module Gitlab
class GitAccessWiki < GitAccess
def change_allowed?(user, project, change)
user.can?(:write_wiki, project)
end
end
end
...@@ -49,6 +49,10 @@ module Gitlab ...@@ -49,6 +49,10 @@ module Gitlab
@adapter ||= Gitlab::LDAP::Adapter.new(provider) @adapter ||= Gitlab::LDAP::Adapter.new(provider)
end end
def ldap_config
Gitlab::LDAP::Config.new(provider)
end
def ldap_user def ldap_user
@ldap_user ||= Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter) @ldap_user ||= Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter)
end end
...@@ -146,10 +150,6 @@ module Gitlab ...@@ -146,10 +150,6 @@ module Gitlab
end.map(&:cn) end.map(&:cn)
end end
def ldap_config
Gitlab::LDAP::Config.new(provider)
end
def sync_ssh_keys? def sync_ssh_keys?
ldap_config.sync_ssh_keys? ldap_config.sync_ssh_keys?
end end
......
# This calls helps to authenticate to LDAP by providing username and password
#
# Since multiple LDAP servers are supported, it will loop through all of them
# until a valid bind is found
#
module Gitlab
module LDAP
class Authentication
def self.login(login, password)
return unless Gitlab::LDAP::Config.enabled?
return unless login.present? && password.present?
auth = nil
# loop through providers until valid bind
providers.find do |provider|
auth = new(provider)
auth.login(login, password) # true will exit the loop
end
# If (login, password) was invalid for all providers, the value of auth is now the last
# Gitlab::LDAP::Authentication instance we tried.
auth.user
end
def self.providers
Gitlab::LDAP::Config.providers
end
attr_accessor :provider, :ldap_user
def initialize(provider)
@provider = provider
end
def login(login, password)
@ldap_user = adapter.bind_as(
filter: user_filter(login),
size: 1,
password: password
)
end
def adapter
OmniAuth::LDAP::Adaptor.new(config.options)
end
def config
Gitlab::LDAP::Config.new(provider)
end
def user_filter(login)
filter = Net::LDAP::Filter.eq(config.uid, login)
# Apply LDAP user filter if present
if config.user_filter.present?
filter = Net::LDAP::Filter.join(
filter,
Net::LDAP::Filter.construct(config.user_filter)
)
end
filter
end
def user
return nil unless ldap_user
Gitlab::LDAP::User.find_by_uid_and_provider(ldap_user.dn, provider)
end
end
end
end
\ No newline at end of file
...@@ -9,11 +9,11 @@ module Gitlab ...@@ -9,11 +9,11 @@ module Gitlab
end end
def self.servers def self.servers
Gitlab.config.ldap.servers Gitlab.config.ldap.servers.values
end end
def self.providers def self.providers
servers.map &:provider_name servers.map {|server| server['provider_name'] }
end end
def initialize(provider) def initialize(provider)
...@@ -79,7 +79,7 @@ module Gitlab ...@@ -79,7 +79,7 @@ module Gitlab
end end
def config_for(provider) def config_for(provider)
base_config.servers.find { |server| server.provider_name == provider } base_config.servers.values.find { |server| server['provider_name'] == provider }
end end
def encryption def encryption
......
...@@ -36,7 +36,7 @@ module Gitlab ...@@ -36,7 +36,7 @@ module Gitlab
member_uids.include?(user.uid) member_uids.include?(user.uid)
elsif member_dns.include?(user.dn) elsif member_dns.include?(user.dn)
true true
else elsif Gitlab.config.ldap.active_directory
adapter.dn_matches_filter?(user.dn, active_directory_recursive_memberof_filter) adapter.dn_matches_filter?(user.dn, active_directory_recursive_memberof_filter)
end end
end end
......
...@@ -9,7 +9,7 @@ module Gitlab ...@@ -9,7 +9,7 @@ module Gitlab
attr_accessor :entry, :provider attr_accessor :entry, :provider
def self.find_by_uid(uid, adapter) def self.find_by_uid(uid, adapter)
adapter.user(Gitlab.config.ldap.uid, uid) adapter.user(adapter.config.uid, uid)
end end
def self.find_by_dn(dn, adapter) def self.find_by_dn(dn, adapter)
......
...@@ -10,52 +10,12 @@ module Gitlab ...@@ -10,52 +10,12 @@ module Gitlab
module LDAP module LDAP
class User < Gitlab::OAuth::User class User < Gitlab::OAuth::User
class << self class << self
# TODO: Look through LDAP servers until valid credentials are found? def find_by_uid_and_provider(uid, provider)
def authenticate(login, password)
# Check user against LDAP backend if user is not authenticated
# Only check with valid login and password to prevent anonymous bind results
return nil unless ldap_conf.enabled? && login.present? && password.present?
ldap_user = adapter.bind_as(
filter: user_filter(login),
size: 1,
password: password
)
find_by_uid(ldap_user.dn) if ldap_user
end
def adapter
@adapter ||= OmniAuth::LDAP::Adaptor.new(ldap_conf.options)
end
def user_filter(login)
filter = Net::LDAP::Filter.eq(adapter.uid, login)
# Apply LDAP user filter if present
if ldap_conf.user_filter.present?
user_filter = Net::LDAP::Filter.construct(ldap_conf.user_filter)
filter = Net::LDAP::Filter.join(filter, user_filter)
end
filter
end
def ldap_conf
Gitlab::LDAP::Config.new(provider)
end
def find_by_uid(uid)
# LDAP distinguished name is case-insensitive # LDAP distinguished name is case-insensitive
model. ::User.
where(provider: [provider, :ldap]). where(provider: [provider, :ldap]).
where('lower(extern_uid) = ?', uid.downcase).last where('lower(extern_uid) = ?', uid.downcase).last
end end
def provider
# Note: for backwards compatibility we just get the first provider
# Later on, we should loop through all servers until a successful
# authentication
Gitlab::LDAP::Config.servers.first.provider_name
end
end end
def initialize(auth_hash) def initialize(auth_hash)
...@@ -69,10 +29,8 @@ module Gitlab ...@@ -69,10 +29,8 @@ module Gitlab
end end
def find_by_uid_and_provider def find_by_uid_and_provider
# LDAP distinguished name is case-insensitive self.class.find_by_uid_and_provider(
model. auth_hash.uid.downcase, auth_hash.provider)
where(provider: [auth_hash.provider, :ldap]).
where('lower(extern_uid) = ?', auth_hash.uid.downcase).last
end end
def find_by_email def find_by_email
......
...@@ -33,6 +33,11 @@ module Gitlab ...@@ -33,6 +33,11 @@ module Gitlab
attr_reader :html_options attr_reader :html_options
def gfm_with_tasks(text, project = @project, html_options = {})
text = gfm(text, project, html_options)
parse_tasks(text)
end
# Public: Parse the provided text with GitLab-Flavored Markdown # Public: Parse the provided text with GitLab-Flavored Markdown
# #
# text - the source text # text - the source text
...@@ -65,14 +70,22 @@ module Gitlab ...@@ -65,14 +70,22 @@ module Gitlab
insert_piece($1) insert_piece($1)
end end
# Context passed to the markdoqwn pipeline # Used markdown pipelines in GitLab:
# GitlabEmojiFilter - performs emoji replacement.
#
# see https://gitlab.com/gitlab-org/html-pipeline-gitlab for more filters
filters = [
HTML::Pipeline::Gitlab::GitlabEmojiFilter
]
markdown_context = { markdown_context = {
asset_root: File.join(root_url, asset_root: Gitlab.config.gitlab.url,
Gitlab::Application.config.assets.prefix) asset_host: Gitlab::Application.config.asset_host
} }
result = HTML::Pipeline::Gitlab::MarkdownPipeline.call(text, markdown_pipeline = HTML::Pipeline::Gitlab.new(filters).pipeline
markdown_context)
result = markdown_pipeline.call(text, markdown_context)
text = result[:output].to_html(save_with: 0) text = result[:output].to_html(save_with: 0)
allowed_attributes = ActionView::Base.sanitized_allowed_attributes allowed_attributes = ActionView::Base.sanitized_allowed_attributes
...@@ -108,15 +121,18 @@ module Gitlab ...@@ -108,15 +121,18 @@ module Gitlab
text text
end end
NAME_STR = '[a-zA-Z][a-zA-Z0-9_\-\.]*'
PROJ_STR = "(?<project>#{NAME_STR}/#{NAME_STR})"
REFERENCE_PATTERN = %r{ REFERENCE_PATTERN = %r{
(?<prefix>\W)? # Prefix (?<prefix>\W)? # Prefix
( # Reference ( # Reference
@(?<user>[a-zA-Z][a-zA-Z0-9_\-\.]*) # User name @(?<user>#{NAME_STR}) # User name
|(?<issue>([A-Z\-]+-)\d+) # JIRA Issue ID |(?<issue>([A-Z\-]+-)\d+) # JIRA Issue ID
|\#(?<issue>([a-zA-Z\-]+-)?\d+) # Issue ID |#{PROJ_STR}?\#(?<issue>([a-zA-Z\-]+-)?\d+) # Issue ID
|!(?<merge_request>\d+) # MR ID |#{PROJ_STR}?!(?<merge_request>\d+) # MR ID
|\$(?<snippet>\d+) # Snippet ID |\$(?<snippet>\d+) # Snippet ID
|(?<commit>[\h]{6,40}) # Commit ID |(#{PROJ_STR}@)?(?<commit>[\h]{6,40}) # Commit ID
|(?<skip>gfm-extraction-[\h]{6,40}) # Skip gfm extractions. Otherwise will be parsed as commit |(?<skip>gfm-extraction-[\h]{6,40}) # Skip gfm extractions. Otherwise will be parsed as commit
) )
(?<suffix>\W)? # Suffix (?<suffix>\W)? # Suffix
...@@ -127,25 +143,46 @@ module Gitlab ...@@ -127,25 +143,46 @@ module Gitlab
def parse_references(text, project = @project) def parse_references(text, project = @project)
# parse reference links # parse reference links
text.gsub!(REFERENCE_PATTERN) do |match| text.gsub!(REFERENCE_PATTERN) do |match|
prefix = $~[:prefix]
suffix = $~[:suffix]
type = TYPES.select{|t| !$~[t].nil?}.first type = TYPES.select{|t| !$~[t].nil?}.first
if type actual_project = project
identifier = $~[type] project_prefix = nil
project_path = $LAST_MATCH_INFO[:project]
if project_path
actual_project = ::Project.find_with_namespace(project_path)
project_prefix = project_path
end
# Avoid HTML entities parse_result($LAST_MATCH_INFO, type,
if prefix && suffix && prefix[0] == '&' && suffix[-1] == ';' actual_project, project_prefix) || match
match
elsif ref_link = reference_link(type, identifier, project)
"#{prefix}#{ref_link}#{suffix}"
else
match
end end
end
# Called from #parse_references. Attempts to build a gitlab reference
# link. Returns nil if +type+ is nil, if the match string is an HTML
# entity, if the reference is invalid, or if the matched text includes an
# invalid project path.
def parse_result(match_info, type, project, project_prefix)
prefix = match_info[:prefix]
suffix = match_info[:suffix]
return nil if html_entity?(prefix, suffix) || type.nil?
return nil if project.nil? && !project_prefix.nil?
identifier = match_info[type]
ref_link = reference_link(type, identifier, project, project_prefix)
if ref_link
"#{prefix}#{ref_link}#{suffix}"
else else
match nil
end end
end end
# Return true if the +prefix+ and +suffix+ indicate that the matched string
# is an HTML entity like &amp;
def html_entity?(prefix, suffix)
prefix && suffix && prefix[0] == '&' && suffix[-1] == ';'
end end
# Private: Dispatches to a dedicated processing method based on reference # Private: Dispatches to a dedicated processing method based on reference
...@@ -154,11 +191,11 @@ module Gitlab ...@@ -154,11 +191,11 @@ module Gitlab
# identifier - Object identifier (Issue ID, SHA hash, etc.) # identifier - Object identifier (Issue ID, SHA hash, etc.)
# #
# Returns string rendered by the processing method # Returns string rendered by the processing method
def reference_link(type, identifier, project = @project) def reference_link(type, identifier, project = @project, prefix_text = nil)
send("reference_#{type}", identifier, project) send("reference_#{type}", identifier, project, prefix_text)
end end
def reference_user(identifier, project = @project) def reference_user(identifier, project = @project, _ = nil)
options = html_options.merge( options = html_options.merge(
class: "gfm gfm-team_member #{html_options[:class]}" class: "gfm gfm-team_member #{html_options[:class]}"
) )
...@@ -170,39 +207,41 @@ module Gitlab ...@@ -170,39 +207,41 @@ module Gitlab
end end
end end
def reference_issue(identifier, project = @project) def reference_issue(identifier, project = @project, prefix_text = nil)
if project.used_default_issues_tracker? || !external_issues_tracker_enabled? if project.used_default_issues_tracker? || !external_issues_tracker_enabled?
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) title = title_for_issue(identifier, project)
options = html_options.merge( options = html_options.merge(
title: "Issue: #{title}", title: "Issue: #{title}",
class: "gfm gfm-issue #{html_options[:class]}" class: "gfm gfm-issue #{html_options[:class]}"
) )
link_to("##{identifier}", url, options) link_to("#{prefix_text}##{identifier}", url, options)
end end
else else
config = Gitlab.config config = Gitlab.config
external_issue_tracker = config.issues_tracker[project.issues_tracker] external_issue_tracker = config.issues_tracker[project.issues_tracker]
if external_issue_tracker.present? if external_issue_tracker.present?
reference_external_issue(identifier, external_issue_tracker, project) reference_external_issue(identifier, external_issue_tracker, project,
prefix_text)
end end
end end
end end
def reference_merge_request(identifier, project = @project) def reference_merge_request(identifier, project = @project,
prefix_text = nil)
if merge_request = project.merge_requests.find_by(iid: identifier) if merge_request = project.merge_requests.find_by(iid: identifier)
options = html_options.merge( options = html_options.merge(
title: "Merge Request: #{merge_request.title}", title: "Merge Request: #{merge_request.title}",
class: "gfm gfm-merge_request #{html_options[:class]}" class: "gfm gfm-merge_request #{html_options[:class]}"
) )
url = project_merge_request_url(project, merge_request) url = project_merge_request_url(project, merge_request)
link_to("!#{identifier}", url, options) link_to("#{prefix_text}!#{identifier}", url, options)
end end
end end
def reference_snippet(identifier, project = @project) def reference_snippet(identifier, project = @project, _ = nil)
if snippet = project.snippets.find_by(id: identifier) if snippet = project.snippets.find_by(id: identifier)
options = html_options.merge( options = html_options.merge(
title: "Snippet: #{snippet.title}", title: "Snippet: #{snippet.title}",
...@@ -213,17 +252,23 @@ module Gitlab ...@@ -213,17 +252,23 @@ module Gitlab
end end
end end
def reference_commit(identifier, project = @project) def reference_commit(identifier, project = @project, prefix_text = nil)
if project.valid_repo? && commit = project.repository.commit(identifier) if project.valid_repo? && commit = project.repository.commit(identifier)
options = html_options.merge( options = html_options.merge(
title: commit.link_title, title: commit.link_title,
class: "gfm gfm-commit #{html_options[:class]}" class: "gfm gfm-commit #{html_options[:class]}"
) )
link_to(identifier, project_commit_url(project, commit), options) prefix_text = "#{prefix_text}@" if prefix_text
link_to(
"#{prefix_text}#{identifier}",
project_commit_url(project, commit),
options
)
end end
end end
def reference_external_issue(identifier, issue_tracker, project = @project) def reference_external_issue(identifier, issue_tracker, project = @project,
prefix_text = nil)
url = url_for_issue(identifier, project) url = url_for_issue(identifier, project)
title = issue_tracker['title'] title = issue_tracker['title']
...@@ -231,7 +276,26 @@ module Gitlab ...@@ -231,7 +276,26 @@ module Gitlab
title: "Issue in #{title}", title: "Issue in #{title}",
class: "gfm gfm-issue #{html_options[:class]}" class: "gfm gfm-issue #{html_options[:class]}"
) )
link_to("##{identifier}", url, options) link_to("#{prefix_text}##{identifier}", url, options)
end
# Turn list items that start with "[ ]" into HTML checkbox inputs.
def parse_tasks(text)
li_tag = '<li class="task-list-item">'
unchecked_box = '<input type="checkbox" value="on" disabled />'
checked_box = unchecked_box.sub(/\/>$/, 'checked="checked" />')
# Regexp captures don't seem to work when +text+ is an
# ActiveSupport::SafeBuffer, hence the `String.new`
String.new(text).gsub(Taskable::TASK_PATTERN_HTML) do
checked = $LAST_MATCH_INFO[:checked].downcase == 'x'
if checked
"#{li_tag}#{checked_box}"
else
"#{li_tag}#{unchecked_box}"
end
end
end end
end end
end end
...@@ -21,5 +21,9 @@ module Gitlab ...@@ -21,5 +21,9 @@ module Gitlab
def gitlab_markdown?(filename) def gitlab_markdown?(filename)
filename.downcase.end_with?(*%w(.mdown .md .markdown)) filename.downcase.end_with?(*%w(.mdown .md .markdown))
end end
def previewable?(filename)
gitlab_markdown?(filename) || markup?(filename)
end
end end
end end
...@@ -70,10 +70,6 @@ module Gitlab ...@@ -70,10 +70,6 @@ module Gitlab
Gitlab::AppLogger Gitlab::AppLogger
end end
def raise_error(message)
raise OmniAuth::Error, "(OAuth) " + message
end
def needs_blocking? def needs_blocking?
Gitlab.config.omniauth['block_auto_created_users'] Gitlab.config.omniauth['block_auto_created_users']
end end
......
...@@ -9,57 +9,69 @@ module Gitlab ...@@ -9,57 +9,69 @@ module Gitlab
@users, @issues, @merge_requests, @snippets, @commits = [], [], [], [], [] @users, @issues, @merge_requests, @snippets, @commits = [], [], [], [], []
end end
def analyze(string) def analyze(string, project)
parse_references(string.dup) parse_references(string.dup, project)
end end
# Given a valid project, resolve the extracted identifiers of the requested type to # Given a valid project, resolve the extracted identifiers of the requested type to
# model objects. # model objects.
def users_for(project) def users_for(project)
users.map do |identifier| users.map do |entry|
project.users.where(username: identifier).first project.users.where(username: entry[:id]).first
end.reject(&:nil?) end.reject(&:nil?)
end end
def issues_for(project) def issues_for(project = nil)
if project.jira_tracker? if project && project.jira_tracker?
issues.uniq.map do |jira_identifier| issues.uniq.map do |jira_identifier|
JiraIssue.new(jira_identifier) JiraIssue.new(jira_identifier[:id])
end end
else else
issues.map do |identifier| issues.map do |entry|
project.issues.where(iid: identifier).first if should_lookup?(project, entry[:project])
entry[:project].issues.where(iid: entry[:id]).first
end
end.reject(&:nil?) end.reject(&:nil?)
end end
end end
def merge_requests_for(project) def merge_requests_for(project = nil)
merge_requests.map do |identifier| merge_requests.map do |entry|
project.merge_requests.where(iid: identifier).first if should_lookup?(project, entry[:project])
entry[:project].merge_requests.where(iid: entry[:id]).first
end
end.reject(&:nil?) end.reject(&:nil?)
end end
def snippets_for(project) def snippets_for(project)
snippets.map do |identifier| snippets.map do |entry|
project.snippets.where(id: identifier).first project.snippets.where(id: entry[:id]).first
end.reject(&:nil?) end.reject(&:nil?)
end end
def commits_for(project) def commits_for(project = nil)
repo = project.repository commits.map do |entry|
return [] if repo.nil? repo = entry[:project].repository if entry[:project]
if should_lookup?(project, entry[:project])
commits.map do |identifier| repo.commit(entry[:id]) if repo
repo.commit(identifier) end
end.reject(&:nil?) end.reject(&:nil?)
end end
private private
def reference_link(type, identifier, project) def reference_link(type, identifier, project, _)
# Append identifier to the appropriate collection. # Append identifier to the appropriate collection.
send("#{type}s") << identifier send("#{type}s") << { project: project, id: identifier }
end
def should_lookup?(project, entry_project)
if entry_project.nil?
false
else
project.nil? || project.id == entry_project.id
end
end end
end end
end end
...@@ -10,6 +10,17 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML ...@@ -10,6 +10,17 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
super options super options
end end
# If project has issue number 39, apostrophe will be linked in
# regular text to the issue as Redcarpet will convert apostrophe to
# #39;
# We replace apostrophe with right single quote before Redcarpet
# does the processing and put the apostrophe back in postprocessing.
# This only influences regular text, code blocks are untouched.
def normal_text(text)
return text unless text.present?
text.gsub("'", "&rsquo;")
end
def block_code(code, language) def block_code(code, language)
# New lines are placed to fix an rendering issue # New lines are placed to fix an rendering issue
# with code wrapped inside <h1> tag for next case: # with code wrapped inside <h1> tag for next case:
...@@ -44,9 +55,14 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML ...@@ -44,9 +55,14 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
end end
def postprocess(full_document) def postprocess(full_document)
full_document.gsub!("&rsquo;", "'")
unless @template.instance_variable_get("@project_wiki") || @project.nil? unless @template.instance_variable_get("@project_wiki") || @project.nil?
full_document = h.create_relative_links(full_document) full_document = h.create_relative_links(full_document)
end end
if @options[:parse_tasks]
h.gfm_with_tasks(full_document)
else
h.gfm(full_document) h.gfm(full_document)
end end
end
end end
...@@ -65,7 +65,8 @@ server { ...@@ -65,7 +65,8 @@ server {
ssl_certificate /etc/nginx/ssl/gitlab.crt; ssl_certificate /etc/nginx/ssl/gitlab.crt;
ssl_certificate_key /etc/nginx/ssl/gitlab.key; ssl_certificate_key /etc/nginx/ssl/gitlab.key;
ssl_ciphers 'AES256+EECDH:AES256+EDH'; # GitLab needs backwards compatible ciphers to retain compatibility with Java IDEs
ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4';
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_session_cache builtin:1000 shared:SSL:10m; ssl_session_cache builtin:1000 shared:SSL:10m;
......
...@@ -27,7 +27,10 @@ namespace :gitlab do ...@@ -27,7 +27,10 @@ namespace :gitlab do
group_name = nil if group_name == '.' group_name = nil if group_name == '.'
# Skip if group or user # Skip if group or user
next if namespaces.include?(name) if namespaces.include?(name)
puts "Skipping #{project.name} due to namespace conflict with group or user".yellow
next
end
puts "Processing #{repo_path}".yellow puts "Processing #{repo_path}".yellow
......
...@@ -10,8 +10,12 @@ describe "Projects", feature: true do ...@@ -10,8 +10,12 @@ describe "Projects", feature: true do
visit edit_project_path(@project) visit edit_project_path(@project)
end end
it "should be correct path" do it "should be correct path", js: true do
expect { click_link "Remove project" }.to change {Project.count}.by(-1) expect {
click_link "Remove project"
fill_in 'confirm_name_input', with: @project.path
click_button 'Confirm'
}.to change {Project.count}.by(-1)
end end
end end
end end
require 'spec_helper'
describe SnippetsFinder do
let(:user) { create :user }
let(:user1) { create :user }
let(:group) { create :group }
let(:project1) { create(:empty_project, :public, group: group) }
let(:project2) { create(:empty_project, :private, group: group) }
context ':all filter' do
before do
@snippet1 = create(:personal_snippet, visibility_level: Snippet::PRIVATE)
@snippet2 = create(:personal_snippet, visibility_level: Snippet::INTERNAL)
@snippet3 = create(:personal_snippet, visibility_level: Snippet::PUBLIC)
end
it "returns all private and internal snippets" do
snippets = SnippetsFinder.new.execute(user, filter: :all)
snippets.should include(@snippet2, @snippet3)
snippets.should_not include(@snippet1)
end
it "returns all public snippets" do
snippets = SnippetsFinder.new.execute(nil, filter: :all)
snippets.should include(@snippet3)
snippets.should_not include(@snippet1, @snippet2)
end
end
context ':by_user filter' do
before do
@snippet1 = create(:personal_snippet, visibility_level: Snippet::PRIVATE, author: user)
@snippet2 = create(:personal_snippet, visibility_level: Snippet::INTERNAL, author: user)
@snippet3 = create(:personal_snippet, visibility_level: Snippet::PUBLIC, author: user)
end
it "returns all public and internal snippets" do
snippets = SnippetsFinder.new.execute(user1, filter: :by_user, user: user)
snippets.should include(@snippet2, @snippet3)
snippets.should_not include(@snippet1)
end
it "returns internal snippets" do
snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_internal")
snippets.should include(@snippet2)
snippets.should_not include(@snippet1, @snippet3)
end
it "returns private snippets" do
snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_private")
snippets.should include(@snippet1)
snippets.should_not include(@snippet2, @snippet3)
end
it "returns public snippets" do
snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_public")
snippets.should include(@snippet3)
snippets.should_not include(@snippet1, @snippet2)
end
it "returns all snippets" do
snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user)
snippets.should include(@snippet1, @snippet2, @snippet3)
end
end
context 'by_project filter' do
before do
@snippet1 = create(:project_snippet, visibility_level: Snippet::PRIVATE, project: project1)
@snippet2 = create(:project_snippet, visibility_level: Snippet::INTERNAL, project: project1)
@snippet3 = create(:project_snippet, visibility_level: Snippet::PUBLIC, project: project1)
end
it "returns public snippets for unauthorized user" do
snippets = SnippetsFinder.new.execute(nil, filter: :by_project, project: project1)
snippets.should include(@snippet3)
snippets.should_not include(@snippet1, @snippet2)
end
it "returns public and internal snippets for none project members" do
snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1)
snippets.should include(@snippet2, @snippet3)
snippets.should_not include(@snippet1)
end
it "returns all snippets for project members" do
project1.team << [user, :developer]
snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1)
snippets.should include(@snippet1, @snippet2, @snippet3)
end
end
end
require 'spec_helper'
describe EventsHelper do
include ApplicationHelper
include GitlabMarkdownHelper
it 'should display one line of plain text without alteration' do
input = 'A short, plain note'
expect(event_note(input)).to match(input)
expect(event_note(input)).not_to match(/\.\.\.\z/)
end
it 'should display inline code' do
input = 'A note with `inline code`'
expected = 'A note with <code>inline code</code>'
expect(event_note(input)).to match(expected)
end
it 'should truncate a note with multiple paragraphs' do
input = "Paragraph 1\n\nParagraph 2"
expected = 'Paragraph 1...'
expect(event_note(input)).to match(expected)
end
it 'should display the first line of a code block' do
input = "```\nCode block\nwith two lines\n```"
expected = '<pre><code class="">Code block...</code></pre>'
expect(event_note(input)).to match(expected)
end
it 'should truncate a single long line of text' do
text = 'The quick brown fox jumped over the lazy dog twice' # 50 chars
input = "#{text}#{text}#{text}#{text}" # 200 chars
expected = "#{text}#{text}".sub(/.{3}/, '...')
expect(event_note(input)).to match(expected)
end
it 'should preserve a link href when link text is truncated' do
text = 'The quick brown fox jumped over the lazy dog' # 44 chars
input = "#{text}#{text}#{text} " # 133 chars
link_url = 'http://example.com/foo/bar/baz' # 30 chars
input << link_url
expected_link_text = 'http://example...</a>'
expect(event_note(input)).to match(link_url)
expect(event_note(input)).to match(expected_link_text)
end
end
...@@ -60,7 +60,7 @@ describe GitlabMarkdownHelper do ...@@ -60,7 +60,7 @@ describe GitlabMarkdownHelper do
end end
it "should link using a short id" do it "should link using a short id" do
actual = "Backported from #{commit.short_id(6)}" actual = "Backported from #{commit.short_id}"
gfm(actual).should match(expected) gfm(actual).should match(expected)
end end
...@@ -181,6 +181,76 @@ describe GitlabMarkdownHelper do ...@@ -181,6 +181,76 @@ describe GitlabMarkdownHelper do
end end
end end
# Shared examples for referencing an object in a different project
#
# Expects the following attributes to be available in the example group:
#
# - object - The object itself
# - reference - The object reference string (e.g., #1234, $1234, !1234)
# - other_project - The project that owns the target object
#
# Currently limited to Snippets, Issues and MergeRequests
shared_examples 'cross-project referenced object' do
let(:project_path) { @other_project.path_with_namespace }
let(:full_reference) { "#{project_path}#{reference}" }
let(:actual) { "Reference to #{full_reference}" }
let(:expected) do
if object.is_a?(Commit)
project_commit_path(@other_project, object)
else
polymorphic_path([@other_project, object])
end
end
it 'should link using a valid id' do
gfm(actual).should match(
/#{expected}.*#{Regexp.escape(full_reference)}/
)
end
it 'should link with adjacent text' do
# Wrap the reference in parenthesis
gfm(actual.gsub(full_reference, "(#{full_reference})")).should(
match(expected)
)
# Append some text to the end of the reference
gfm(actual.gsub(full_reference, "#{full_reference}, right?")).should(
match(expected)
)
end
it 'should keep whitespace intact' do
actual = "Referenced #{full_reference} already."
expected = /Referenced <a.+>[^\s]+<\/a> already/
gfm(actual).should match(expected)
end
it 'should not link with an invalid id' do
# Modify the reference string so it's still parsed, but is invalid
if object.is_a?(Commit)
reference.gsub!(/^(.).+$/, '\1' + '12345abcd')
else
reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2))
end
gfm(actual).should == actual
end
it 'should include a title attribute' do
if object.is_a?(Commit)
title = object.link_title
else
title = "#{object.class.to_s.titlecase}: #{object.title}"
end
gfm(actual).should match(/title="#{title}"/)
end
it 'should include standard gfm classes' do
css = object.class.to_s.underscore
gfm(actual).should match(/class="\s?gfm gfm-#{css}\s?"/)
end
end
describe "referencing an issue" do describe "referencing an issue" do
let(:object) { issue } let(:object) { issue }
let(:reference) { "##{issue.iid}" } let(:reference) { "##{issue.iid}" }
...@@ -188,6 +258,38 @@ describe GitlabMarkdownHelper do ...@@ -188,6 +258,38 @@ describe GitlabMarkdownHelper do
include_examples 'referenced object' include_examples 'referenced object'
end end
context 'cross-repo references' do
before(:all) do
@other_project = create(:project, :public)
@commit2 = @other_project.repository.commit
@issue2 = create(:issue, project: @other_project)
@merge_request2 = create(:merge_request,
source_project: @other_project,
target_project: @other_project)
end
describe 'referencing an issue in another project' do
let(:object) { @issue2 }
let(:reference) { "##{@issue2.iid}" }
include_examples 'cross-project referenced object'
end
describe 'referencing an merge request in another project' do
let(:object) { @merge_request2 }
let(:reference) { "!#{@merge_request2.iid}" }
include_examples 'cross-project referenced object'
end
describe 'referencing a commit in another project' do
let(:object) { @commit2 }
let(:reference) { "@#{@commit2.id}" }
include_examples 'cross-project referenced object'
end
end
describe "referencing a Jira issue" do describe "referencing a Jira issue" do
let(:actual) { "Reference to JIRA-#{issue.iid}" } let(:actual) { "Reference to JIRA-#{issue.iid}" }
let(:expected) { "http://jira.example/browse/JIRA-#{issue.iid}" } let(:expected) { "http://jira.example/browse/JIRA-#{issue.iid}" }
...@@ -474,6 +576,24 @@ describe GitlabMarkdownHelper do ...@@ -474,6 +576,24 @@ describe GitlabMarkdownHelper do
markdown(actual).should match(%r{<li>light by <a.+>@#{member.user.username}</a></li>}) markdown(actual).should match(%r{<li>light by <a.+>@#{member.user.username}</a></li>})
end end
it "should not link the apostrophe to issue 39" do
project.team << [user, :master]
project.issues.stub(:where).with(iid: '39').and_return([issue])
actual = "Yes, it is @#{member.user.username}'s task."
expected = /Yes, it is <a.+>@#{member.user.username}<\/a>'s task/
markdown(actual).should match(expected)
end
it "should not link the apostrophe to issue 39 in code blocks" do
project.team << [user, :master]
project.issues.stub(:where).with(iid: '39').and_return([issue])
actual = "Yes, `it is @#{member.user.username}'s task.`"
expected = /Yes, <code>it is @gfm\'s task.<\/code>/
markdown(actual).should match(expected)
end
it "should handle references in <em>" do it "should handle references in <em>" do
actual = "Apply _!#{merge_request.iid}_ ASAP" actual = "Apply _!#{merge_request.iid}_ ASAP"
...@@ -520,9 +640,21 @@ describe GitlabMarkdownHelper do ...@@ -520,9 +640,21 @@ describe GitlabMarkdownHelper do
end end
it "should generate absolute urls for emoji" do it "should generate absolute urls for emoji" do
markdown(":smile:").should include("src=\"#{url_helper('emoji/smile')}") markdown(":smile:").should include("src=\"http://localhost/assets/emoji/smile.png")
end
it "should generate absolute urls for emoji if relative url is present" do
Gitlab.config.gitlab.stub(:url).and_return('http://localhost/gitlab/root')
markdown(":smile:").should include("src=\"http://localhost/gitlab/root/assets/emoji/smile.png")
end end
it "should generate absolute urls for emoji if asset_host is present" do
Gitlab::Application.config.stub(:asset_host).and_return("https://cdn.example.com")
ActionView::Base.any_instance.stub_chain(:config, :asset_host).and_return("https://cdn.example.com")
markdown(":smile:").should include("src=\"https://cdn.example.com/assets/emoji/smile.png")
end
it "should handle relative urls for a file in master" do it "should handle relative urls for a file in master" do
actual = "[GitLab API doc](doc/api/README.md)\n" actual = "[GitLab API doc](doc/api/README.md)\n"
expected = "<p><a href=\"/#{project.path_with_namespace}/blob/#{@ref}/doc/api/README.md\">GitLab API doc</a></p>\n" expected = "<p><a href=\"/#{project.path_with_namespace}/blob/#{@ref}/doc/api/README.md\">GitLab API doc</a></p>\n"
...@@ -560,7 +692,7 @@ describe GitlabMarkdownHelper do ...@@ -560,7 +692,7 @@ describe GitlabMarkdownHelper do
end end
end end
describe "markdwon for empty repository" do describe 'markdown for empty repository' do
before do before do
@project = empty_project @project = empty_project
@repository = empty_project.repository @repository = empty_project.repository
...@@ -596,4 +728,103 @@ describe GitlabMarkdownHelper do ...@@ -596,4 +728,103 @@ describe GitlabMarkdownHelper do
helper.render_wiki_content(@wiki) helper.render_wiki_content(@wiki)
end end
end end
describe '#gfm_with_tasks' do
before(:all) do
@source_text_asterisk = <<EOT.gsub(/^\s{8}/, '')
* [ ] valid unchecked task
* [x] valid lowercase checked task
* [X] valid uppercase checked task
* [ ] valid unchecked nested task
* [x] valid checked nested task
[ ] not an unchecked task - no list item
[x] not a checked task - no list item
* [ ] not an unchecked task - too many spaces
* [x ] not a checked task - too many spaces
* [] not an unchecked task - no spaces
* Not a task [ ] - not at beginning
EOT
@source_text_dash = <<EOT.gsub(/^\s{8}/, '')
- [ ] valid unchecked task
- [x] valid lowercase checked task
- [X] valid uppercase checked task
- [ ] valid unchecked nested task
- [x] valid checked nested task
EOT
end
it 'should render checkboxes at beginning of asterisk list items' do
rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
expect(rendered_text).to match(/<input.*checkbox.*valid unchecked task/)
expect(rendered_text).to match(
/<input.*checkbox.*valid lowercase checked task/
)
expect(rendered_text).to match(
/<input.*checkbox.*valid uppercase checked task/
)
end
it 'should render checkboxes at beginning of dash list items' do
rendered_text = markdown(@source_text_dash, parse_tasks: true)
expect(rendered_text).to match(/<input.*checkbox.*valid unchecked task/)
expect(rendered_text).to match(
/<input.*checkbox.*valid lowercase checked task/
)
expect(rendered_text).to match(
/<input.*checkbox.*valid uppercase checked task/
)
end
it 'should not be confused by whitespace before bullets' do
rendered_text_asterisk = markdown(@source_text_asterisk,
parse_tasks: true)
rendered_text_dash = markdown(@source_text_dash, parse_tasks: true)
expect(rendered_text_asterisk).to match(
/<input.*checkbox.*valid unchecked nested task/
)
expect(rendered_text_asterisk).to match(
/<input.*checkbox.*valid checked nested task/
)
expect(rendered_text_dash).to match(
/<input.*checkbox.*valid unchecked nested task/
)
expect(rendered_text_dash).to match(
/<input.*checkbox.*valid checked nested task/
)
end
it 'should not render checkboxes outside of list items' do
rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
expect(rendered_text).not_to match(
/<input.*checkbox.*not an unchecked task - no list item/
)
expect(rendered_text).not_to match(
/<input.*checkbox.*not a checked task - no list item/
)
end
it 'should not render checkboxes with invalid formatting' do
rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
expect(rendered_text).not_to match(
/<input.*checkbox.*not an unchecked task - too many spaces/
)
expect(rendered_text).not_to match(
/<input.*checkbox.*not a checked task - too many spaces/
)
expect(rendered_text).not_to match(
/<input.*checkbox.*not an unchecked task - no spaces/
)
expect(rendered_text).not_to match(
/Not a task.*<input.*checkbox.*not at beginning/
)
end
end
end end
...@@ -31,13 +31,13 @@ describe Gitlab::Auth do ...@@ -31,13 +31,13 @@ describe Gitlab::Auth do
before { Gitlab::LDAP::Config.stub(enabled?: true) } before { Gitlab::LDAP::Config.stub(enabled?: true) }
it "tries to autheticate with db before ldap" do it "tries to autheticate with db before ldap" do
expect(Gitlab::LDAP::User).not_to receive(:authenticate) expect(Gitlab::LDAP::Authentication).not_to receive(:login)
gl_auth.find(username, password) gl_auth.find(username, password)
end end
it "uses ldap as fallback to for authentication" do it "uses ldap as fallback to for authentication" do
expect(Gitlab::LDAP::User).to receive(:authenticate) expect(Gitlab::LDAP::Authentication).to receive(:login)
gl_auth.find('ldap_user', 'password') gl_auth.find('ldap_user', 'password')
end end
......
require 'spec_helper'
describe Gitlab::GitAccessWiki do
let(:access) { Gitlab::GitAccessWiki.new }
let(:project) { create(:project) }
let(:user) { create(:user) }
describe 'push_allowed?' do
before do
create(:protected_branch, name: 'master', project: project)
project.team << [user, :developer]
end
subject { access.push_allowed?(user, project, changes) }
it { should be_true }
end
def changes
['6f6d7e7ed 570e7b2ab refs/heads/master']
end
end
require 'spec_helper'
describe Gitlab::LDAP::Authentication do
let(:klass) { Gitlab::LDAP::Authentication }
let(:user) { create(:user, :ldap, extern_uid: dn) }
let(:dn) { 'uid=john,ou=people,dc=example,dc=com' }
let(:login) { 'john' }
let(:password) { 'password' }
describe :login do
let(:adapter) { double :adapter }
before do
Gitlab::LDAP::Config.stub(enabled?: true)
end
it "finds the user if authentication is successful" do
user
# try only to fake the LDAP call
klass.any_instance.stub(adapter: double(:adapter,
bind_as: double(:ldap_user, dn: dn)
))
expect(klass.login(login, password)).to be_true
end
it "is false if the user does not exist" do
# try only to fake the LDAP call
klass.any_instance.stub(adapter: double(:adapter,
bind_as: double(:ldap_user, dn: dn)
))
expect(klass.login(login, password)).to be_false
end
it "is false if authentication fails" do
user
# try only to fake the LDAP call
klass.any_instance.stub(adapter: double(:adapter, bind_as: nil))
expect(klass.login(login, password)).to be_false
end
it "fails if ldap is disabled" do
Gitlab::LDAP::Config.stub(enabled?: false)
expect(klass.login(login, password)).to be_false
end
it "fails if no login is supplied" do
expect(klass.login('', password)).to be_false
end
it "fails if no password is supplied" do
expect(klass.login(login, '')).to be_false
end
end
end
\ No newline at end of file
...@@ -33,21 +33,4 @@ describe Gitlab::LDAP::User do ...@@ -33,21 +33,4 @@ describe Gitlab::LDAP::User do
expect{ gl_user.save }.to change{ User.count }.by(1) expect{ gl_user.save }.to change{ User.count }.by(1)
end end
end end
describe "authenticate" do
let(:login) { 'john' }
let(:password) { 'my-secret' }
# before {
# Gitlab.config.ldap['enabled'] = true
# Gitlab.config.ldap['user_filter'] = 'employeeType=developer'
# }
# after { Gitlab.config.ldap['enabled'] = false }
it "send an authentication request to ldap" do
pending('needs refactoring')
expect( Gitlab::LDAP::User.adapter ).to receive(:bind_as)
Gitlab::LDAP::User.authenticate(login, password)
end
end
end end
...@@ -2,45 +2,48 @@ require 'spec_helper' ...@@ -2,45 +2,48 @@ require 'spec_helper'
describe Gitlab::ReferenceExtractor do describe Gitlab::ReferenceExtractor do
it 'extracts username references' do it 'extracts username references' do
subject.analyze "this contains a @user reference" subject.analyze('this contains a @user reference', nil)
subject.users.should == ["user"] subject.users.should == [{ project: nil, id: 'user' }]
end end
it 'extracts issue references' do it 'extracts issue references' do
subject.analyze "this one talks about issue #1234" subject.analyze('this one talks about issue #1234', nil)
subject.issues.should == ["1234"] subject.issues.should == [{ project: nil, id: '1234' }]
end end
it 'extracts JIRA issue references' do it 'extracts JIRA issue references' do
Gitlab.config.gitlab.stub(:issues_tracker).and_return("jira") Gitlab.config.gitlab.stub(:issues_tracker).and_return('jira')
subject.analyze "this one talks about issue JIRA-1234" subject.analyze('this one talks about issue JIRA-1234', nil)
subject.issues.should == ["JIRA-1234"] subject.issues.should == [{ project: nil, id: 'JIRA-1234' }]
end end
it 'extracts merge request references' do it 'extracts merge request references' do
subject.analyze "and here's !43, a merge request" subject.analyze("and here's !43, a merge request", nil)
subject.merge_requests.should == ["43"] subject.merge_requests.should == [{ project: nil, id: '43' }]
end end
it 'extracts snippet ids' do it 'extracts snippet ids' do
subject.analyze "snippets like $12 get extracted as well" subject.analyze('snippets like $12 get extracted as well', nil)
subject.snippets.should == ["12"] subject.snippets.should == [{ project: nil, id: '12' }]
end end
it 'extracts commit shas' do it 'extracts commit shas' do
subject.analyze "commit shas 98cf0ae3 are pulled out as Strings" subject.analyze('commit shas 98cf0ae3 are pulled out as Strings', nil)
subject.commits.should == ["98cf0ae3"] subject.commits.should == [{ project: nil, id: '98cf0ae3' }]
end end
it 'extracts multiple references and preserves their order' do it 'extracts multiple references and preserves their order' do
subject.analyze "@me and @you both care about this" subject.analyze('@me and @you both care about this', nil)
subject.users.should == ["me", "you"] subject.users.should == [
{ project: nil, id: 'me' },
{ project: nil, id: 'you' }
]
end end
it 'leaves the original note unmodified' do it 'leaves the original note unmodified' do
text = "issue #123 is just the worst, @user" text = 'issue #123 is just the worst, @user'
subject.analyze text subject.analyze(text, nil)
text.should == "issue #123 is just the worst, @user" text.should == 'issue #123 is just the worst, @user'
end end
it 'handles all possible kinds of references' do it 'handles all possible kinds of references' do
...@@ -59,7 +62,7 @@ describe Gitlab::ReferenceExtractor do ...@@ -59,7 +62,7 @@ describe Gitlab::ReferenceExtractor do
project.team << [@u_foo, :reporter] project.team << [@u_foo, :reporter]
project.team << [@u_bar, :guest] project.team << [@u_bar, :guest]
subject.analyze "@foo, @baduser, @bar, and @offteam" subject.analyze('@foo, @baduser, @bar, and @offteam', project)
subject.users_for(project).should == [@u_foo, @u_bar] subject.users_for(project).should == [@u_foo, @u_bar]
end end
...@@ -67,14 +70,14 @@ describe Gitlab::ReferenceExtractor do ...@@ -67,14 +70,14 @@ describe Gitlab::ReferenceExtractor do
@i0 = create(:issue, project: project) @i0 = create(:issue, project: project)
@i1 = create(:issue, project: project) @i1 = create(:issue, project: project)
subject.analyze "##{@i0.iid}, ##{@i1.iid}, and #999." subject.analyze("##{@i0.iid}, ##{@i1.iid}, and #999.", project)
subject.issues_for(project).should == [@i0, @i1] subject.issues_for(project).should == [@i0, @i1]
end end
it 'returns JIRA issues for a JIRA-integrated project' do it 'returns JIRA issues for a JIRA-integrated project' do
project.stub(jira_tracker?: true) project.stub(jira_tracker?: true)
subject.analyze('JIRA-123 and FOOBAR-4567') subject.analyze('JIRA-123 and FOOBAR-4567', project)
subject.issues_for(project).should eq( subject.issues_for(project).should eq(
[JiraIssue.new('JIRA-123'), JiraIssue.new('FOOBAR-4567')] [JiraIssue.new('JIRA-123'), JiraIssue.new('FOOBAR-4567')]
) )
...@@ -84,7 +87,7 @@ describe Gitlab::ReferenceExtractor do ...@@ -84,7 +87,7 @@ describe Gitlab::ReferenceExtractor do
@m0 = create(:merge_request, source_project: project, target_project: project, source_branch: 'aaa') @m0 = create(:merge_request, source_project: project, target_project: project, source_branch: 'aaa')
@m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'bbb') @m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'bbb')
subject.analyze "!999, !#{@m1.iid}, and !#{@m0.iid}." subject.analyze("!999, !#{@m1.iid}, and !#{@m0.iid}.", project)
subject.merge_requests_for(project).should == [@m1, @m0] subject.merge_requests_for(project).should == [@m1, @m0]
end end
...@@ -93,14 +96,15 @@ describe Gitlab::ReferenceExtractor do ...@@ -93,14 +96,15 @@ describe Gitlab::ReferenceExtractor do
@s1 = create(:project_snippet, project: project) @s1 = create(:project_snippet, project: project)
@s2 = create(:project_snippet) @s2 = create(:project_snippet)
subject.analyze "$#{@s0.id}, $999, $#{@s2.id}, $#{@s1.id}" subject.analyze("$#{@s0.id}, $999, $#{@s2.id}, $#{@s1.id}", project)
subject.snippets_for(project).should == [@s0, @s1] subject.snippets_for(project).should == [@s0, @s1]
end end
it 'accesses valid commits' do it 'accesses valid commits' do
commit = project.repository.commit("master") commit = project.repository.commit('master')
subject.analyze "this references commits #{commit.sha[0..6]} and 012345" subject.analyze("this references commits #{commit.sha[0..6]} and 012345",
project)
extracted = subject.commits_for(project) extracted = subject.commits_for(project)
extracted.should have(1).item extracted.should have(1).item
extracted[0].sha.should == commit.sha extracted[0].sha.should == commit.sha
......
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
require 'spec_helper'
describe BuildboxService do
describe 'Associations' do
it { should belong_to :project }
it { should have_one :service_hook }
end
describe 'commits methods' do
before do
@project = Project.new
@project.stub(
default_branch: 'default-brancho'
)
@service = BuildboxService.new
@service.stub(
project: @project,
service_hook: true,
project_url: 'https://buildbox.io/account-name/example-project',
token: 'secret-sauce-webhook-token:secret-sauce-status-token'
)
end
describe :webhook_url do
it 'returns the webhook url' do
@service.webhook_url.should ==
'https://webhook.buildbox.io/deliver/secret-sauce-webhook-token'
end
end
describe :commit_status_path do
it 'returns the correct status page' do
@service.commit_status_path('2ab7834c').should ==
'https://gitlab.buildbox.io/status/secret-sauce-status-token.json?commit=2ab7834c'
end
end
describe :build_page do
it 'returns the correct build page' do
@service.build_page('2ab7834c').should ==
'https://buildbox.io/account-name/example-project/builds?commit=2ab7834c'
end
end
describe :builds_page do
it 'returns the correct path to the builds page' do
@service.builds_path.should ==
'https://buildbox.io/account-name/example-project/builds?branch=default-brancho'
end
end
describe :status_img_path do
it 'returns the correct path to the status image' do
@service.status_img_path.should == 'https://badge.buildbox.io/secret-sauce-status-token.svg'
end
end
end
end
...@@ -53,17 +53,29 @@ eos ...@@ -53,17 +53,29 @@ eos
describe '#closes_issues' do describe '#closes_issues' do
let(:issue) { create :issue, project: project } let(:issue) { create :issue, project: project }
let(:other_project) { create :project, :public }
let(:other_issue) { create :issue, project: other_project }
it 'detects issues that this commit is marked as closing' do it 'detects issues that this commit is marked as closing' do
commit.stub(issue_closing_regex: /^([Cc]loses|[Ff]ixes) #\d+/, safe_message: "Fixes ##{issue.iid}") stub_const('Gitlab::ClosingIssueExtractor::ISSUE_CLOSING_REGEX',
/Fixes #\d+/)
commit.stub(safe_message: "Fixes ##{issue.iid}")
commit.closes_issues(project).should == [issue] commit.closes_issues(project).should == [issue]
end end
it 'does not detect issues from other projects' do
ext_ref = "#{other_project.path_with_namespace}##{other_issue.iid}"
stub_const('Gitlab::ClosingIssueExtractor::ISSUE_CLOSING_REGEX',
/^([Cc]loses|[Ff]ixes)/)
commit.stub(safe_message: "Fixes #{ext_ref}")
commit.closes_issues(project).should be_empty
end
end end
it_behaves_like 'a mentionable' do it_behaves_like 'a mentionable' do
let(:subject) { commit } let(:subject) { commit }
let(:mauthor) { create :user, email: commit.author_email } let(:mauthor) { create :user, email: commit.author_email }
let(:backref_text) { "commit #{subject.sha[0..5]}" } let(:backref_text) { "commit #{subject.id}" }
let(:set_mentionable_text) { ->(txt){ subject.stub(safe_message: txt) } } let(:set_mentionable_text) { ->(txt){ subject.stub(safe_message: txt) } }
# Include the subject in the repository stub. # Include the subject in the repository stub.
......
# == Schema Information # == Schema Information
# #
# Table name: group_members # Table name: members
# #
# id :integer not null, primary key # id :integer not null, primary key
# access_level :integer not null # access_level :integer not null
# group_id :integer not null # source_id :integer not null
# source_type :string(255) not null
# user_id :integer not null # user_id :integer not null
# notification_level :integer not null
# type :string(255)
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# notification_level :integer default(3), not null
# #
require 'spec_helper' require 'spec_helper'
......
...@@ -60,4 +60,8 @@ describe Issue do ...@@ -60,4 +60,8 @@ describe Issue do
let(:backref_text) { "issue ##{subject.iid}" } let(:backref_text) { "issue ##{subject.iid}" }
let(:set_mentionable_text) { ->(txt){ subject.description = txt } } let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
end end
it_behaves_like 'a Taskable' do
let(:subject) { create :issue }
end
end end
...@@ -130,4 +130,8 @@ describe MergeRequest do ...@@ -130,4 +130,8 @@ describe MergeRequest do
let(:backref_text) { "merge request !#{subject.iid}" } let(:backref_text) { "merge request !#{subject.iid}" }
let(:set_mentionable_text) { ->(txt){ subject.title = txt } } let(:set_mentionable_text) { ->(txt){ subject.title = txt } }
end end
it_behaves_like 'a Taskable' do
let(:subject) { create :merge_request, :simple }
end
end end
...@@ -228,7 +228,7 @@ describe Note do ...@@ -228,7 +228,7 @@ describe Note do
it { should be_valid } it { should be_valid }
its(:noteable) { should == issue } its(:noteable) { should == issue }
its(:note) { should == "_mentioned in commit #{commit.sha[0..5]}_" } its(:note) { should == "_mentioned in commit #{commit.sha}_" }
end end
context 'merge request from an issue' do context 'merge request from an issue' do
...@@ -258,14 +258,25 @@ describe Note do ...@@ -258,14 +258,25 @@ describe Note do
its(:commit_id) { should == commit.id } its(:commit_id) { should == commit.id }
its(:note) { should == "_mentioned in issue ##{issue.iid}_" } its(:note) { should == "_mentioned in issue ##{issue.iid}_" }
end end
context 'commit from commit' do
let(:parent_commit) { commit.parents.first }
subject { Note.create_cross_reference_note(commit, parent_commit, author, project) }
it { should be_valid }
its(:noteable_type) { should == "Commit" }
its(:noteable_id) { should be_nil }
its(:commit_id) { should == commit.id }
its(:note) { should == "_mentioned in commit #{parent_commit.id}_" }
end
end end
describe '#cross_reference_exists?' do describe '#cross_reference_exists?' do
let(:project) { create :project } let(:project) { create :project }
let(:author) { create :user } let(:author) { create :user }
let(:issue) { create :issue } let(:issue) { create :issue }
let(:commit0) { double 'commit0', gfm_reference: 'commit 123456' } let(:commit0) { project.repository.commit }
let(:commit1) { double 'commit1', gfm_reference: 'commit 654321' } let(:commit1) { project.repository.commit('HEAD~2') }
before do before do
Note.create_cross_reference_note(issue, commit0, author, project) Note.create_cross_reference_note(issue, commit0, author, project)
...@@ -278,6 +289,15 @@ describe Note do ...@@ -278,6 +289,15 @@ describe Note do
it 'detects if a mentionable has not already been mentioned' do it 'detects if a mentionable has not already been mentioned' do
Note.cross_reference_exists?(issue, commit1).should be_false Note.cross_reference_exists?(issue, commit1).should be_false
end end
context 'commit on commit' do
before do
Note.create_cross_reference_note(commit0, commit1, author, project)
end
it { Note.cross_reference_exists?(commit0, commit1).should be_true }
it { Note.cross_reference_exists?(commit1, commit0).should be_false }
end
end end
describe '#system?' do describe '#system?' do
......
# == Schema Information # == Schema Information
# #
# Table name: project_members # Table name: members
# #
# id :integer not null, primary key # id :integer not null, primary key
# access_level :integer not null
# source_id :integer not null
# source_type :string(255) not null
# user_id :integer not null # user_id :integer not null
# project_id :integer not null # notification_level :integer not null
# type :string(255)
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# project_access :integer default(0), not null
# notification_level :integer default(3), not null
# #
require 'spec_helper' require 'spec_helper'
......
...@@ -11,8 +11,8 @@ ...@@ -11,8 +11,8 @@
# updated_at :datetime # updated_at :datetime
# file_name :string(255) # file_name :string(255)
# expires_at :datetime # expires_at :datetime
# private :boolean default(TRUE), not null
# type :string(255) # type :string(255)
# visibility_level :integer default(0), not null
# #
require 'spec_helper' require 'spec_helper'
......
...@@ -47,6 +47,7 @@ describe Project do ...@@ -47,6 +47,7 @@ describe Project do
it { should have_many(:protected_branches).dependent(:destroy) } it { should have_many(:protected_branches).dependent(:destroy) }
it { should have_one(:forked_project_link).dependent(:destroy) } it { should have_one(:forked_project_link).dependent(:destroy) }
it { should have_one(:slack_service).dependent(:destroy) } it { should have_one(:slack_service).dependent(:destroy) }
it { should have_one(:pushover_service).dependent(:destroy) }
end end
describe "Mass assignment" do describe "Mass assignment" do
......
...@@ -27,6 +27,8 @@ describe ProjectTeam do ...@@ -27,6 +27,8 @@ describe ProjectTeam do
it { project.team.master?(guest).should be_false } it { project.team.master?(guest).should be_false }
it { project.team.master?(reporter).should be_false } it { project.team.master?(reporter).should be_false }
it { project.team.master?(nonmember).should be_false } it { project.team.master?(nonmember).should be_false }
it { project.team.member?(nonmember).should be_false }
it { project.team.member?(guest).should be_true }
end end
end end
...@@ -60,6 +62,8 @@ describe ProjectTeam do ...@@ -60,6 +62,8 @@ describe ProjectTeam do
it { project.team.master?(guest).should be_true } it { project.team.master?(guest).should be_true }
it { project.team.master?(reporter).should be_false } it { project.team.master?(reporter).should be_false }
it { project.team.master?(nonmember).should be_false } it { project.team.master?(nonmember).should be_false }
it { project.team.member?(nonmember).should be_false }
it { project.team.member?(guest).should be_true }
end end
end end
......
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
require 'spec_helper'
describe PushoverService 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 :api_key }
it { should validate_presence_of :user_key }
it { should validate_presence_of :priority }
end
end
describe 'Execute' do
let(:pushover) { PushoverService.new }
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:sample_data) { GitPushService.new.sample_data(project, user) }
let(:api_key) { 'verySecret' }
let(:user_key) { 'verySecret' }
let(:device) { 'myDevice' }
let(:priority) { 0 }
let(:sound) { 'bike' }
let(:api_url) { 'https://api.pushover.net/1/messages.json' }
before do
pushover.stub(
project: project,
project_id: project.id,
service_hook: true,
api_key: api_key,
user_key: user_key,
device: device,
priority: priority,
sound: sound
)
WebMock.stub_request(:post, api_url)
end
it 'should call Pushover API' do
pushover.execute(sample_data)
WebMock.should have_requested(:post, api_url).once
end
end
end
...@@ -26,31 +26,28 @@ describe SlackService do ...@@ -26,31 +26,28 @@ describe SlackService do
subject.active = true subject.active = true
end end
it { should validate_presence_of :room } it { should validate_presence_of :webhook }
it { should validate_presence_of :subdomain }
it { should validate_presence_of :token }
end end
end end
describe "Execute" do describe "Execute" do
let(:slack) { SlackService.new } let(:slack) { SlackService.new }
let(:slack_service) { SlackService.new }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project) } let(:project) { create(:project) }
let(:sample_data) { GitPushService.new.sample_data(project, user) } let(:sample_data) { GitPushService.new.sample_data(project, user) }
let(:subdomain) { 'gitlab' } let(:webhook) { 'https://gitlabhq.slack.com/services/hooks?token=cdIj4r4LfXUOySDUjp0tk3OI' }
let(:token) { 'verySecret' } let(:new_webhook) { 'https://hooks.gitlabhq.slack.com/services/cdIj4r4LfXUOySDUjp0tk3OI' }
let(:api_url) { let(:api_url) {
"https://#{subdomain}.slack.com/services/hooks/incoming-webhook?token=#{token}" 'https://gitlabhq.slack.com/services/hooks/incoming-webhook?token=cdIj4r4LfXUOySDUjp0tk3OI'
} }
before do before do
slack.stub( slack.stub(
project: project, project: project,
project_id: project.id, project_id: project.id,
room: '#gitlab',
service_hook: true, service_hook: true,
subdomain: subdomain, webhook: webhook
token: token
) )
WebMock.stub_request(:post, api_url) WebMock.stub_request(:post, api_url)
...@@ -61,5 +58,24 @@ describe SlackService do ...@@ -61,5 +58,24 @@ describe SlackService do
WebMock.should have_requested(:post, api_url).once WebMock.should have_requested(:post, api_url).once
end end
context 'with new webhook syntax' do
before do
slack_service.stub(
project: project,
project_id: project.id,
service_hook: true,
webhook: new_webhook
)
WebMock.stub_request(:post, api_url)
end
it "should call Slack API" do
slack_service.execute(sample_data)
WebMock.should have_requested(:post, api_url).once
end
end
end end
end end
...@@ -11,8 +11,8 @@ ...@@ -11,8 +11,8 @@
# updated_at :datetime # updated_at :datetime
# file_name :string(255) # file_name :string(255)
# expires_at :datetime # expires_at :datetime
# private :boolean default(TRUE), not null
# type :string(255) # type :string(255)
# visibility_level :integer default(0), not null
# #
require 'spec_helper' require 'spec_helper'
......
...@@ -448,4 +448,32 @@ describe User do ...@@ -448,4 +448,32 @@ describe User do
expect(user.starred?(project)).to be_false expect(user.starred?(project)).to be_false
end end
end end
describe "#sort" do
before do
User.delete_all
@user = create :user, created_at: Date.today, last_sign_in_at: Date.today, name: 'Alpha'
@user1 = create :user, created_at: Date.today - 1, last_sign_in_at: Date.today - 1, name: 'Omega'
end
it "sorts users as recently_signed_in" do
User.sort('recent_sign_in').first.should == @user
end
it "sorts users as late_signed_in" do
User.sort('oldest_sign_in').first.should == @user1
end
it "sorts users as recently_created" do
User.sort('recently_created').first.should == @user
end
it "sorts users as late_created" do
User.sort('late_created').first.should == @user1
end
it "sorts users by name when nil is passed" do
User.sort(nil).first.should == @user
end
end
end end
...@@ -20,8 +20,7 @@ describe API::API, api: true do ...@@ -20,8 +20,7 @@ describe API::API, api: true do
response.status.should == 200 response.status.should == 200
json_response.should be_an Array json_response.should be_an Array
# Admin namespace + 2 group namespaces json_response.length.should == Namespace.count
json_response.length.should == 3
end end
end end
end end
......
...@@ -54,8 +54,15 @@ describe API::API, api: true do ...@@ -54,8 +54,15 @@ describe API::API, api: true do
get api("/projects/all", admin) get api("/projects/all", admin)
response.status.should == 200 response.status.should == 200
json_response.should be_an Array json_response.should be_an Array
json_response.first['name'].should == project.name project_name = project.name
json_response.first['owner']['username'].should == user.username
json_response.detect {
|project| project['name'] == project_name
}['name'].should == project_name
json_response.detect {
|project| project['owner']['username'] == user.username
}['owner']['username'].should == user.username
end end
end end
end end
......
...@@ -20,7 +20,10 @@ describe API::API, api: true do ...@@ -20,7 +20,10 @@ describe API::API, api: true do
get api("/users", user) get api("/users", user)
response.status.should == 200 response.status.should == 200
json_response.should be_an Array json_response.should be_an Array
json_response.first['username'].should == user.username username = user.username
json_response.detect {
|user| user['username'] == username
}['username'].should == username
end end
end end
......
...@@ -14,21 +14,31 @@ def common_mentionable_setup ...@@ -14,21 +14,31 @@ def common_mentionable_setup
let(:mentioned_mr) { create :merge_request, :simple, source_project: mproject } let(:mentioned_mr) { create :merge_request, :simple, source_project: mproject }
let(:mentioned_commit) { double('commit', sha: '1234567890abcdef').as_null_object } let(:mentioned_commit) { double('commit', sha: '1234567890abcdef').as_null_object }
let(:ext_proj) { create :project, :public }
let(:ext_issue) { create :issue, project: ext_proj }
let(:other_ext_issue) { create :issue, project: ext_proj }
let(:ext_mr) { create :merge_request, :simple, source_project: ext_proj }
let(:ext_commit) { ext_proj.repository.commit }
# Override to add known commits to the repository stub. # Override to add known commits to the repository stub.
let(:extra_commits) { [] } let(:extra_commits) { [] }
# A string that mentions each of the +mentioned_.*+ objects above. Mentionables should add a self-reference # A string that mentions each of the +mentioned_.*+ objects above. Mentionables should add a self-reference
# to this string and place it in their +mentionable_text+. # to this string and place it in their +mentionable_text+.
let(:ref_string) do let(:ref_string) do
"mentions ##{mentioned_issue.iid} twice ##{mentioned_issue.iid}, !#{mentioned_mr.iid}, " + "mentions ##{mentioned_issue.iid} twice ##{mentioned_issue.iid}, " +
"#{mentioned_commit.sha[0..5]} and itself as #{backref_text}" "!#{mentioned_mr.iid}, " +
"#{ext_proj.path_with_namespace}##{ext_issue.iid}, " +
"#{ext_proj.path_with_namespace}!#{ext_mr.iid}, " +
"#{ext_proj.path_with_namespace}@#{ext_commit.short_id}, " +
"#{mentioned_commit.sha[0..10]} and itself as #{backref_text}"
end end
before do before do
# Wire the project's repository to return the mentioned commit, and +nil+ for any # Wire the project's repository to return the mentioned commit, and +nil+ for any
# unrecognized commits. # unrecognized commits.
commitmap = { '123456' => mentioned_commit } commitmap = { '1234567890a' => mentioned_commit }
extra_commits.each { |c| commitmap[c.sha[0..5]] = c } extra_commits.each { |c| commitmap[c.short_id] = c }
mproject.repository.stub(:commit) { |sha| commitmap[sha] } mproject.repository.stub(:commit) { |sha| commitmap[sha] }
set_mentionable_text.call(ref_string) set_mentionable_text.call(ref_string)
end end
...@@ -44,15 +54,20 @@ shared_examples 'a mentionable' do ...@@ -44,15 +54,20 @@ shared_examples 'a mentionable' do
it "extracts references from its reference property" do it "extracts references from its reference property" do
# De-duplicate and omit itself # De-duplicate and omit itself
refs = subject.references(mproject) refs = subject.references(mproject)
refs.should have(6).items
refs.should have(3).items
refs.should include(mentioned_issue) refs.should include(mentioned_issue)
refs.should include(mentioned_mr) refs.should include(mentioned_mr)
refs.should include(mentioned_commit) refs.should include(mentioned_commit)
refs.should include(ext_issue)
refs.should include(ext_mr)
refs.should include(ext_commit)
end end
it 'creates cross-reference notes' do it 'creates cross-reference notes' do
[mentioned_issue, mentioned_mr, mentioned_commit].each do |referenced| mentioned_objects = [mentioned_issue, mentioned_mr, mentioned_commit,
ext_issue, ext_mr, ext_commit]
mentioned_objects.each do |referenced|
Note.should_receive(:create_cross_reference_note).with(referenced, subject.local_reference, mauthor, mproject) Note.should_receive(:create_cross_reference_note).with(referenced, subject.local_reference, mauthor, mproject)
end end
...@@ -73,15 +88,25 @@ shared_examples 'an editable mentionable' do ...@@ -73,15 +88,25 @@ shared_examples 'an editable mentionable' do
it_behaves_like 'a mentionable' it_behaves_like 'a mentionable'
it 'creates new cross-reference notes when the mentionable text is edited' do it 'creates new cross-reference notes when the mentionable text is edited' do
new_text = "this text still mentions ##{mentioned_issue.iid} and #{mentioned_commit.sha[0..5]}, " + new_text = "still mentions ##{mentioned_issue.iid}, " +
"but now it mentions ##{other_issue.iid}, too." "#{mentioned_commit.sha[0..10]}, " +
"#{ext_issue.iid}, " +
"new refs: ##{other_issue.iid}, " +
"#{ext_proj.path_with_namespace}##{other_ext_issue.iid}"
[mentioned_issue, mentioned_commit].each do |oldref| [mentioned_issue, mentioned_commit, ext_issue].each do |oldref|
Note.should_not_receive(:create_cross_reference_note).with(oldref, subject.local_reference, Note.should_not_receive(:create_cross_reference_note).with(oldref, subject.local_reference,
mauthor, mproject) mauthor, mproject)
end end
Note.should_receive(:create_cross_reference_note).with(other_issue, subject.local_reference, mauthor, mproject) [other_issue, other_ext_issue].each do |newref|
Note.should_receive(:create_cross_reference_note).with(
newref,
subject.local_reference,
mauthor,
mproject
)
end
subject.save subject.save
set_mentionable_text.call(new_text) set_mentionable_text.call(new_text)
......
# Specs for task state functionality for issues and merge requests.
#
# Requires a context containing:
# let(:subject) { Issue or MergeRequest }
shared_examples 'a Taskable' do
before do
subject.description = <<EOT.gsub(/ {6}/, '')
* [ ] Task 1
* [x] Task 2
* [x] Task 3
* [ ] Task 4
* [ ] Task 5
EOT
end
it 'updates the Nth task correctly' do
subject.update_nth_task(1, true)
expect(subject.description).to match(/\[x\] Task 1/)
subject.update_nth_task(2, true)
expect(subject.description).to match('\[x\] Task 2')
subject.update_nth_task(3, false)
expect(subject.description).to match('\[ \] Task 3')
subject.update_nth_task(4, false)
expect(subject.description).to match('\[ \] Task 4')
end
it 'returns the correct task status' do
expect(subject.task_status).to match('5 tasks')
expect(subject.task_status).to match('2 done')
expect(subject.task_status).to match('3 unfinished')
end
it 'knows if it has tasks' do
expect(subject.tasks?).to be_true
subject.description = 'Now I have no tasks'
expect(subject.tasks?).to be_false
end
end
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