Commit 3fcd75a9 authored by Valery Sizov's avatar Valery Sizov

Merge CE master into EE master

Conflicts:
	Gemfile
	VERSION
	app/assets/javascripts/project.js.coffee
	app/assets/javascripts/users_select.js.coffee
	app/controllers/projects/services_controller.rb
	app/models/project.rb
	app/views/groups/edit.html.haml
	features/project/service.feature
	features/steps/project/services.rb
	spec/models/concerns/mentionable_spec.rb
parents 3aa59d36 6adc313a
v 7.5.0
- API: Add support for Hipchat (Kevin Houdebert)
- Add time zone configuration on gitlab.yml (Sullivan Senechal)
- Fix LDAP authentication for Git HTTP access
- Fix LDAP config lookup for provider 'ldap'
- Drop all sequences during Postgres database restore
- Project title links to project homepage (Ben Bodenmiller)
- Add Atlassian Bamboo CI service (Drew Blessing)
- Mentioned @user will receive email even if he is not participating in issue or commit
- Session API: Use case-insensitive authentication like in UI (Andrey Krivko)
- Tie up loose ends with annotated tags: API & UI (Sean Edge)
- Return valid json for deleting branch via API (sponsored by O'Reilly Media)
- Expose username in project events API (sponsored by O'Reilly Media)
- Adds comments to commits in the API
v 7.4.3 v 7.4.3
- Fix raw snippets view - Fix raw snippets view
- Fix security issue for member api - Fix security issue for member api
......
...@@ -54,6 +54,8 @@ We welcome merge requests with fixes and improvements to GitLab code, tests, and ...@@ -54,6 +54,8 @@ We welcome merge requests with fixes and improvements to GitLab code, tests, and
Merge requests can be filed either at [gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests) or [github.com](https://github.com/gitlabhq/gitlabhq/pulls). Merge requests can be filed either at [gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests) or [github.com](https://github.com/gitlabhq/gitlabhq/pulls).
If you are new to GitLab development (or web development in general), search for the label `easyfix` ([gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=easyfix), [github](https://github.com/gitlabhq/gitlabhq/labels/easyfix)). Those are issues easy to fix, marked by the GitLab core-team. If you are unsure how to proceed but want to help, mention one of the core-team members to give you a hint.
### Merge request guidelines ### Merge request guidelines
If you can, please submit a merge request with the fix or improvements including tests. If you don't know how to fix the issue but can write a test that exposes the issue we will accept that as well. In general bug fixes that include a regression test are merged quickly while new features without proper tests are least likely to receive timely feedback. The workflow to make a merge request is as follows: If you can, please submit a merge request with the fix or improvements including tests. If you don't know how to fix the issue but can write a test that exposes the issue we will accept that as well. In general bug fixes that include a regression test are merged quickly while new features without proper tests are least likely to receive timely feedback. The workflow to make a merge request is as follows:
...@@ -101,7 +103,11 @@ For examples of feedback on merge requests please look at already [closed merge ...@@ -101,7 +103,11 @@ For examples of feedback on merge requests please look at already [closed merge
1. Contains functionality we think other users will benefit from too 1. Contains functionality we think other users will benefit from too
1. Doesn't add configuration options since they complicate future changes 1. Doesn't add configuration options since they complicate future changes
1. Changes after submitting the merge request should be in separate commits (no squashing). You will be asked to squash when the review is over, before merging. 1. Changes after submitting the merge request should be in separate commits (no squashing). You will be asked to squash when the review is over, before merging.
1. It conforms to the following style guides 1. It conforms to the following style guides.
If your change touches a line that does not follow the style,
modify the entire line to follow it. This prevents linting tools from generating warnings.
Don't touch neighbouring lines. As an exception, automatic mass refactoring modifications
may leave style non-compliant.
## Style guides ## Style guides
......
...@@ -37,7 +37,7 @@ gem "gitlab_git", '7.0.0.rc10' ...@@ -37,7 +37,7 @@ gem "gitlab_git", '7.0.0.rc10'
gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack' gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack'
# LDAP Auth # LDAP Auth
gem 'gitlab_omniauth-ldap', '1.1.0', require: "omniauth-ldap" gem 'gitlab_omniauth-ldap', '1.2.0', require: "omniauth-ldap"
gem 'net-ldap' gem 'net-ldap'
# Git Wiki # Git Wiki
...@@ -145,7 +145,7 @@ gem "gitlab-flowdock-git-hook", "~> 0.4.2" ...@@ -145,7 +145,7 @@ gem "gitlab-flowdock-git-hook", "~> 0.4.2"
gem "gemnasium-gitlab-service", "~> 0.2" gem "gemnasium-gitlab-service", "~> 0.2"
# Slack integration # Slack integration
gem "slack-notifier", "~> 0.3.2" gem "slack-notifier", "~> 1.0.0"
# d3 # d3
gem "d3_rails", "~> 3.1.4" gem "d3_rails", "~> 3.1.4"
......
...@@ -185,11 +185,11 @@ GEM ...@@ -185,11 +185,11 @@ GEM
gitlab-linguist (~> 3.0) gitlab-linguist (~> 3.0)
rugged (~> 0.21.0) rugged (~> 0.21.0)
gitlab_meta (7.0) gitlab_meta (7.0)
gitlab_omniauth-ldap (1.1.0) gitlab_omniauth-ldap (1.2.0)
net-ldap (~> 0.7.0) net-ldap (~> 0.9)
omniauth (~> 1.0) omniauth (~> 1.0)
pyu-ruby-sasl (~> 0.0.3.1) pyu-ruby-sasl (~> 0.0.3.1)
rubyntlm (~> 0.1.1) rubyntlm (~> 0.3)
gollum-lib (3.0.0) gollum-lib (3.0.0)
github-markup (~> 1.1.0) github-markup (~> 1.1.0)
gitlab-grit (~> 2.6.5) gitlab-grit (~> 2.6.5)
...@@ -300,7 +300,7 @@ GEM ...@@ -300,7 +300,7 @@ GEM
multi_xml (0.5.5) multi_xml (0.5.5)
multipart-post (1.2.0) multipart-post (1.2.0)
mysql2 (0.3.16) mysql2 (0.3.16)
net-ldap (0.7.0) net-ldap (0.9.0)
net-scp (1.1.2) net-scp (1.1.2)
net-ssh (>= 2.6.5) net-ssh (>= 2.6.5)
net-ssh (2.8.0) net-ssh (2.8.0)
...@@ -446,7 +446,7 @@ GEM ...@@ -446,7 +446,7 @@ GEM
rspec-expectations (~> 2.14.0) rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0) rspec-mocks (~> 2.14.0)
ruby-progressbar (1.2.0) ruby-progressbar (1.2.0)
rubyntlm (0.1.1) rubyntlm (0.4.0)
rubypants (0.2.0) rubypants (0.2.0)
rugged (0.21.0) rugged (0.21.0)
safe_yaml (0.9.7) safe_yaml (0.9.7)
...@@ -493,7 +493,7 @@ GEM ...@@ -493,7 +493,7 @@ GEM
rack-protection (~> 1.4) rack-protection (~> 1.4)
tilt (~> 1.3, >= 1.3.4) tilt (~> 1.3, >= 1.3.4)
six (0.2.0) six (0.2.0)
slack-notifier (0.3.2) slack-notifier (1.0.0)
slim (2.0.2) slim (2.0.2)
temple (~> 0.6.6) temple (~> 0.6.6)
tilt (>= 1.3.3, < 2.1) tilt (>= 1.3.3, < 2.1)
...@@ -632,7 +632,7 @@ DEPENDENCIES ...@@ -632,7 +632,7 @@ DEPENDENCIES
gitlab_emoji (~> 0.0.1.1) gitlab_emoji (~> 0.0.1.1)
gitlab_git (= 7.0.0.rc10) gitlab_git (= 7.0.0.rc10)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (= 1.1.0) gitlab_omniauth-ldap (= 1.2.0)
gollum-lib (~> 3.0.0) gollum-lib (~> 3.0.0)
gon (~> 5.0.0) gon (~> 5.0.0)
grape (~> 0.6.1) grape (~> 0.6.1)
...@@ -696,7 +696,7 @@ DEPENDENCIES ...@@ -696,7 +696,7 @@ DEPENDENCIES
simplecov simplecov
sinatra sinatra
six six
slack-notifier (~> 0.3.2) slack-notifier (~> 1.0.0)
slim slim
spinach-rails spinach-rails
spring (= 1.1.3) spring (= 1.1.3)
......
...@@ -57,14 +57,8 @@ Since a manual installation is a lot of work and error prone we strongly recomme ...@@ -57,14 +57,8 @@ Since a manual installation is a lot of work and error prone we strongly recomme
## Third-party applications ## Third-party applications
Access GitLab from multiple platforms with applications below. There are a lot of applications and API wrappers for GitLab.
These applications are maintained by contributors, GitLab B.V. does not offer support for them. Find them [on our website](https://about.gitlab.com/applications/).
- [iPhone app](http://gitlabcontrol.com/)
- [Android app](https://play.google.com/store/apps/details?id=com.bd.gitlab&hl=en)
- [Chrome app](https://chrome.google.com/webstore/detail/chrome-gitlab-notifier/eageapgbnjicdjjihgclpclilenjbobi)
- [Command line client](https://github.com/drewblessing/gitlab-cli)
- [Ruby API wrapper](https://github.com/NARKOZ/gitlab)
### New versions ### New versions
......
class Activities class @Activities
constructor: -> constructor: ->
Pager.init 20, true Pager.init 20, true
$(".event_filter_link").bind "click", (event) => $(".event_filter_link").bind "click", (event) =>
...@@ -27,5 +27,3 @@ class Activities ...@@ -27,5 +27,3 @@ class Activities
event_filters.splice index, 1 event_filters.splice index, 1
$.cookie "event_filter", event_filters.join(","), { path: '/' } $.cookie "event_filter", event_filters.join(","), { path: '/' }
@Activities = Activities
class Admin class @Admin
constructor: -> constructor: ->
$('input#user_force_random_password').on 'change', (elem) -> $('input#user_force_random_password').on 'change', (elem) ->
elems = $('#user_password, #user_password_confirmation') elems = $('#user_password, #user_password_confirmation')
...@@ -51,5 +51,3 @@ class Admin ...@@ -51,5 +51,3 @@ class Admin
$('li.group_member').bind 'ajax:success', -> $('li.group_member').bind 'ajax:success', ->
Turbolinks.visit(location.href) Turbolinks.visit(location.href)
@Admin = Admin
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#= require jquery.turbolinks #= require jquery.turbolinks
#= require turbolinks #= require turbolinks
#= require bootstrap #= require bootstrap
#= require password_strength
#= require select2 #= require select2
#= require raphael #= require raphael
#= require g.raphael-min #= require g.raphael-min
...@@ -63,7 +64,7 @@ window.extractLast = (term) -> ...@@ -63,7 +64,7 @@ window.extractLast = (term) ->
return split( term ).pop() return split( term ).pop()
window.rstrip = (val) -> window.rstrip = (val) ->
return val.replace(/\s+$/, '') return if val then val.replace(/\s+$/, '') else val
# Disable button if text field is empty # Disable button if text field is empty
window.disableButtonIfEmptyField = (field_selector, button_selector) -> window.disableButtonIfEmptyField = (field_selector, button_selector) ->
......
class BlobView class @BlobView
constructor: -> constructor: ->
# handle multi-line select # handle multi-line select
handleMultiSelect = (e) -> handleMultiSelect = (e) ->
...@@ -71,6 +71,3 @@ class BlobView ...@@ -71,6 +71,3 @@ class BlobView
# Highlight the correct lines when the hash part of the URL changes # Highlight the correct lines when the hash part of the URL changes
$(window).on("hashchange", highlightBlobLines) $(window).on("hashchange", highlightBlobLines)
@BlobView = BlobView
class Commit class @Commit
constructor: -> constructor: ->
$('.files .diff-file').each -> $('.files .diff-file').each ->
new CommitFile(this) new CommitFile(this)
@Commit = Commit
class CommitFile class @CommitFile
constructor: (file) -> constructor: (file) ->
if $('.image', file).length if $('.image', file).length
new ImageFile(file) new ImageFile(file)
@CommitFile = CommitFile
class ImageFile class @ImageFile
# Width where images must fits in, for 2-up this gets divided by 2 # Width where images must fits in, for 2-up this gets divided by 2
@availWidth = 900 @availWidth = 900
...@@ -124,5 +124,3 @@ class ImageFile ...@@ -124,5 +124,3 @@ class ImageFile
else else
img.on 'load', => img.on 'load', =>
callback.call(this, domImg.naturalWidth, domImg.naturalHeight) callback.call(this, domImg.naturalWidth, domImg.naturalHeight)
@ImageFile = ImageFile
class CommitsList class @CommitsList
@data = @data =
ref: null ref: null
limit: 0 limit: 0
...@@ -53,5 +53,3 @@ class CommitsList ...@@ -53,5 +53,3 @@ class CommitsList
@disable @disable
callback: => callback: =>
this.getOld() this.getOld()
this.CommitsList = CommitsList
class ConfirmDangerModal class @ConfirmDangerModal
constructor: (form, text) -> constructor: (form, text) ->
@form = form @form = form
$('.js-confirm-text').text(text || '') $('.js-confirm-text').text(text || '')
...@@ -16,5 +16,3 @@ class ConfirmDangerModal ...@@ -16,5 +16,3 @@ class ConfirmDangerModal
$('.js-confirm-danger-submit').on 'click', => $('.js-confirm-danger-submit').on 'click', =>
@form.submit() @form.submit()
@ConfirmDangerModal = ConfirmDangerModal
class Dashboard class @Dashboard
constructor: -> constructor: ->
@initSidebarTab() @initSidebarTab()
...@@ -28,6 +28,3 @@ class Dashboard ...@@ -28,6 +28,3 @@ class Dashboard
# show tab from cookie # show tab from cookie
sidebar_filter = $.cookie(key) sidebar_filter = $.cookie(key)
$("#" + sidebar_filter).tab('show') if sidebar_filter $("#" + sidebar_filter).tab('show') if sidebar_filter
@Dashboard = Dashboard
class Diff class @Diff
UNFOLD_COUNT = 20 UNFOLD_COUNT = 20
constructor: -> constructor: ->
$(document).on('click', '.js-unfold', (event) => $(document).on('click', '.js-unfold', (event) =>
...@@ -41,6 +41,3 @@ class Diff ...@@ -41,6 +41,3 @@ class Diff
lines = line.children().slice(0, 2) lines = line.children().slice(0, 2)
line_numbers = ($(l).attr('data-linenumber') for l in lines) line_numbers = ($(l).attr('data-linenumber') for l in lines)
(parseInt(line_number) for line_number in line_numbers) (parseInt(line_number) for line_number in line_numbers)
@Diff = Diff
...@@ -58,15 +58,11 @@ class Dispatcher ...@@ -58,15 +58,11 @@ class Dispatcher
when 'groups:show', 'projects:show' when 'groups:show', 'projects:show'
new Activities() new Activities()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
when 'projects:new'
new Project()
when 'projects:edit'
new Project()
shortcut_handler = new ShortcutsNavigation()
when 'projects:teams:members:index'
new TeamMembers()
when 'groups:members' when 'groups:members'
new GroupMembers() new GroupMembers()
new UsersSelect()
when 'groups:new', 'groups:edit', 'admin:groups:edit'
new GroupAvatar()
when 'projects:tree:show' when 'projects:tree:show'
new TreeView() new TreeView()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
...@@ -79,13 +75,33 @@ class Dispatcher ...@@ -79,13 +75,33 @@ class Dispatcher
# Ensure we don't create a particular shortcut handler here. This is # Ensure we don't create a particular shortcut handler here. This is
# already created, where the network graph is created. # already created, where the network graph is created.
shortcut_handler = true shortcut_handler = true
when 'users:show'
new User()
switch path.first() switch path.first()
when 'admin' then new Admin() when 'admin'
new Admin()
switch path[1]
when 'groups'
new UsersSelect()
when 'projects'
new NamespaceSelect()
when 'dashboard' when 'dashboard'
shortcut_handler = new ShortcutsDashboardNavigation() shortcut_handler = new ShortcutsDashboardNavigation()
when 'profiles'
new Profile()
when 'projects' when 'projects'
new Project()
switch path[1] switch path[1]
when 'edit'
shortcut_handler = new ShortcutsNavigation()
new ProjectNew()
when 'new'
new ProjectNew()
when 'show'
new ProjectShow()
when 'issues', 'merge_requests'
new ProjectUsersSelect()
when 'wikis' when 'wikis'
new Wikis() new Wikis()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
...@@ -94,6 +110,7 @@ class Dispatcher ...@@ -94,6 +110,7 @@ class Dispatcher
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
when 'team_members', 'deploy_keys', 'hooks', 'services', 'protected_branches' when 'team_members', 'deploy_keys', 'hooks', 'services', 'protected_branches'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new UsersSelect()
# If we haven't installed a custom shortcut handler, install the default one # If we haven't installed a custom shortcut handler, install the default one
......
class Flash class @Flash
constructor: (message, type)-> constructor: (message, type)->
flash = $(".flash-container") flash = $(".flash-container")
flash.html("") flash.html("")
...@@ -10,5 +10,3 @@ class Flash ...@@ -10,5 +10,3 @@ class Flash
flash.click -> $(@).fadeOut() flash.click -> $(@).fadeOut()
flash.show() flash.show()
@Flash = Flash
class @GroupAvatar
constructor: ->
$('.js-choose-group-avatar-button').bind "click", ->
form = $(this).closest("form")
form.find(".js-group-avatar-input").click()
$('.js-group-avatar-input').bind "change", ->
form = $(this).closest("form")
filename = $(this).val().replace(/^.*[\\\/]/, '')
form.find(".js-avatar-filename").text(filename)
class GroupMembers class @GroupMembers
constructor: -> constructor: ->
$('li.group_member').bind 'ajax:success', -> $('li.group_member').bind 'ajax:success', ->
$(this).fadeOut() $(this).fadeOut()
@GroupMembers = GroupMembers
$ ->
# avatar
$('.js-choose-group-avatar-button').bind "click", ->
form = $(this).closest("form")
form.find(".js-group-avatar-input").click()
$('.js-group-avatar-input').bind "change", ->
form = $(this).closest("form")
filename = $(this).val().replace(/^.*[\\\/]/, '')
form.find(".js-avatar-filename").text(filename)
class Issue class @Issue
constructor: -> constructor: ->
$('.edit-issue.inline-update input[type="submit"]').hide() $('.edit-issue.inline-update input[type="submit"]').hide()
$(".issue-box .inline-update").on "change", "select", -> $(".issue-box .inline-update").on "change", "select", ->
...@@ -15,5 +15,3 @@ class Issue ...@@ -15,5 +15,3 @@ class Issue
"issue" "issue"
updateTaskState updateTaskState
) )
@Issue = Issue
class Labels class @Labels
constructor: -> constructor: ->
form = $('.label-form') form = $('.label-form')
@setupLabelForm(form) @setupLabelForm(form)
...@@ -31,5 +31,3 @@ class Labels ...@@ -31,5 +31,3 @@ class Labels
# Notify the form, that color has changed # Notify the form, that color has changed
$('.label-form').trigger('keyup') $('.label-form').trigger('keyup')
e.preventDefault() e.preventDefault()
@Labels = Labels
class MergeRequest class @MergeRequest
constructor: (@opts) -> constructor: (@opts) ->
@initContextWidget() @initContextWidget()
this.$el = $('.merge-request') this.$el = $('.merge-request')
...@@ -132,5 +132,3 @@ class MergeRequest ...@@ -132,5 +132,3 @@ class MergeRequest
this.$('.automerge_widget').hide() this.$('.automerge_widget').hide()
this.$('.merge-in-progress').hide() this.$('.merge-in-progress').hide()
this.$('.automerge_widget.already_cannot_be_merged').show() this.$('.automerge_widget.already_cannot_be_merged').show()
this.MergeRequest = MergeRequest
class Milestone class @Milestone
@updateIssue: (li, issue_url, data) -> @updateIssue: (li, issue_url, data) ->
$.ajax $.ajax
type: "PUT" type: "PUT"
...@@ -115,5 +115,3 @@ class Milestone ...@@ -115,5 +115,3 @@ class Milestone
Milestone.updateMergeRequest(ui.item, merge_request_url, data) Milestone.updateMergeRequest(ui.item, merge_request_url, data)
).disableSelection() ).disableSelection()
@Milestone = Milestone
$ -> class @NamespaceSelect
constructor: ->
namespaceFormatResult = (namespace) -> namespaceFormatResult = (namespace) ->
markup = "<div class='namespace-result'>" markup = "<div class='namespace-result'>"
markup += "<span class='namespace-kind'>" + namespace.kind + "</span>" markup += "<span class='namespace-kind'>" + namespace.kind + "</span>"
......
class Notes class @Notes
@interval: null @interval: null
constructor: (notes_url, note_ids, last_fetched_at) -> constructor: (notes_url, note_ids, last_fetched_at) ->
...@@ -514,7 +514,3 @@ class Notes ...@@ -514,7 +514,3 @@ class Notes
else else
form.find('.js-note-target-reopen').text('Reopen') form.find('.js-note-target-reopen').text('Reopen')
form.find('.js-note-target-close').text('Close') form.find('.js-note-target-close').text('Close')
@Notes = Notes
class NotesVotes class @NotesVotes
updateVotes: -> updateVotes: ->
votes = $("#votes .votes") votes = $("#votes .votes")
notes = $("#notes-list .note .vote") notes = $("#notes-list .note .vote")
...@@ -18,5 +18,3 @@ class NotesVotes ...@@ -18,5 +18,3 @@ class NotesVotes
# replace vote numbers # replace vote numbers
votes.find(".upvotes").text votes.find(".upvotes").text().replace(/\d+/, upvotes) votes.find(".upvotes").text votes.find(".upvotes").text().replace(/\d+/, upvotes)
votes.find(".downvotes").text votes.find(".downvotes").text().replace(/\d+/, downvotes) votes.find(".downvotes").text votes.find(".downvotes").text().replace(/\d+/, downvotes)
@NotesVotes = NotesVotes
#= require pwstrength-bootstrap-1.2.2
overwritten_messages =
wordSimilarToUsername: "Your password should not contain your username"
overwritten_rules =
wordSequences: false
options =
showProgressBar: false
showVerdicts: false
showPopover: true
showErrors: true
showStatus: true
errorMessages: overwritten_messages
$(document).ready ->
profileOptions = {}
profileOptions.ui = options
profileOptions.rules =
activated: overwritten_rules
deviseOptions = {}
deviseOptions.common =
usernameField: "#user_username"
deviseOptions.ui = options
deviseOptions.rules =
activated: overwritten_rules
$("#user_password_profile").pwstrength profileOptions
$("#user_password_sign_up").pwstrength deviseOptions
$("#user_password_recover").pwstrength deviseOptions
$ -> class @Profile
constructor: ->
$('.edit_user .application-theme input, .edit_user .code-preview-theme input').click -> $('.edit_user .application-theme input, .edit_user .code-preview-theme input').click ->
# Submit the form # Submit the form
$('.edit_user').submit() $('.edit_user').submit()
...@@ -26,5 +27,3 @@ $ -> ...@@ -26,5 +27,3 @@ $ ->
form = $(this).closest("form") form = $(this).closest("form")
filename = $(this).val().replace(/^.*[\\\/]/, '') filename = $(this).val().replace(/^.*[\\\/]/, '')
form.find(".js-avatar-filename").text(filename) form.find(".js-avatar-filename").text(filename)
$('.profile-groups-avatars').tooltip("placement": "top")
class Project class @Project
constructor: -> constructor: ->
$('.project-edit-container').on 'ajax:before', =>
$('.project-edit-container').hide()
$('.save-project-loader').show()
@initEvents()
initEvents: ->
disableButtonIfEmptyField '#project_name', '.project-submit'
$('#project_issues_enabled').change ->
if ($(this).is(':checked') == true)
$('#project_issues_tracker').removeAttr('disabled')
else
$('#project_issues_tracker').attr('disabled', 'disabled')
$('#project_issues_tracker').change()
$('#project_issues_tracker').change ->
if ($(this).val() == gon.default_issues_tracker || $(this).is(':disabled'))
$('#project_issues_tracker_id').attr('disabled', 'disabled')
else
$('#project_issues_tracker_id').removeAttr('disabled')
$('#project_merge_requests_enabled').change ->
if ($(this).is(':checked') == true)
$('#project_merge_requests_template').removeAttr('disabled')
else
$('#project_merge_requests_template').attr('disabled', 'disabled')
$('#project_merge_requests_template').change()
@Project = Project
$ ->
# Git clone panel switcher # Git clone panel switcher
scope = $ '.git-clone-holder' scope = $ '.git-clone-holder'
if scope.length > 0 if scope.length > 0
...@@ -54,17 +18,3 @@ $ -> ...@@ -54,17 +18,3 @@ $ ->
$.cookie('hide_no_ssh_message', 'false', { path: path }) $.cookie('hide_no_ssh_message', 'false', { path: path })
$(@).parents('.no-ssh-key-message').hide() $(@).parents('.no-ssh-key-message').hide()
e.preventDefault() e.preventDefault()
\ No newline at end of file
$('.project-home-panel .star').on 'ajax:success', (e, data, status, xhr) ->
$(@).toggleClass('on').find('.count').html(data.star_count)
.on 'ajax:error', (e, xhr, status, error) ->
new Flash('Star toggle failed. Try again later.', 'alert')
$("a[data-toggle='tab']").on "shown.bs.tab", (e) ->
$.cookie "default_view", $(e.target).attr("href")
defaultView = $.cookie("default_view")
if defaultView
$("a[href=" + defaultView + "]").tab "show"
else
$("a[data-toggle='tab']:first").tab "show"
class ProjectImport class @ProjectImport
constructor: -> constructor: ->
setTimeout -> setTimeout ->
Turbolinks.visit(location.href) Turbolinks.visit(location.href)
, 5000 , 5000
@ProjectImport = ProjectImport
class @ProjectNew
constructor: ->
$('.project-edit-container').on 'ajax:before', =>
$('.project-edit-container').hide()
$('.save-project-loader').show()
@initEvents()
initEvents: ->
disableButtonIfEmptyField '#project_name', '.project-submit'
$('#project_issues_enabled').change ->
if ($(this).is(':checked') == true)
$('#project_issues_tracker').removeAttr('disabled')
else
$('#project_issues_tracker').attr('disabled', 'disabled')
$('#project_issues_tracker').change()
$('#project_issues_tracker').change ->
if ($(this).val() == gon.default_issues_tracker || $(this).is(':disabled'))
$('#project_issues_tracker_id').attr('disabled', 'disabled')
else
$('#project_issues_tracker_id').removeAttr('disabled')
class @ProjectShow
constructor: ->
$('.project-home-panel .star').on 'ajax:success', (e, data, status, xhr) ->
$(@).toggleClass('on').find('.count').html(data.star_count)
.on 'ajax:error', (e, xhr, status, error) ->
new Flash('Star toggle failed. Try again later.', 'alert')
$("a[data-toggle='tab']").on "shown.bs.tab", (e) ->
$.cookie "default_view", $(e.target).attr("href")
defaultView = $.cookie("default_view")
if defaultView
$("a[href=" + defaultView + "]").tab "show"
else
$("a[data-toggle='tab']:first").tab "show"
@projectUsersSelect = class @ProjectUsersSelect
init: -> constructor: ->
$('.ajax-project-users-select').each (i, select) -> $('.ajax-project-users-select').each (i, select) =>
project_id = $(select).data('project-id') || $('body').data('project-id') project_id = $(select).data('project-id') || $('body').data('project-id')
$(select).select2 $(select).select2
...@@ -28,14 +28,16 @@ ...@@ -28,14 +28,16 @@
Api.user(id, callback) Api.user(id, callback)
formatResult: projectUsersSelect.projectUserFormatResult formatResult: (args...) =>
formatSelection: projectUsersSelect.projectUserFormatSelection @formatResult(args...)
formatSelection: (args...) =>
@formatSelection(args...)
dropdownCssClass: "ajax-project-users-dropdown" dropdownCssClass: "ajax-project-users-dropdown"
dropdownAutoWidth: true dropdownAutoWidth: true
escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
m m
projectUserFormatResult: (user) -> formatResult: (user) ->
if user.avatar_url if user.avatar_url
avatar = user.avatar_url avatar = user.avatar_url
else else
...@@ -52,8 +54,5 @@ ...@@ -52,8 +54,5 @@
<div class='user-username'>#{user.username}</div> <div class='user-username'>#{user.username}</div>
</div>" </div>"
projectUserFormatSelection: (user) -> formatSelection: (user) ->
user.name user.name
$ ->
projectUsersSelect.init()
class SearchAutocomplete class @SearchAutocomplete
constructor: (search_autocomplete_path, project_id, project_ref) -> constructor: (search_autocomplete_path, project_id, project_ref) ->
project_id = '' unless project_id project_id = '' unless project_id
project_ref = '' unless project_ref project_ref = '' unless project_ref
...@@ -9,5 +9,3 @@ class SearchAutocomplete ...@@ -9,5 +9,3 @@ class SearchAutocomplete
minLength: 1 minLength: 1
select: (event, ui) -> select: (event, ui) ->
location.href = ui.item.url location.href = ui.item.url
@SearchAutocomplete = SearchAutocomplete
class window.StatGraph class @StatGraph
@log: {} @log: {}
@get_log: -> @get_log: ->
@log @log
......
class window.ContributorsStatGraph class @ContributorsStatGraph
init: (log) -> init: (log) ->
@parsed_log = ContributorsStatGraphUtil.parse_log(log) @parsed_log = ContributorsStatGraphUtil.parse_log(log)
@set_current_field("commits") @set_current_field("commits")
......
class window.ContributorsGraph class @ContributorsGraph
MARGIN: MARGIN:
top: 20 top: 20
right: 20 right: 20
...@@ -44,7 +44,7 @@ class window.ContributorsGraph ...@@ -44,7 +44,7 @@ class window.ContributorsGraph
set_data: (data) -> set_data: (data) ->
@data = data @data = data
class window.ContributorsMasterGraph extends ContributorsGraph class @ContributorsMasterGraph extends ContributorsGraph
constructor: (@data) -> constructor: (@data) ->
@width = $('.container').width() - 70 @width = $('.container').width() - 70
@height = 200 @height = 200
...@@ -117,7 +117,7 @@ class window.ContributorsMasterGraph extends ContributorsGraph ...@@ -117,7 +117,7 @@ class window.ContributorsMasterGraph extends ContributorsGraph
@svg.select("path").attr("d", @area) @svg.select("path").attr("d", @area)
@svg.select(".y.axis").call(@y_axis) @svg.select(".y.axis").call(@y_axis)
class window.ContributorsAuthorGraph extends ContributorsGraph class @ContributorsAuthorGraph extends ContributorsGraph
constructor: (@data) -> constructor: (@data) ->
@width = $('.container').width()/2 - 100 @width = $('.container').width()/2 - 100
@height = 200 @height = 200
......
class TeamMembers
constructor: ->
$('.team-members .project-access-select').on "change", ->
$(this.form).submit()
@TeamMembers = TeamMembers
class TreeView class @TreeView
constructor: -> constructor: ->
@initKeyNav() @initKeyNav()
...@@ -39,5 +39,3 @@ class TreeView ...@@ -39,5 +39,3 @@ class TreeView
else if e.which is 13 else if e.which is 13
path = $('.tree-item.selected .tree-item-file-name a').attr('href') path = $('.tree-item.selected .tree-item-file-name a').attr('href')
Turbolinks.visit(path) Turbolinks.visit(path)
@TreeView = TreeView
class @User
constructor: ->
$('.profile-groups-avatars').tooltip("placement": "top")
$ -> class @UsersSelect
userFormatResult = (user) -> constructor: ->
if user.avatar_url $('.ajax-users-select').each (i, select) =>
avatar = user.avatar_url
else
avatar = gon.default_avatar_url
"<div class='user-result'>
<div class='user-image'><img class='avatar s24' src='#{avatar}'></div>
<div class='user-name'>#{user.name}</div>
<div class='user-username'>#{user.username}</div>
</div>"
userFormatSelection = (user) ->
user.name
$('.ajax-users-select').each (i, select) ->
skip_ldap = $(select).hasClass('skip_ldap') skip_ldap = $(select).hasClass('skip_ldap')
$(select).select2 $(select).select2
...@@ -32,8 +18,25 @@ $ -> ...@@ -32,8 +18,25 @@ $ ->
Api.user(id, callback) Api.user(id, callback)
formatResult: userFormatResult formatResult: (args...) =>
formatSelection: userFormatSelection @formatResult(args...)
formatSelection: (args...) =>
@formatSelection(args...)
dropdownCssClass: "ajax-users-dropdown" dropdownCssClass: "ajax-users-dropdown"
escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
m m
formatResult: (user) ->
if user.avatar_url
avatar = user.avatar_url
else
avatar = gon.default_avatar_url
"<div class='user-result'>
<div class='user-image'><img class='avatar s24' src='#{avatar}'></div>
<div class='user-name'>#{user.name}</div>
<div class='user-username'>#{user.username}</div>
</div>"
formatSelection: (user) ->
user.name
\ No newline at end of file
class Wikis class @Wikis
constructor: -> constructor: ->
$('.build-new-wiki').bind "click", -> $('.build-new-wiki').bind "click", ->
field = $('#new_wiki_path') field = $('#new_wiki_path')
...@@ -7,6 +7,3 @@ class Wikis ...@@ -7,6 +7,3 @@ class Wikis
if(slug.length > 0) if(slug.length > 0)
location.href = path + "/" + slug location.href = path + "/" + slug
@Wikis = Wikis
// Details // Details
//-------- //--------
.js-details-container .content { display: none; } .js-details-container {
.js-details-container .content.hide { display: block; } .content {
.js-details-container.open .content { display: block; } display: none;
.js-details-container.open .content.hide { display: none; } &.hide { display: block; }
}
&.open .content {
display: block;
&.hide { display: none; }
}
}
// Toggle between two states. // Toggle between two states.
.js-toggler-container .turn-on { display: block; } .js-toggler-container {
.js-toggler-container .turn-off { display: none; } .turn-on { display: block; }
.js-toggler-container.on .turn-on { display: none; } .turn-off { display: none; }
.js-toggler-container.on .turn-off { display: block; } &.on {
.turn-on { display: none; }
.turn-off { display: block; }
}
}
...@@ -186,3 +186,11 @@ ...@@ -186,3 +186,11 @@
} }
} }
} }
.readme-holder .wiki, .note-body, .wiki-holder {
.white {
.highlight, pre, .hljs {
background: #F9F9F9;
}
}
}
/** Typo **/ /** Typo **/
$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace; $monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
$regular_font: "Helvetica Neue", Helvetica, Arial, sans-serif; $regular_font: "Helvetica Neue", Helvetica, Arial, sans-serif;
...@@ -111,3 +111,20 @@ ...@@ -111,3 +111,20 @@
height: 50px; height: 50px;
} }
} }
//CSS for password-strength indicator
#password-strength {
margin-bottom: 0;
}
.has-success input {
background-color: #D6F1D7 !important;
}
.has-error input {
background-color: #F3CECE !important;
}
.has-warning input {
background-color: #FFE9A4 !important;
}
class Admin::BackgroundJobsController < Admin::ApplicationController class Admin::BackgroundJobsController < Admin::ApplicationController
def show def show
ps_output, _ = Gitlab::Popen.popen(%W(ps -U #{Settings.gitlab.user} -o pid,pcpu,pmem,stat,start,command)) ps_output, _ = Gitlab::Popen.popen(%W(ps -U #{Gitlab.config.gitlab.user} -o pid,pcpu,pmem,stat,start,command))
@sidekiq_processes = ps_output.split("\n").grep(/sidekiq/) @sidekiq_processes = ps_output.split("\n").grep(/sidekiq/)
end end
end end
...@@ -31,17 +31,11 @@ class Admin::ProjectsController < Admin::ApplicationController ...@@ -31,17 +31,11 @@ class Admin::ProjectsController < Admin::ApplicationController
protected protected
def project def project
id = params[:project_id] || params[:id] @project = Project.find_with_namespace(params[:id])
@project = Project.find_with_namespace(id)
@project || render_404 @project || render_404
end end
def group def group
@group ||= project.group @group ||= @project.group
end
def repository
@repository ||= project.repository
end end
end end
...@@ -5,9 +5,7 @@ class ApplicationController < ActionController::Base ...@@ -5,9 +5,7 @@ class ApplicationController < ActionController::Base
before_filter :authenticate_user! before_filter :authenticate_user!
before_filter :reject_blocked! before_filter :reject_blocked!
before_filter :check_password_expiration before_filter :check_password_expiration
before_filter :add_abilities
before_filter :ldap_security_check before_filter :ldap_security_check
before_filter :dev_tools if Rails.env == 'development'
before_filter :default_headers before_filter :default_headers
before_filter :add_gon_variables before_filter :add_gon_variables
before_filter :configure_permitted_parameters, if: :devise_controller? before_filter :configure_permitted_parameters, if: :devise_controller?
...@@ -73,7 +71,7 @@ class ApplicationController < ActionController::Base ...@@ -73,7 +71,7 @@ class ApplicationController < ActionController::Base
end end
def abilities def abilities
@abilities ||= Six.new Ability.abilities
end end
def can?(object, action, subject) def can?(object, action, subject)
...@@ -81,6 +79,7 @@ class ApplicationController < ActionController::Base ...@@ -81,6 +79,7 @@ class ApplicationController < ActionController::Base
end end
def project def project
unless @project
id = params[:project_id] || params[:id] id = params[:project_id] || params[:id]
# Redirect from # Redirect from
...@@ -104,6 +103,8 @@ class ApplicationController < ActionController::Base ...@@ -104,6 +103,8 @@ class ApplicationController < ActionController::Base
render_404 and return render_404 and return
end end
end end
@project
end
def repository def repository
@repository ||= project.repository @repository ||= project.repository
...@@ -111,22 +112,10 @@ class ApplicationController < ActionController::Base ...@@ -111,22 +112,10 @@ class ApplicationController < ActionController::Base
nil nil
end end
def add_abilities
abilities << Ability
end
def authorize_project!(action) def authorize_project!(action)
return access_denied! unless can?(current_user, action, project) return access_denied! unless can?(current_user, action, project)
end end
def authorize_code_access!
return access_denied! unless can?(current_user, :download_code, project)
end
def authorize_push!
return access_denied! unless can?(current_user, :push_code, project)
end
def authorize_labels! def authorize_labels!
# Labels should be accessible for issues and/or merge requests # Labels should be accessible for issues and/or merge requests
authorize_read_issue! || authorize_read_merge_request! authorize_read_issue! || authorize_read_merge_request!
...@@ -170,9 +159,6 @@ class ApplicationController < ActionController::Base ...@@ -170,9 +159,6 @@ class ApplicationController < ActionController::Base
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT" response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
end end
def dev_tools
end
def default_headers def default_headers
headers['X-Frame-Options'] = 'DENY' headers['X-Frame-Options'] = 'DENY'
headers['X-XSS-Protection'] = '1; mode=block' headers['X-XSS-Protection'] = '1; mode=block'
......
class Explore::GroupsController < ApplicationController class Explore::GroupsController < ApplicationController
skip_before_filter :authenticate_user!, skip_before_filter :authenticate_user!,
:reject_blocked, :set_current_user_for_observers, :reject_blocked, :set_current_user_for_observers
:add_abilities
layout "explore" layout "explore"
......
class Explore::ProjectsController < ApplicationController class Explore::ProjectsController < ApplicationController
skip_before_filter :authenticate_user!, skip_before_filter :authenticate_user!,
:reject_blocked, :reject_blocked
:add_abilities
layout 'explore' layout 'explore'
......
class Projects::BaseTreeController < Projects::ApplicationController class Projects::BaseTreeController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
before_filter :authorize_read_project! before_filter :authorize_download_code!
before_filter :authorize_code_access!
before_filter :require_non_empty_project before_filter :require_non_empty_project
end end
...@@ -3,8 +3,7 @@ class Projects::BlameController < Projects::ApplicationController ...@@ -3,8 +3,7 @@ class Projects::BlameController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
# Authorize # Authorize
before_filter :authorize_read_project! before_filter :authorize_download_code!
before_filter :authorize_code_access!
before_filter :require_non_empty_project before_filter :require_non_empty_project
def show def show
......
...@@ -3,10 +3,9 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -3,10 +3,9 @@ class Projects::BlobController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
# Authorize # Authorize
before_filter :authorize_read_project! before_filter :authorize_download_code!
before_filter :authorize_code_access!
before_filter :require_non_empty_project before_filter :require_non_empty_project
before_filter :authorize_push!, only: [:destroy] before_filter :authorize_push_code!, only: [:destroy]
before_filter :blob before_filter :blob
......
class Projects::BranchesController < Projects::ApplicationController class Projects::BranchesController < Projects::ApplicationController
# Authorize # Authorize
before_filter :authorize_read_project!
before_filter :require_non_empty_project before_filter :require_non_empty_project
before_filter :authorize_code_access! before_filter :authorize_download_code!
before_filter :authorize_push!, only: [:create, :destroy] before_filter :authorize_push_code!, only: [:create, :destroy]
def index def index
@sort = params[:sort] || 'name' @sort = params[:sort] || 'name'
...@@ -19,6 +18,7 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -19,6 +18,7 @@ class Projects::BranchesController < Projects::ApplicationController
def create def create
result = CreateBranchService.new(project, current_user). result = CreateBranchService.new(project, current_user).
execute(params[:branch_name], params[:ref]) execute(params[:branch_name], params[:ref])
if result[:status] == :success if result[:status] == :success
@branch = result[:branch] @branch = result[:branch]
redirect_to project_tree_path(@project, @branch.name) redirect_to project_tree_path(@project, @branch.name)
......
...@@ -3,20 +3,19 @@ ...@@ -3,20 +3,19 @@
# Not to be confused with CommitsController, plural. # Not to be confused with CommitsController, plural.
class Projects::CommitController < Projects::ApplicationController class Projects::CommitController < Projects::ApplicationController
# Authorize # Authorize
before_filter :authorize_read_project! before_filter :authorize_download_code!
before_filter :authorize_code_access!
before_filter :require_non_empty_project before_filter :require_non_empty_project
before_filter :commit before_filter :commit
def show def show
return git_not_found! unless @commit return git_not_found! unless @commit
@line_notes = project.notes.for_commit_id(commit.id).inline @line_notes = @project.notes.for_commit_id(commit.id).inline
@branches = project.repository.branch_names_contains(commit.id) @branches = @project.repository.branch_names_contains(commit.id)
@diffs = @commit.diffs @diffs = @commit.diffs
@note = project.build_commit_note(commit) @note = @project.build_commit_note(commit)
@notes_count = project.notes.for_commit_id(commit.id).count @notes_count = @project.notes.for_commit_id(commit.id).count
@notes = project.notes.for_commit_id(@commit.id).not_inline.fresh @notes = @project.notes.for_commit_id(@commit.id).not_inline.fresh
@noteable = @commit @noteable = @commit
@comments_allowed = @reply_allowed = true @comments_allowed = @reply_allowed = true
@comments_target = { @comments_target = {
...@@ -32,6 +31,6 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -32,6 +31,6 @@ class Projects::CommitController < Projects::ApplicationController
end end
def commit def commit
@commit ||= project.repository.commit(params[:id]) @commit ||= @project.repository.commit(params[:id])
end end
end end
...@@ -4,8 +4,7 @@ class Projects::CommitsController < Projects::ApplicationController ...@@ -4,8 +4,7 @@ class Projects::CommitsController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
# Authorize # Authorize
before_filter :authorize_read_project! before_filter :authorize_download_code!
before_filter :authorize_code_access!
before_filter :require_non_empty_project before_filter :require_non_empty_project
def show def show
......
class Projects::CompareController < Projects::ApplicationController class Projects::CompareController < Projects::ApplicationController
# Authorize # Authorize
before_filter :authorize_read_project! before_filter :authorize_download_code!
before_filter :authorize_code_access!
before_filter :require_non_empty_project before_filter :require_non_empty_project
def index def index
......
...@@ -42,7 +42,7 @@ class Projects::DeployKeysController < Projects::ApplicationController ...@@ -42,7 +42,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
end end
def enable def enable
project.deploy_keys << available_keys.find(params[:id]) @project.deploy_keys << available_keys.find(params[:id])
redirect_to project_deploy_keys_path(@project) redirect_to project_deploy_keys_path(@project)
end end
......
class Projects::EditTreeController < Projects::BaseTreeController class Projects::EditTreeController < Projects::BaseTreeController
before_filter :require_branch_head before_filter :require_branch_head
before_filter :blob before_filter :blob
before_filter :authorize_push! before_filter :authorize_push_code!
before_filter :from_merge_request before_filter :from_merge_request
before_filter :after_edit_path before_filter :after_edit_path
......
class Projects::GraphsController < Projects::ApplicationController class Projects::GraphsController < Projects::ApplicationController
# Authorize # Authorize
before_filter :authorize_read_project! before_filter :authorize_download_code!
before_filter :authorize_code_access!
before_filter :require_non_empty_project before_filter :require_non_empty_project
def show def show
......
...@@ -3,8 +3,7 @@ class Projects::NetworkController < Projects::ApplicationController ...@@ -3,8 +3,7 @@ class Projects::NetworkController < Projects::ApplicationController
include ApplicationHelper include ApplicationHelper
# Authorize # Authorize
before_filter :authorize_read_project! before_filter :authorize_download_code!
before_filter :authorize_code_access!
before_filter :require_non_empty_project before_filter :require_non_empty_project
def show def show
......
class Projects::NewTreeController < Projects::BaseTreeController class Projects::NewTreeController < Projects::BaseTreeController
before_filter :require_branch_head before_filter :require_branch_head
before_filter :authorize_push! before_filter :authorize_push_code!
def show def show
end end
......
...@@ -3,8 +3,7 @@ class Projects::RawController < Projects::ApplicationController ...@@ -3,8 +3,7 @@ class Projects::RawController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
# Authorize # Authorize
before_filter :authorize_read_project! before_filter :authorize_download_code!
before_filter :authorize_code_access!
before_filter :require_non_empty_project before_filter :require_non_empty_project
def show def show
......
...@@ -2,8 +2,7 @@ class Projects::RefsController < Projects::ApplicationController ...@@ -2,8 +2,7 @@ class Projects::RefsController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
# Authorize # Authorize
before_filter :authorize_read_project! before_filter :authorize_download_code!
before_filter :authorize_code_access!
before_filter :require_non_empty_project before_filter :require_non_empty_project
def switch def switch
......
class Projects::RepositoriesController < Projects::ApplicationController class Projects::RepositoriesController < Projects::ApplicationController
# Authorize # Authorize
before_filter :authorize_read_project! before_filter :authorize_download_code!
before_filter :authorize_code_access!
before_filter :require_non_empty_project before_filter :require_non_empty_project
def archive def archive
......
...@@ -40,8 +40,9 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -40,8 +40,9 @@ 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, :webhook, :username, :password, :api_version, :room, :recipients, :project_url, :webhook,
:user_key, :device, :priority, :sound :user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
:build_key
) )
end end
end end
class Projects::TagsController < Projects::ApplicationController class Projects::TagsController < Projects::ApplicationController
# Authorize # Authorize
before_filter :authorize_read_project!
before_filter :require_non_empty_project before_filter :require_non_empty_project
before_filter :authorize_download_code!
before_filter :authorize_code_access! before_filter :authorize_push_code!, only: [:create]
before_filter :authorize_push!, only: [:create]
before_filter :authorize_admin_project!, only: [:destroy] before_filter :authorize_admin_project!, only: [:destroy]
def index def index
......
...@@ -11,7 +11,7 @@ class Projects::TeamMembersController < Projects::ApplicationController ...@@ -11,7 +11,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end end
def new def new
@user_project_relation = project.project_members.new @user_project_relation = @project.project_members.new
end end
def create def create
...@@ -27,7 +27,7 @@ class Projects::TeamMembersController < Projects::ApplicationController ...@@ -27,7 +27,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end end
def update def update
@user_project_relation = project.project_members.find_by(user_id: member) @user_project_relation = @project.project_members.find_by(user_id: member)
@user_project_relation.update_attributes(member_params) @user_project_relation.update_attributes(member_params)
unless @user_project_relation.valid? unless @user_project_relation.valid?
...@@ -37,7 +37,7 @@ class Projects::TeamMembersController < Projects::ApplicationController ...@@ -37,7 +37,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end end
def destroy def destroy
@user_project_relation = project.project_members.find_by(user_id: member) @user_project_relation = @project.project_members.find_by(user_id: member)
@user_project_relation.destroy @user_project_relation.destroy
respond_to do |format| respond_to do |format|
...@@ -47,7 +47,7 @@ class Projects::TeamMembersController < Projects::ApplicationController ...@@ -47,7 +47,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end end
def leave def leave
project.project_members.find_by(user_id: current_user).destroy @project.project_members.find_by(user_id: current_user).destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to :back } format.html { redirect_to :back }
......
...@@ -4,9 +4,7 @@ class ProjectsController < ApplicationController ...@@ -4,9 +4,7 @@ class ProjectsController < ApplicationController
before_filter :repository, except: [:new, :create] before_filter :repository, except: [:new, :create]
# Authorize # Authorize
before_filter :authorize_read_project!, except: [:index, :new, :create]
before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive, :retry_import] before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive, :retry_import]
before_filter :require_non_empty_project, only: [:blob, :tree, :graph]
layout 'navless', only: [:new, :create, :fork] layout 'navless', only: [:new, :create, :fork]
before_filter :set_title, only: [:new, :create] before_filter :set_title, only: [:new, :create]
...@@ -53,8 +51,6 @@ class ProjectsController < ApplicationController ...@@ -53,8 +51,6 @@ class ProjectsController < ApplicationController
return return
end end
return authenticate_user! unless @project.public? || current_user
limit = (params[:limit] || 20).to_i limit = (params[:limit] || 20).to_i
@events = @project.events.recent @events = @project.events.recent
@events = event_filter.apply_filter(@events) @events = event_filter.apply_filter(@events)
...@@ -76,7 +72,7 @@ class ProjectsController < ApplicationController ...@@ -76,7 +72,7 @@ class ProjectsController < ApplicationController
end end
def import def import
if project.import_finished? if @project.import_finished?
redirect_to @project redirect_to @project
return return
end end
...@@ -98,7 +94,7 @@ class ProjectsController < ApplicationController ...@@ -98,7 +94,7 @@ class ProjectsController < ApplicationController
end end
def destroy def destroy
return access_denied! unless can?(current_user, :remove_project, project) return access_denied! unless can?(current_user, :remove_project, @project)
::Projects::DestroyService.new(@project, current_user, {}).execute ::Projects::DestroyService.new(@project, current_user, {}).execute
...@@ -148,8 +144,8 @@ class ProjectsController < ApplicationController ...@@ -148,8 +144,8 @@ class ProjectsController < ApplicationController
end end
def archive def archive
return access_denied! unless can?(current_user, :archive_project, project) return access_denied! unless can?(current_user, :archive_project, @project)
project.archive! @project.archive!
respond_to do |format| respond_to do |format|
format.html { redirect_to @project } format.html { redirect_to @project }
...@@ -157,8 +153,8 @@ class ProjectsController < ApplicationController ...@@ -157,8 +153,8 @@ class ProjectsController < ApplicationController
end end
def unarchive def unarchive
return access_denied! unless can?(current_user, :archive_project, project) return access_denied! unless can?(current_user, :archive_project, @project)
project.unarchive! @project.unarchive!
respond_to do |format| respond_to do |format|
format.html { redirect_to @project } format.html { redirect_to @project }
......
...@@ -48,7 +48,7 @@ class IssuableFinder ...@@ -48,7 +48,7 @@ class IssuableFinder
else else
[] []
end end
elsif current_user && params[:authorized_only].presence elsif current_user && params[:authorized_only].presence && !current_user_related?
klass.of_projects(current_user.authorized_projects).references(:project) klass.of_projects(current_user.authorized_projects).references(:project)
else else
klass.of_projects(ProjectsFinder.new.execute(current_user)).references(:project) klass.of_projects(ProjectsFinder.new.execute(current_user)).references(:project)
...@@ -142,4 +142,8 @@ class IssuableFinder ...@@ -142,4 +142,8 @@ class IssuableFinder
def project def project
Project.where(id: params[:project_id]).first if params[:project_id].present? Project.where(id: params[:project_id]).first if params[:project_id].present?
end end
def current_user_related?
params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
end
end end
module EmailsHelper
# Google Actions
# https://developers.google.com/gmail/markup/reference/go-to-action
def email_action(url)
name = action_title(url)
if name
data = {
"@context" => "http://schema.org",
"@type" => "EmailMessage",
"action" => {
"@type" => "ViewAction",
"name" => name,
"url" => url,
}
}
content_tag :script, type: 'application/ld+json' do
data.to_json.html_safe
end
end
end
def action_title(url)
return unless url
["merge_requests", "issues", "commit"].each do |action|
if url.split("/").include?(action)
return "View #{action.humanize.singularize}"
end
end
end
end
...@@ -62,6 +62,19 @@ module IssuesHelper ...@@ -62,6 +62,19 @@ module IssuesHelper
'' ''
end end
def issue_timestamp(issue)
# Shows the created at time and the updated at time if different
ts = "#{time_ago_with_tooltip(issue.created_at, 'bottom', 'note_created_ago')}"
if issue.updated_at != issue.created_at
ts << capture_haml do
haml_tag :small do
haml_concat " (Edited #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_edited_ago')})"
end
end
end
ts.html_safe
end
# Checks if issues_tracker setting exists in gitlab.yml # Checks if issues_tracker setting exists in gitlab.yml
def external_issues_tracker_enabled? def external_issues_tracker_enabled?
Gitlab.config.issues_tracker && Gitlab.config.issues_tracker.values.any? Gitlab.config.issues_tracker && Gitlab.config.issues_tracker.values.any?
......
...@@ -42,12 +42,12 @@ module ProjectsHelper ...@@ -42,12 +42,12 @@ module ProjectsHelper
def project_title(project) def project_title(project)
if project.group if project.group
content_tag :span do content_tag :span do
link_to(simple_sanitize(project.group.name), group_path(project.group)) + " / " + project.name link_to(simple_sanitize(project.group.name), group_path(project.group)) + ' / ' + link_to(simple_sanitize(project.name), project_path(project))
end end
else else
owner = project.namespace.owner owner = project.namespace.owner
content_tag :span do content_tag :span do
link_to(simple_sanitize(owner.name), user_path(owner)) + " / " + project.name link_to(simple_sanitize(owner.name), user_path(owner)) + ' / ' + link_to(simple_sanitize(project.name), project_path(project))
end end
end end
end end
......
...@@ -66,7 +66,7 @@ module TreeHelper ...@@ -66,7 +66,7 @@ module TreeHelper
def tree_breadcrumbs(tree, max_links = 2) def tree_breadcrumbs(tree, max_links = 2)
if @path.present? if @path.present?
part_path = "" part_path = ""
parts = @path.split("\/") parts = @path.split('/')
yield('..', nil) if parts.count > max_links yield('..', nil) if parts.count > max_links
......
...@@ -12,6 +12,7 @@ class Notify < ActionMailer::Base ...@@ -12,6 +12,7 @@ class Notify < ActionMailer::Base
add_template_helper ApplicationHelper add_template_helper ApplicationHelper
add_template_helper GitlabMarkdownHelper add_template_helper GitlabMarkdownHelper
add_template_helper MergeRequestsHelper add_template_helper MergeRequestsHelper
add_template_helper EmailsHelper
default_url_options[:host] = Gitlab.config.gitlab.host default_url_options[:host] = Gitlab.config.gitlab.host
default_url_options[:protocol] = Gitlab.config.gitlab.protocol default_url_options[:protocol] = Gitlab.config.gitlab.protocol
......
...@@ -262,5 +262,13 @@ class Ability ...@@ -262,5 +262,13 @@ class Ability
end end
rules rules
end end
def abilities
@abilities ||= begin
abilities = Six.new
abilities << self
abilities
end
end
end end
end end
...@@ -131,9 +131,10 @@ module Issuable ...@@ -131,9 +131,10 @@ module Issuable
users.concat(mentions.reduce([], :|)).uniq users.concat(mentions.reduce([], :|)).uniq
end end
def to_hook_data def to_hook_data(user)
{ {
object_kind: self.class.name.underscore, object_kind: self.class.name.underscore,
user: user.hook_attrs,
object_attributes: hook_attrs object_attributes: hook_attrs
} }
end end
......
...@@ -51,12 +51,8 @@ module Mentionable ...@@ -51,12 +51,8 @@ module Mentionable
identifier = match.delete "@" identifier = match.delete "@"
if identifier == "all" if identifier == "all"
users += project.team.members.flatten users += project.team.members.flatten
else
if has_project
id = project.team.members.find_by(username: identifier).try(:id)
else else
id = User.find_by(username: identifier).try(:id) id = User.find_by(username: identifier).try(:id)
end
users << User.find(id) unless id.blank? users << User.find(id) unless id.blank?
end end
end end
......
...@@ -186,10 +186,6 @@ class Event < ActiveRecord::Base ...@@ -186,10 +186,6 @@ class Event < ActiveRecord::Base
data[:ref]["refs/heads"] data[:ref]["refs/heads"]
end end
def new_branch?
commit_from =~ /^00000/
end
def new_ref? def new_ref?
commit_from =~ /^00000/ commit_from =~ /^00000/
end end
......
...@@ -80,7 +80,7 @@ class Note < ActiveRecord::Base ...@@ -80,7 +80,7 @@ class Note < ActiveRecord::Base
note_options = { note_options = {
project: project, project: project,
author: author, author: author,
note: "_mentioned in #{gfm_reference}_", note: cross_reference_note_content(gfm_reference),
system: true system: true
} }
...@@ -90,7 +90,7 @@ class Note < ActiveRecord::Base ...@@ -90,7 +90,7 @@ class Note < ActiveRecord::Base
note_options.merge!(noteable: noteable) note_options.merge!(noteable: noteable)
end end
create(note_options) create(note_options) unless cross_reference_disallowed?(noteable, mentioner)
end end
def create_milestone_change_note(noteable, project, author, milestone) def create_milestone_change_note(noteable, project, author, milestone)
...@@ -165,6 +165,15 @@ class Note < ActiveRecord::Base ...@@ -165,6 +165,15 @@ class Note < ActiveRecord::Base
[:discussion, type.try(:underscore), id, line_code].join("-").to_sym [:discussion, type.try(:underscore), id, line_code].join("-").to_sym
end end
# Determine if cross reference note should be created.
# eg. mentioning a commit in MR comments which exists inside a MR
# should not create "mentioned in" note.
def cross_reference_disallowed?(noteable, mentioner)
if mentioner.kind_of?(MergeRequest)
mentioner.commits.map(&:id).include? noteable.id
end
end
# 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)
gfm_reference = mentioner_gfm_ref(noteable, mentioner) gfm_reference = mentioner_gfm_ref(noteable, mentioner)
...@@ -174,7 +183,7 @@ class Note < ActiveRecord::Base ...@@ -174,7 +183,7 @@ class Note < ActiveRecord::Base
where(noteable_id: noteable.id) where(noteable_id: noteable.id)
end end
notes.where('note like ?', "_mentioned in #{gfm_reference}_"). notes.where('note like ?', cross_reference_note_content(gfm_reference)).
system.any? system.any?
end end
...@@ -182,8 +191,16 @@ class Note < ActiveRecord::Base ...@@ -182,8 +191,16 @@ class Note < ActiveRecord::Base
where("note like :query", query: "%#{query}%") where("note like :query", query: "%#{query}%")
end end
def cross_reference_note_prefix
'_mentioned in '
end
private private
def cross_reference_note_content(gfm_reference)
cross_reference_note_prefix + "#{gfm_reference}_"
end
# Prepend the mentioner's namespaced project path to the GFM reference for # Prepend the mentioner's namespaced project path to the GFM reference for
# cross-project references. For same-project references, return the # cross-project references. For same-project references, return the
# unmodified GFM reference. # unmodified GFM reference.
...@@ -243,12 +260,16 @@ class Note < ActiveRecord::Base ...@@ -243,12 +260,16 @@ class Note < ActiveRecord::Base
def commit_author def commit_author
@commit_author ||= @commit_author ||=
project.users.find_by(email: noteable.author_email) || project.team.users.find_by(email: noteable.author_email) ||
project.users.find_by(name: noteable.author_name) project.team.users.find_by(name: noteable.author_name)
rescue rescue
nil nil
end end
def cross_reference?
note.start_with?(self.class.cross_reference_note_prefix)
end
def find_diff def find_diff
return nil unless noteable && noteable.diffs.present? return nil unless noteable && noteable.diffs.present?
...@@ -296,7 +317,7 @@ class Note < ActiveRecord::Base ...@@ -296,7 +317,7 @@ class Note < ActiveRecord::Base
end end
def diff_file_index def diff_file_index
line_code.split('_')[0] line_code.split('_')[0] if line_code
end end
def diff_file_name def diff_file_name
...@@ -312,11 +333,11 @@ class Note < ActiveRecord::Base ...@@ -312,11 +333,11 @@ class Note < ActiveRecord::Base
end end
def diff_old_line def diff_old_line
line_code.split('_')[1].to_i line_code.split('_')[1].to_i if line_code
end end
def diff_new_line def diff_new_line
line_code.split('_')[2].to_i line_code.split('_')[2].to_i if line_code
end end
def generate_line_code(line) def generate_line_code(line)
...@@ -337,6 +358,20 @@ class Note < ActiveRecord::Base ...@@ -337,6 +358,20 @@ class Note < ActiveRecord::Base
@diff_line @diff_line
end end
def diff_line_type
return @diff_line_type if @diff_line_type
if diff
diff_lines.each do |line|
if generate_line_code(line) == self.line_code
@diff_line_type = line.type
end
end
end
@diff_line_type
end
def truncated_diff_lines def truncated_diff_lines
max_number_of_lines = 16 max_number_of_lines = 16
prev_match_line = nil prev_match_line = nil
......
...@@ -68,6 +68,7 @@ class Project < ActiveRecord::Base ...@@ -68,6 +68,7 @@ class Project < ActiveRecord::Base
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 :buildbox_service, dependent: :destroy
has_one :bamboo_service, dependent: :destroy
has_one :pushover_service, dependent: :destroy has_one :pushover_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
...@@ -180,7 +181,7 @@ class Project < ActiveRecord::Base ...@@ -180,7 +181,7 @@ class Project < ActiveRecord::Base
end end
def with_push def with_push
includes(:events).where('events.action = ?', Event::PUSHED) joins(:events).where('events.action = ?', Event::PUSHED)
end end
def active def active
...@@ -320,7 +321,7 @@ class Project < ActiveRecord::Base ...@@ -320,7 +321,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 pushover buildbox) %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack jira jenkins pushover buildbox bamboo)
end end
def gitlab_ci? def gitlab_ci?
...@@ -416,43 +417,8 @@ class Project < ActiveRecord::Base ...@@ -416,43 +417,8 @@ class Project < ActiveRecord::Base
end end
def update_merge_requests(oldrev, newrev, ref, user) def update_merge_requests(oldrev, newrev, ref, user)
return true unless ref =~ /heads/ MergeRequests::RefreshService.new(self, user).
branch_name = ref.gsub("refs/heads/", "") execute(oldrev, newrev, ref)
commits = self.repository.commits_between(oldrev, newrev)
c_ids = commits.map(&:id)
# Close merge requests
mrs = self.merge_requests.opened.where(target_branch: branch_name).to_a
mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) }
mrs.uniq.each do |merge_request|
MergeRequests::MergeService.new.execute(merge_request, user, nil)
end
# Update code for merge requests into project between project branches
mrs = self.merge_requests.opened.by_branch(branch_name).to_a
# Update code for merge requests between project and project fork
mrs += self.fork_merge_requests.opened.by_branch(branch_name).to_a
mrs.uniq.each do |merge_request|
merge_request.reload_code
merge_request.mark_as_unchecked
end
# Add comment about pushing new commits to merge requests
comment_mr_with_commits(branch_name, commits, user)
true
end
def comment_mr_with_commits(branch_name, commits, user)
mrs = self.origin_merge_requests.opened.where(source_branch: branch_name).to_a
mrs += self.fork_merge_requests.opened.where(source_branch: branch_name).to_a
mrs.uniq.each do |merge_request|
Note.create_new_commits_note(merge_request, merge_request.project,
user, commits)
end
end end
def valid_repo? def valid_repo?
......
class BambooService < CiService
include HTTParty
prop_accessor :bamboo_url, :build_key, :username, :password
validates :bamboo_url, presence: true,
format: { with: URI::regexp }, if: :activated?
validates :build_key, presence: true, if: :activated?
validates :username, presence: true,
if: ->(service) { service.password? }, if: :activated?
validates :password, presence: true,
if: ->(service) { service.username? }, if: :activated?
attr_accessor :response
after_save :compose_service_hook, if: :activated?
def compose_service_hook
hook = service_hook || build_service_hook
hook.save
end
def title
'Atlassian Bamboo CI'
end
def description
'A continuous integration and build server'
end
def help
'You must set up automatic revision labeling and a repository trigger in Bamboo.'
end
def to_param
'bamboo'
end
def fields
[
{ type: 'text', name: 'bamboo_url',
placeholder: 'Bamboo root URL like https://bamboo.example.com' },
{ type: 'text', name: 'build_key',
placeholder: 'Bamboo build plan key like KEY' },
{ type: 'text', name: 'username',
placeholder: 'A user with API access, if applicable' },
{ type: 'password', name: 'password' },
]
end
def build_info(sha)
url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}")
if username.blank? && password.blank?
@response = HTTParty.get(parsed_url.to_s, verify: false)
else
get_url = "#{url}&os_authType=basic"
auth = {
username: username,
password: password,
}
@response = HTTParty.get(get_url, verify: false, basic_auth: auth)
end
end
def build_page(sha)
build_info(sha) if @response.nil? || !@response.code
if @response.code != 200 || @response['results']['results']['size'] == '0'
# If actual build link can't be determined, send user to build summary page.
"#{bamboo_url}/browse/#{build_key}"
else
# If actual build link is available, go to build result page.
result_key = @response['results']['results']['result']['planResultKey']['key']
"#{bamboo_url}/browse/#{result_key}"
end
end
def commit_status(sha)
build_info(sha) if @response.nil? || !@response.code
return :error unless @response.code == 200 || @response.code == 404
status = if @response.code == 404 || @response['results']['results']['size'] == '0'
'Pending'
else
@response['results']['results']['result']['buildState']
end
if status.include?('Success')
'success'
elsif status.include?('Failed')
'failed'
elsif status.include?('Pending')
'pending'
else
:error
end
end
def execute(_data)
# Bamboo requires a GET and does not take any data.
self.class.get("#{bamboo_url}/updateAndBuild.action?buildKey=#{build_key}",
verify: false)
end
end
...@@ -37,13 +37,12 @@ class FlowdockService < Service ...@@ -37,13 +37,12 @@ class FlowdockService < Service
end end
def execute(push_data) def execute(push_data)
repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git")
Flowdock::Git.post( Flowdock::Git.post(
push_data[:ref], push_data[:ref],
push_data[:before], push_data[:before],
push_data[:after], push_data[:after],
token: token, token: token,
repo: repo_path, repo: project.repository.path_to_repo,
repo_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}", repo_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}",
commit_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/%s", commit_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/%s",
diff_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/compare/%s...%s", diff_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/compare/%s...%s",
......
...@@ -38,14 +38,13 @@ class GemnasiumService < Service ...@@ -38,14 +38,13 @@ class GemnasiumService < Service
end end
def execute(push_data) def execute(push_data)
repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git")
Gemnasium::GitlabService.execute( Gemnasium::GitlabService.execute(
ref: push_data[:ref], ref: push_data[:ref],
before: push_data[:before], before: push_data[:before],
after: push_data[:after], after: push_data[:after],
token: token, token: token,
api_key: api_key, api_key: api_key,
repo: repo_path repo: project.repository.path_to_repo
) )
end end
end end
...@@ -28,7 +28,7 @@ class GitlabCiService < CiService ...@@ -28,7 +28,7 @@ class GitlabCiService < CiService
end end
def commit_status_path(sha) def commit_status_path(sha)
project_url + "/builds/#{sha}/status.json?token=#{token}" project_url + "/commits/#{sha}/status.json?token=#{token}"
end end
def get_ci_build(sha) def get_ci_build(sha)
...@@ -55,7 +55,7 @@ class GitlabCiService < CiService ...@@ -55,7 +55,7 @@ class GitlabCiService < CiService
end end
def build_page(sha) def build_page(sha)
project_url + "/builds/#{sha}" project_url + "/commits/#{sha}"
end end
def builds_path def builds_path
......
...@@ -30,25 +30,21 @@ class SlackService < Service ...@@ -30,25 +30,21 @@ class SlackService < Service
def fields def fields
[ [
{ type: 'text', name: 'webhook', placeholder: '' } { type: 'text', name: 'webhook', placeholder: 'https://hooks.slack.com/services/...' }
] ]
end end
def execute(push_data) def execute(push_data)
return unless webhook.present?
message = SlackMessage.new(push_data.merge( message = SlackMessage.new(push_data.merge(
project_url: project_url, project_url: project_url,
project_name: project_name project_name: project_name
)) ))
credentials = webhook.match(/([\w-]*).slack.com.*services\/(.*)/) notifier = Slack::Notifier.new(webhook)
if credentials.present?
subdomain = credentials[1]
token = credentials[2].split("token=").last
notifier = Slack::Notifier.new(subdomain, token)
notifier.ping(message.pretext, attachments: message.attachments) notifier.ping(message.pretext, attachments: message.attachments)
end end
end
private private
......
...@@ -231,6 +231,11 @@ class User < ActiveRecord::Base ...@@ -231,6 +231,11 @@ class User < ActiveRecord::Base
where("lower(name) LIKE :query OR lower(email) LIKE :query OR lower(username) LIKE :query", query: "%#{query.downcase}%") where("lower(name) LIKE :query OR lower(email) LIKE :query OR lower(username) LIKE :query", query: "%#{query.downcase}%")
end end
def by_login(login)
where('lower(username) = :value OR lower(email) = :value',
value: login.to_s.downcase).first
end
def by_username_or_id(name_or_id) def by_username_or_id(name_or_id)
where('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i).first where('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i).first
end end
...@@ -336,11 +341,7 @@ class User < ActiveRecord::Base ...@@ -336,11 +341,7 @@ class User < ActiveRecord::Base
end end
def abilities def abilities
@abilities ||= begin Ability.abilities
abilities = Six.new
abilities << Ability
abilities
end
end end
def can_select_namespace? def can_select_namespace?
...@@ -503,6 +504,14 @@ class User < ActiveRecord::Base ...@@ -503,6 +504,14 @@ class User < ActiveRecord::Base
end end
end end
def hook_attrs
{
name: name,
username: username,
avatar_url: avatar_url
}
end
def ensure_namespace_correct def ensure_namespace_correct
# Ensure user has namespace # Ensure user has namespace
self.create_namespace!(path: self.username, name: self.username) unless self.namespace self.create_namespace!(path: self.username, name: self.username) unless self.namespace
......
...@@ -6,11 +6,7 @@ class BaseService ...@@ -6,11 +6,7 @@ class BaseService
end end
def abilities def abilities
@abilities ||= begin Ability.abilities
abilities = Six.new
abilities << Ability
abilities
end
end end
def can?(object, action, subject) def can?(object, action, subject)
......
...@@ -83,9 +83,14 @@ class GitPushService ...@@ -83,9 +83,14 @@ class GitPushService
# closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to # closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to
# a different branch. # a different branch.
issues_to_close = commit.closes_issues(project) issues_to_close = commit.closes_issues(project)
author = commit_user(commit)
if !issues_to_close.empty? && is_default_branch # Load commit author only if needed.
# For push with 1k commits it prevents 900+ requests in database
author = nil
if issues_to_close.present? && is_default_branch
author ||= commit_user(commit)
issues_to_close.each do |issue| issues_to_close.each do |issue|
if project.jira_tracker? && project.jira_service.active if project.jira_tracker? && project.jira_service.active
project.jira_service.execute(push_data, issue) project.jira_service.execute(push_data, issue)
...@@ -100,11 +105,16 @@ class GitPushService ...@@ -100,11 +105,16 @@ class GitPushService
# being pushed to a different branch). # being pushed to a different branch).
refs = commit.references(project) - issues_to_close refs = commit.references(project) - issues_to_close
refs.reject! { |r| commit.has_mentioned?(r) } refs.reject! { |r| commit.has_mentioned?(r) }
if refs.present?
author ||= commit_user(commit)
refs.each do |r| refs.each do |r|
Note.create_cross_reference_note(r, commit, author, project) Note.create_cross_reference_note(r, commit, author, project)
end end
end end
end end
end
# Produce a hash of post-receive data # Produce a hash of post-receive data
# #
...@@ -164,19 +174,19 @@ class GitPushService ...@@ -164,19 +174,19 @@ class GitPushService
ref_parts = ref.split('/') ref_parts = ref.split('/')
# Return if this is not a push to a branch (e.g. new commits) # Return if this is not a push to a branch (e.g. new commits)
ref_parts[1] =~ /heads/ && oldrev != "0000000000000000000000000000000000000000" ref_parts[1] =~ /heads/ && oldrev != Gitlab::Git::BLANK_SHA
end end
def push_to_new_branch?(ref, oldrev) def push_to_new_branch?(ref, oldrev)
ref_parts = ref.split('/') ref_parts = ref.split('/')
ref_parts[1] =~ /heads/ && oldrev == "0000000000000000000000000000000000000000" ref_parts[1] =~ /heads/ && oldrev == Gitlab::Git::BLANK_SHA
end end
def push_remove_branch?(ref, newrev) def push_remove_branch?(ref, newrev)
ref_parts = ref.split('/') ref_parts = ref.split('/')
ref_parts[1] =~ /heads/ && newrev == "0000000000000000000000000000000000000000" ref_parts[1] =~ /heads/ && newrev == Gitlab::Git::BLANK_SHA
end end
def push_to_branch?(ref) def push_to_branch?(ref)
......
...@@ -4,7 +4,7 @@ module Issues ...@@ -4,7 +4,7 @@ module Issues
private private
def execute_hooks(issue, action = 'open') def execute_hooks(issue, action = 'open')
issue_data = issue.to_hook_data issue_data = issue.to_hook_data(current_user)
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)
......
...@@ -13,7 +13,8 @@ module MergeRequests ...@@ -13,7 +13,8 @@ module MergeRequests
def execute_project_hooks(merge_request) def execute_project_hooks(merge_request)
if merge_request.project if merge_request.project
merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks) hook_data = merge_request.to_hook_data(current_user)
merge_request.project.execute_hooks(hook_data, :merge_request_hooks)
end end
end end
end end
......
...@@ -7,7 +7,8 @@ module MergeRequests ...@@ -7,7 +7,8 @@ module MergeRequests
def execute_hooks(merge_request) def execute_hooks(merge_request)
if merge_request.project if merge_request.project
merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks) hook_data = merge_request.to_hook_data(current_user)
merge_request.project.execute_hooks(hook_data, :merge_request_hooks)
end end
end end
end end
......
module MergeRequests
class RefreshService < MergeRequests::BaseService
def execute(oldrev, newrev, ref)
return true unless ref =~ /heads/
@branch_name = ref.gsub("refs/heads/", "")
@fork_merge_requests = @project.fork_merge_requests.opened
@commits = @project.repository.commits_between(oldrev, newrev)
close_merge_requests
reload_merge_requests
comment_mr_with_commits
true
end
private
# Collect open merge requests that target same branch we push into
# and close if push to master include last commit from merge request
# We need this to close(as merged) merge requests that were merged into
# target branch manually
def close_merge_requests
commit_ids = @commits.map(&:id)
merge_requests = @project.merge_requests.opened.where(target_branch: @branch_name).to_a
merge_requests = merge_requests.select(&:last_commit)
merge_requests = merge_requests.select do |merge_request|
commit_ids.include?(merge_request.last_commit.id)
end
merge_requests.uniq.select(&:source_project).each do |merge_request|
MergeRequests::MergeService.new.execute(merge_request, @current_user, nil)
end
end
# Refresh merge request diff if we push to source or target branch of merge request
# Note: we should update merge requests from forks too
def reload_merge_requests
merge_requests = @project.merge_requests.opened.by_branch(@branch_name).to_a
merge_requests += @fork_merge_requests.by_branch(@branch_name).to_a
merge_requests = filter_merge_requests(merge_requests)
merge_requests.each do |merge_request|
merge_request.reload_code
merge_request.mark_as_unchecked
end
end
# Add comment about pushing new commits to merge requests
def comment_mr_with_commits
merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a
merge_requests += @fork_merge_requests.where(source_branch: @branch_name).to_a
merge_requests = filter_merge_requests(merge_requests)
merge_requests.each do |merge_request|
Note.create_new_commits_note(merge_request, merge_request.project,
@current_user, @commits)
end
end
def filter_merge_requests(merge_requests)
merge_requests.uniq.select(&:source_project)
end
end
end
...@@ -119,11 +119,12 @@ class NotificationService ...@@ -119,11 +119,12 @@ class NotificationService
# ignore gitlab service messages # ignore gitlab service messages
return true if note.note =~ /\A_Status changed to closed_/ return true if note.note =~ /\A_Status changed to closed_/
return true if note.note =~ /\A_mentioned in / && note.system == true return true if note.cross_reference? && note.system == true
opts = { noteable_type: note.noteable_type, project_id: note.project_id } opts = { noteable_type: note.noteable_type, project_id: note.project_id }
target = note.noteable target = note.noteable
if target.respond_to?(:participants) if target.respond_to?(:participants)
recipients = target.participants recipients = target.participants
else else
......
...@@ -18,7 +18,7 @@ class SystemHooksService ...@@ -18,7 +18,7 @@ class SystemHooksService
def build_event_data(model, event) def build_event_data(model, event)
data = { data = {
event_name: build_event_name(model, event), event_name: build_event_name(model, event),
created_at: model.created_at created_at: model.created_at.xmlschema
} }
case model case model
......
...@@ -26,6 +26,10 @@ class AttachmentUploader < CarrierWave::Uploader::Base ...@@ -26,6 +26,10 @@ class AttachmentUploader < CarrierWave::Uploader::Base
Gitlab.config.gitlab.relative_url_root + "/files/#{model.class.to_s.underscore}/#{model.id}/#{file.filename}" Gitlab.config.gitlab.relative_url_root + "/files/#{model.class.to_s.underscore}/#{model.id}/#{file.filename}"
end end
def url
Gitlab.config.gitlab.relative_url_root + super unless super.nil?
end
def file_storage? def file_storage?
self.class.storage == CarrierWave::Storage::File self.class.storage == CarrierWave::Storage::File
end end
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
- next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/) - next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/)
- data = process.strip.split(' ') - data = process.strip.split(' ')
%tr %tr
%td= Settings.gitlab.user %td= gitlab_config.user
- 5.times do - 5.times do
%td= data.shift %td= data.shift
%td= data.join(' ') %td= data.join(' ')
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
If '[25 of 25 busy]' is shown, restart GitLab with 'sudo service gitlab reload'. If '[25 of 25 busy]' is shown, restart GitLab with 'sudo service gitlab reload'.
%p %p
%i.fa.fa-exclamation-circle %i.fa.fa-exclamation-circle
If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{Settings.gitlab.user} -f sidekiq) and restart GitLab. If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{gitlab_config.user} -f sidekiq) and restart GitLab.
......
...@@ -2,39 +2,20 @@ ...@@ -2,39 +2,20 @@
- if @group.errors.any? - if @group.errors.any?
.alert.alert-danger .alert.alert-danger
%span= @group.errors.full_messages.first %span= @group.errors.full_messages.first
.form-group.group_name_holder
= f.label :name, class: 'control-label' do
Group name
.col-sm-10
= f.text_field :name, placeholder: "Example Group", class: "form-control"
.form-group.group-description-holder = render 'shared/group_form', f: f
= f.label :description, "Details", class: 'control-label'
.col-sm-10
= f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4
.form-group.group-description-holder .form-group.group-description-holder
= f.label :avatar, "Group avatar", class: 'control-label' = f.label :avatar, "Group avatar", class: 'control-label'
.col-sm-10 .col-sm-10
%a.choose-btn.btn.btn-small.js-choose-group-avatar-button = render 'shared/choose_group_avatar_button', f: f
%i.fa.fa-paperclip
%span Choose File ...
&nbsp;
%span.file_name.js-avatar-filename File name...
= f.file_field :avatar, class: "js-group-avatar-input hidden"
.light The maximum file size allowed is 100KB.
- if @group.new_record? - if @group.new_record?
.form-group .form-group
.col-sm-2 .col-sm-2
.col-sm-10 .col-sm-10
.bs-callout.bs-callout-info .bs-callout.bs-callout-info
%ul = render 'shared/group_tips'
%li A group is a collection of several projects
%li Groups are private by default
%li Members of a group may only view projects they have permission to access
%li Group project URLs are prefixed with the group namespace
%li Existing projects may be moved into a group
.form-actions .form-actions
= f.submit 'Create group', class: "btn btn-create" = f.submit 'Create group', class: "btn btn-create"
= link_to 'Cancel', admin_groups_path, class: "btn btn-cancel" = link_to 'Cancel', admin_groups_path, class: "btn btn-cancel"
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
= form_tag admin_groups_path, method: :get, class: 'form-inline' do = form_tag admin_groups_path, method: :get, class: 'form-inline' do
.form-group .form-group
= text_field_tag :name, params[:name], class: "form-control input-mn-300" = text_field_tag :name, params[:name], class: "form-control input-mn-300"
= submit_tag "Search", class: "btn submit btn-primary" = button_tag "Search", class: "btn submit btn-primary"
%hr %hr
......
...@@ -91,7 +91,7 @@ ...@@ -91,7 +91,7 @@
%div.prepend-top-10 %div.prepend-top-10
= select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2" = select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
%hr %hr
= submit_tag 'Add users into group', class: "btn btn-create" = button_tag 'Add users into group', class: "btn btn-create"
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
%h3.panel-title %h3.panel-title
......
- loggers = [Gitlab::GitLogger, Gitlab::AppLogger,
Gitlab::ProductionLogger, Gitlab::SidekiqLogger]
%ul.nav.nav-tabs.log-tabs %ul.nav.nav-tabs.log-tabs
%li.active - loggers.each do |klass|
= link_to "githost.log", "#githost", 'data-toggle' => 'tab' %li{ class: (klass == Gitlab::GitLogger ? 'active' : '') }
%li = link_to klass::file_name, "##{klass::file_name_noext}",
= link_to "application.log", "#application", 'data-toggle' => 'tab' 'data-toggle' => 'tab'
%li
= link_to "production.log", "#production", 'data-toggle' => 'tab'
%li
= link_to "sidekiq.log", "#sidekiq", 'data-toggle' => 'tab'
%p.light To prevent performance issues admin logs output the last 2000 lines %p.light To prevent performance issues admin logs output the last 2000 lines
.tab-content .tab-content
.tab-pane.active#githost - loggers.each do |klass|
.file-holder#README .tab-pane{ class: (klass == Gitlab::GitLogger ? 'active' : ''),
.file-title id: klass::file_name_noext }
%i.fa.fa-file
githost.log
.pull-right
= link_to '#', class: 'log-bottom' do
%i.fa.fa-arrow-down
Scroll down
.file-content.logs
%ol
- Gitlab::GitLogger.read_latest.each do |line|
%li
%p= line
.tab-pane#application
.file-holder#README
.file-title
%i.fa.fa-file
application.log
.pull-right
= link_to '#', class: 'log-bottom' do
%i.fa.fa-arrow-down
Scroll down
.file-content.logs
%ol
- Gitlab::AppLogger.read_latest.each do |line|
%li
%p= line
.tab-pane#production
.file-holder#README
.file-title
%i.fa.fa-file
production.log
.pull-right
= link_to '#', class: 'log-bottom' do
%i.fa.fa-arrow-down
Scroll down
.file-content.logs
%ol
- Gitlab::Logger.read_latest_for('production.log').each do |line|
%li
%p= line
.tab-pane#sidekiq
.file-holder#README .file-holder#README
.file-title .file-title
%i.fa.fa-file %i.fa.fa-file
sidekiq.log = klass::file_name
.pull-right .pull-right
= link_to '#', class: 'log-bottom' do = link_to '#', class: 'log-bottom' do
%i.fa.fa-arrow-down %i.fa.fa-arrow-down
Scroll down Scroll down
.file-content.logs .file-content.logs
%ol %ol
- Gitlab::Logger.read_latest_for('sidekiq.log').each do |line| - klass.read_latest.each do |line|
%li %li
%p= line %p= line
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
= label = label
%hr %hr
= hidden_field_tag :sort, params[:sort] = hidden_field_tag :sort, params[:sort]
= submit_tag "Search", class: "btn submit btn-primary" = button_tag "Search", class: "btn submit btn-primary"
= link_to "Reset", admin_projects_path, class: "btn btn-cancel" = link_to "Reset", admin_projects_path, class: "btn btn-cancel"
.col-md-9 .col-md-9
......
...@@ -6,8 +6,8 @@ ...@@ -6,8 +6,8 @@
.devise-errors .devise-errors
= devise_error_messages! = devise_error_messages!
= f.hidden_field :reset_password_token = f.hidden_field :reset_password_token
%div .form-group#password-strength
= f.password_field :password, class: "form-control top", placeholder: "New password", required: true = f.password_field :password, class: "form-control top", id: "user_password_recover", placeholder: "New password", required: true
%div %div
= f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm new password", required: true = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm new password", required: true
.clearfix.append-bottom-10 .clearfix.append-bottom-10
......
...@@ -11,8 +11,8 @@ ...@@ -11,8 +11,8 @@
= f.text_field :username, class: "form-control middle", placeholder: "Username", required: true = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true
%div %div
= f.email_field :email, class: "form-control middle", placeholder: "Email", required: true = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true
%div .form-group#password-strength
= f.password_field :password, class: "form-control middle", placeholder: "Password", required: true = f.password_field :password, class: "form-control middle", id: "user_password_sign_up", placeholder: "Password", required: true
%div %div
= f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm password", required: true = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm password", required: true
%div %div
......
...@@ -2,4 +2,4 @@ ...@@ -2,4 +2,4 @@
= text_field_tag :username, nil, {class: "form-control top", placeholder: "LDAP Login", autofocus: "autofocus"} = text_field_tag :username, nil, {class: "form-control top", placeholder: "LDAP Login", autofocus: "autofocus"}
= password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"}
%br/ %br/
= submit_tag "LDAP Sign in", class: "btn-save btn" = button_tag "LDAP Sign in", class: "btn-save btn"
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.form-group .form-group
= search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "groups_search" = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "groups_search"
.form-group .form-group
= submit_tag 'Search', class: "btn btn-primary wide" = button_tag 'Search', class: "btn btn-primary wide"
.pull-right .pull-right
.dropdown.inline .dropdown.inline
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.form-group .form-group
= search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "projects_search" = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "projects_search"
.form-group .form-group
= submit_tag 'Search', class: "btn btn-primary wide" = button_tag 'Search', class: "btn btn-primary wide"
.pull-right .pull-right
.dropdown.inline .dropdown.inline
......
...@@ -11,16 +11,8 @@ ...@@ -11,16 +11,8 @@
- if @group.errors.any? - if @group.errors.any?
.alert.alert-danger .alert.alert-danger
%span= @group.errors.full_messages.first %span= @group.errors.full_messages.first
.form-group = render 'shared/group_form', f: f
= f.label :name, class: 'control-label' do
Group name
.col-sm-10
= f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control left"
.form-group.group-description-holder
= f.label :description, "Details", class: 'control-label'
.col-sm-10
= f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4
.form-group .form-group
.col-sm-2 .col-sm-2
.col-sm-10 .col-sm-10
...@@ -30,13 +22,7 @@ ...@@ -30,13 +22,7 @@
You can change your group avatar here You can change your group avatar here
- else - else
You can upload a group avatar here You can upload a group avatar here
%a.choose-btn.btn.btn-small.js-choose-group-avatar-button = render 'shared/choose_group_avatar_button', f: f
%i.fa.fa-paperclip
%span Choose File ...
&nbsp;
%span.file_name.js-avatar-filename File name...
= f.file_field :avatar, class: "js-group-avatar-input hidden"
.light The maximum file size allowed is 100KB.
- if @group.avatar? - if @group.avatar?
%hr %hr
= link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar" = link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar"
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
= form_tag members_group_path(@group), method: :get, class: 'form-inline member-search-form' do = form_tag members_group_path(@group), method: :get, class: 'form-inline member-search-form' do
.form-group .form-group
= search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input input-mn-300' } = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input input-mn-300' }
= submit_tag 'Search', class: 'btn' = button_tag 'Search', class: 'btn'
- if current_user && current_user.can?(:manage_group, @group) - if current_user && current_user.can?(:manage_group, @group)
.pull-right .pull-right
......
...@@ -2,37 +2,18 @@ ...@@ -2,37 +2,18 @@
- if @group.errors.any? - if @group.errors.any?
.alert.alert-danger .alert.alert-danger
%span= @group.errors.full_messages.first %span= @group.errors.full_messages.first
.form-group
= f.label :name, class: 'control-label' do
Group name
.col-sm-10
= f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control", tabindex: 1, autofocus: true
.form-group.group-description-holder = render 'shared/group_form', f: f, autofocus: true
= f.label :description, "Details", class: 'control-label'
.col-sm-10
= f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4, tabindex: 2
.form-group.group-description-holder .form-group.group-description-holder
= f.label :avatar, "Group avatar", class: 'control-label' = f.label :avatar, "Group avatar", class: 'control-label'
.col-sm-10 .col-sm-10
%a.choose-btn.btn.btn-small.js-choose-group-avatar-button = render 'shared/choose_group_avatar_button', f: f
%i.fa.fa-paperclip
%span Choose File ...
&nbsp;
%span.file_name.js-avatar-filename File name...
= f.file_field :avatar, class: "js-group-avatar-input hidden"
.light The maximum file size allowed is 100KB.
.form-group .form-group
.col-sm-2 .col-sm-2
.col-sm-10 .col-sm-10
%ul = render 'shared/group_tips'
%li A group is a collection of several projects
%li Groups are private by default
%li Members of a group may only view projects they have permission to access
%li Group project URLs are prefixed with the group namespace
%li Existing projects may be moved into a group
.form-actions .form-actions
= f.submit 'Create group', class: "btn btn-create", tabindex: 3 = f.submit 'Create group', class: "btn btn-create", tabindex: 3
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
- if @snippet || @snippets - if @snippet || @snippets
= hidden_field_tag :snippets, true = hidden_field_tag :snippets, true
= hidden_field_tag :repository_ref, @ref = hidden_field_tag :repository_ref, @ref
= submit_tag 'Go' if ENV['RAILS_ENV'] == 'test' = button_tag 'Go' if ENV['RAILS_ENV'] == 'test'
.search-autocomplete-opts.hide{:'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref } .search-autocomplete-opts.hide{:'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref }
:javascript :javascript
......
...@@ -28,3 +28,4 @@ ...@@ -28,3 +28,4 @@
You're receiving this notification because you are a member of the #{link_to_unless @target_url, @project.name_with_namespace, project_url(@project)} project team. You're receiving this notification because you are a member of the #{link_to_unless @target_url, @project.name_with_namespace, project_url(@project)} project team.
- if @target_url - if @target_url
#{link_to "View it on GitLab", @target_url} #{link_to "View it on GitLab", @target_url}
= email_action @target_url
...@@ -6,5 +6,5 @@ ...@@ -6,5 +6,5 @@
title: title:
%code= @key.title %code= @key.title
%p %p
If this key was added in error, you can remove it here: If this key was added in error, you can remove it under
= link_to "SSH Keys", profile_keys_url = link_to "SSH Keys", profile_keys_url
...@@ -2,6 +2,6 @@ Hi <%= @user.name %>! ...@@ -2,6 +2,6 @@ Hi <%= @user.name %>!
A new public key was added to your account: A new public key was added to your account:
title.................. <%= @key.title %> Title: <%= @key.title %>
If this key was added in error, you can remove it here: <%= profile_keys_url %> If this key was added in error, you can remove it at <%= profile_keys_url %>
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
.form-group .form-group
= f.label :password, 'New password', class: 'control-label' = f.label :password, 'New password', class: 'control-label'
.col-sm-10 .col-sm-10
= f.password_field :password, required: true, class: 'form-control' = f.password_field :password, required: true, class: 'form-control', id: 'user_password_profile'
.form-group .form-group
= f.label :password_confirmation, class: 'control-label' = f.label :password_confirmation, class: 'control-label'
.col-sm-10 .col-sm-10
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
.col-sm-10= f.password_field :current_password, required: true, class: 'form-control' .col-sm-10= f.password_field :current_password, required: true, class: 'form-control'
.form-group .form-group
= f.label :password, class: 'control-label' = f.label :password, class: 'control-label'
.col-sm-10= f.password_field :password, required: true, class: 'form-control' .col-sm-10= f.password_field :password, required: true, class: 'form-control', id: 'user_password_profile'
.form-group .form-group
= f.label :password_confirmation, class: 'control-label' = f.label :password_confirmation, class: 'control-label'
.col-sm-10 .col-sm-10
......
...@@ -23,5 +23,6 @@ ...@@ -23,5 +23,6 @@
tree_join(@commit.sha, @path)), class: 'btn btn-small' tree_join(@commit.sha, @path)), class: 'btn btn-small'
- if allowed_tree_edit? - if allowed_tree_edit?
= link_to '#modal-remove-blob', class: "remove-blob btn btn-small btn-remove", "data-toggle" => "modal" do = button_tag class: 'remove-blob btn btn-small btn-remove',
'data-toggle' => 'modal', 'data-target' => '#modal-remove-blob' do
Remove Remove
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
.form-group .form-group
.col-sm-2 .col-sm-2
.col-sm-10 .col-sm-10
= submit_tag 'Remove file', class: 'btn btn-remove btn-remove-file' = button_tag 'Remove file', class: 'btn btn-remove btn-remove-file'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
:javascript :javascript
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
.col-sm-10 .col-sm-10
= text_field_tag :ref, params[:ref], placeholder: 'existing branch name, tag or commit SHA', required: true, tabindex: 2, class: 'form-control' = text_field_tag :ref, params[:ref], placeholder: 'existing branch name, tag or commit SHA', required: true, tabindex: 2, class: 'form-control'
.form-actions .form-actions
= submit_tag 'Create branch', class: 'btn btn-create', tabindex: 3 = button_tag 'Create branch', class: 'btn btn-create', tabindex: 3
= link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel' = link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel'
:javascript :javascript
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
%span.input-group-addon to %span.input-group-addon to
= text_field_tag :to, params[:to], class: "form-control" = text_field_tag :to, params[:to], class: "form-control"
&nbsp; &nbsp;
= submit_tag "Compare", class: "btn btn-create commits-compare-btn" = button_tag "Compare", class: "btn btn-create commits-compare-btn"
- if compare_to_mr_button? - if compare_to_mr_button?
= link_to compare_mr_path, class: 'prepend-left-10 btn' do = link_to compare_mr_path, class: 'prepend-left-10 btn' do
%strong Make a merge request %strong Make a merge request
......
...@@ -9,6 +9,9 @@ ...@@ -9,6 +9,9 @@
.diff-btn-group .diff-btn-group
- if @commit.parent_ids.present? - if @commit.parent_ids.present?
= view_file_btn(@commit.parent_id, diff_file, project) = view_file_btn(@commit.parent_id, diff_file, project)
- else
- if diff_file.renamed_file
%span= "#{diff_file.old_path} renamed to #{diff_file.new_path}"
- else - else
%span= diff_file.new_path %span= diff_file.new_path
- if diff_file.mode_changed? - if diff_file.mode_changed?
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
= f.label :name, class: 'control-label' do = f.label :name, class: 'control-label' do
Project name Project name
.col-sm-10 .col-sm-10
= f.text_field :name, placeholder: "Example Project", class: "form-control" = f.text_field :name, placeholder: "Example Project", class: "form-control", id: "project_name_edit"
.form-group .form-group
...@@ -131,6 +131,12 @@ ...@@ -131,6 +131,12 @@
.errors-holder .errors-holder
.panel-body .panel-body
= form_for(@project, html: { class: 'form-horizontal' }) do |f| = form_for(@project, html: { class: 'form-horizontal' }) do |f|
.form-group.project_name_holder
= f.label :name, class: 'control-label' do
Project name
.col-sm-9
.form-group
= f.text_field :name, placeholder: "Example Project", class: "form-control"
.form-group .form-group
= f.label :path, class: 'control-label' do = f.label :path, class: 'control-label' do
%span Path %span Path
......
...@@ -14,8 +14,6 @@ ...@@ -14,8 +14,6 @@
.issue-info .issue-info
- if issue.assignee - if issue.assignee
assigned to #{link_to_member(@project, issue.assignee)} assigned to #{link_to_member(@project, issue.assignee)}
- else
unassigned
- if issue.votes_count > 0 - if issue.votes_count > 0
= render 'votes/votes_inline', votable: issue = render 'votes/votes_inline', votable: issue
- if issue.notes.any? - if issue.notes.any?
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
= hidden_field_tag :issue_context = hidden_field_tag :issue_context
= f.submit class: 'btn' = f.submit class: 'btn'
- elsif issue.milestone - elsif issue.milestone
= link_to issue.milestone.title, project_milestone_path = link_to project_milestone_path(@project, @issue.milestone) do
= @issue.milestone.title
- else - else
None None
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
Open Open
.creator .creator
Created by #{link_to_member(@project, @issue.author)} #{time_ago_with_tooltip(@issue.created_at)} Created by #{link_to_member(@project, @issue.author)} #{issue_timestamp(@issue)}
%h4.title %h4.title
= gfm escape_once(@issue.title) = gfm escape_once(@issue.title)
......
...@@ -10,11 +10,11 @@ ...@@ -10,11 +10,11 @@
- target_remote = @merge_request.target_project.namespace.nil? ? "target" :@merge_request.target_project.namespace.path - target_remote = @merge_request.target_project.namespace.nil? ? "target" :@merge_request.target_project.namespace.path
%p %p
%strong Step 1. %strong Step 1.
Checkout the branch we are going to merge and pull in the code Fetch the code and create a new branch pointing to it
%pre.dark %pre.dark
:preserve :preserve
git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} #{@merge_request.target_branch} git fetch #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch}
git pull #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch} git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} FETCH_HEAD
%p %p
%strong Step 2. %strong Step 2.
Merge the branch and push the changes to GitLab Merge the branch and push the changes to GitLab
......
...@@ -19,14 +19,14 @@ ...@@ -19,14 +19,14 @@
Encoding Encoding
.col-sm-10 .col-sm-10
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control' = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
= render 'shared/commit_message_container', params: params,
placeholder: 'Add new file'
.file-holder .file-holder
.file-title .file-title
%i.fa.fa-file %i.fa.fa-file
.file-content.code .file-content.code
%pre#editor= params[:content] %pre#editor= params[:content]
= render 'shared/commit_message_container', params: params,
placeholder: 'Add new file'
= hidden_field_tag 'content', '', id: 'file-content' = hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref, = render 'projects/commit_button', ref: @ref,
cancel_path: project_tree_path(@project, @id) cancel_path: project_tree_path(@project, @id)
......
...@@ -59,7 +59,7 @@ ...@@ -59,7 +59,7 @@
- if note.attachment.image? - if note.attachment.image?
= link_to note.attachment.secure_url, target: '_blank' do = link_to note.attachment.secure_url, target: '_blank' do
= image_tag note.attachment.secure_url, class: 'note-image-attach' = image_tag note.attachment.secure_url, class: 'note-image-attach'
.attachment.pull-right .attachment
= link_to note.attachment.secure_url, target: "_blank" do = link_to note.attachment.secure_url, target: "_blank" do
%i.fa.fa-paperclip %i.fa.fa-paperclip
= note.attachment_identifier = note.attachment_identifier
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
- @service.fields.each do |field| - @service.fields.each do |field|
- name = field[:name] - name = field[:name]
- value = @service.send(name) - value = @service.send(name) unless field[:type] == 'password'
- type = field[:type] - type = field[:type]
- placeholder = field[:placeholder] - placeholder = field[:placeholder]
- choices = field[:choices] - choices = field[:choices]
...@@ -47,6 +47,8 @@ ...@@ -47,6 +47,8 @@
= f.password_field name, class: "form-control" = f.password_field name, class: "form-control"
- elsif type == 'select' - elsif type == 'select'
= f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" } = f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
- elsif type == 'password'
= f.password_field name, class: 'form-control'
.form-actions .form-actions
= f.submit 'Save', class: 'btn btn-save' = f.submit 'Save', class: 'btn btn-save'
......
...@@ -4,6 +4,9 @@ ...@@ -4,6 +4,9 @@
= link_to project_commits_path(@project, tag.name), class: "" do = link_to project_commits_path(@project, tag.name), class: "" do
%i.fa.fa-tag %i.fa.fa-tag
= tag.name = tag.name
- if tag.message.present?
&nbsp;
= tag.message
.pull-right .pull-right
- if can? current_user, :download_code, @project - if can? current_user, :download_code, @project
= render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-small' = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-small'
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
= text_field_tag :message, nil, placeholder: 'Enter message.', required: false, tabindex: 3, class: 'form-control' = text_field_tag :message, nil, placeholder: 'Enter message.', required: false, tabindex: 3, class: 'form-control'
.light (Optional) Entering a message will create an annotated tag. .light (Optional) Entering a message will create an annotated tag.
.form-actions .form-actions
= submit_tag 'Create tag', class: 'btn btn-create', tabindex: 3 = button_tag 'Create tag', class: 'btn btn-create', tabindex: 3
= link_to 'Cancel', project_tags_path(@project), class: 'btn btn-cancel' = link_to 'Cancel', project_tags_path(@project), class: 'btn btn-cancel'
:javascript :javascript
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
- unless @project.personal? && user == current_user - unless @project.personal? && user == current_user
.pull-left .pull-left
= form_for(member, as: :project_member, url: project_team_member_path(@project, member.user)) do |f| = form_for(member, as: :project_member, url: project_team_member_path(@project, member.user)) do |f|
= f.select :access_level, options_for_select(ProjectMember.access_roles, member.access_level), {}, class: "medium project-access-select span2 trigger-submit" = f.select :access_level, options_for_select(ProjectMember.access_roles, member.access_level), {}, class: "trigger-submit"
&nbsp; &nbsp;
= link_to project_team_member_path(@project, user), data: { confirm: remove_from_project_team_message(@project, user)}, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from team' do = link_to project_team_member_path(@project, user), data: { confirm: remove_from_project_team_message(@project, user)}, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from team' do
%i.fa.fa-minus.fa-inverse %i.fa.fa-minus.fa-inverse
......
...@@ -9,6 +9,6 @@ ...@@ -9,6 +9,6 @@
.col-sm-10= select_tag(:source_project_id, options_from_collection_for_select(current_user.authorized_projects, :id, :name_with_namespace), prompt: "Select project", class: "select2 lg", required: true) .col-sm-10= select_tag(:source_project_id, options_from_collection_for_select(current_user.authorized_projects, :id, :name_with_namespace), prompt: "Select project", class: "select2 lg", required: true)
.form-actions .form-actions
= submit_tag 'Import project members', class: "btn btn-create" = button_tag 'Import project members', class: "btn btn-create"
= link_to "Cancel", project_team_index_path(@project), class: "btn btn-cancel" = link_to "Cancel", project_team_index_path(@project), class: "btn btn-cancel"
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
.col-sm-6 .col-sm-6
= search_field_tag :search, params[:search], placeholder: "issue 143", class: "form-control search-text-input", id: "dashboard_search" = search_field_tag :search, params[:search], placeholder: "issue 143", class: "form-control search-text-input", id: "dashboard_search"
.col-sm-4 .col-sm-4
= submit_tag 'Search', class: "btn btn-create" = button_tag 'Search', class: "btn btn-create"
.form-group .form-group
.col-sm-2 .col-sm-2
- unless params[:snippets].eql? 'true' - unless params[:snippets].eql? 'true'
......
%a.choose-btn.btn.btn-small.js-choose-group-avatar-button
%i.fa.fa-paperclip
%span Choose File ...
&nbsp;
%span.file_name.js-avatar-filename File name...
= f.file_field :avatar, class: 'js-group-avatar-input hidden'
.light The maximum file size allowed is 100KB.
.form-group
= f.label :name, class: 'control-label' do
Group name
.col-sm-10
= f.text_field :name, placeholder: 'Example Group', class: 'form-control',
autofocus: local_assigns[:autofocus] || false
.form-group.group-description-holder
= f.label :description, 'Details', class: 'control-label'
.col-sm-10
= f.text_area :description, maxlength: 250,
class: 'form-control js-gfm-input', rows: 4
%ul
%li A group is a collection of several projects
%li Groups are private by default
%li Members of a group may only view projects they have permission to access
%li Group project URLs are prefixed with the group namespace
%li Existing projects may be moved into a group
- groups.each do |group| - groups.each do |group|
= link_to group, class: 'profile-groups-avatars', :title => group.name do = link_to group, class: 'profile-groups-avatars', :title => group.name do
= image_tag group_icon(group.path) - image_tag group_icon(group.path)
...@@ -13,7 +13,6 @@ module Gitlab ...@@ -13,7 +13,6 @@ module Gitlab
# Custom directories with classes and modules you want to be autoloadable. # Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += %W(#{config.root}/lib config.autoload_paths += %W(#{config.root}/lib
#{config.root}/app/finders
#{config.root}/app/models/hooks #{config.root}/app/models/hooks
#{config.root}/app/models/concerns #{config.root}/app/models/concerns
#{config.root}/app/models/project_services #{config.root}/app/models/project_services
...@@ -23,10 +22,6 @@ module Gitlab ...@@ -23,10 +22,6 @@ module Gitlab
# :all can be used as a placeholder for all plugins not explicitly named. # :all can be used as a placeholder for all plugins not explicitly named.
# config.plugins = [ :exception_notification, :ssl_requirement, :all ] # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
# config.time_zone = 'Central Time (US & Canada)'
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de # config.i18n.default_locale = :de
......
...@@ -33,7 +33,14 @@ production: &base ...@@ -33,7 +33,14 @@ production: &base
# Uncomment and customize if you can't use the default user to run GitLab (default: 'git') # Uncomment and customize if you can't use the default user to run GitLab (default: 'git')
# user: git # user: git
## Date & Time settings
# Uncomment and customize if you want to change the default time zone of GitLab application.
# To see all available zones, run `bundle exec rake time:zones:all`
# time_zone: 'UTC'
## Email settings ## Email settings
# Uncomment and set to false if you need to disable email sending from GitLab (default: true)
# email_enabled: true
# Email address used in the "From" field in mails sent by GitLab # Email address used in the "From" field in mails sent by GitLab
email_from: example@example.com email_from: example@example.com
......
...@@ -102,6 +102,7 @@ Settings.gitlab['https'] = false if Settings.gitlab['https'].nil? ...@@ -102,6 +102,7 @@ Settings.gitlab['https'] = false if Settings.gitlab['https'].nil?
Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80 Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80
Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || '' Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || ''
Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http" Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http"
Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].nil?
Settings.gitlab['email_from'] ||= "gitlab@#{Settings.gitlab.host}" Settings.gitlab['email_from'] ||= "gitlab@#{Settings.gitlab.host}"
Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url) Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url)
Settings.gitlab['user'] ||= 'git' Settings.gitlab['user'] ||= 'git'
...@@ -110,6 +111,7 @@ Settings.gitlab['user_home'] ||= begin ...@@ -110,6 +111,7 @@ Settings.gitlab['user_home'] ||= begin
rescue ArgumentError # no user configured rescue ArgumentError # no user configured
'/home/' + Settings.gitlab['user'] '/home/' + Settings.gitlab['user']
end end
Settings.gitlab['time_zone'] ||= nil
Settings.gitlab['signup_enabled'] ||= false Settings.gitlab['signup_enabled'] ||= false
Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil? Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], []) Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
......
# Interceptor in lib/disable_email_interceptor.rb
ActionMailer::Base.register_interceptor(DisableEmailInterceptor) unless Gitlab.config.gitlab.email_enabled
Time.zone = Gitlab.config.gitlab.time_zone || Time.zone
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
# Use at least one worker per core if you're on a dedicated server, # Use at least one worker per core if you're on a dedicated server,
# more will usually help for _short_ waits on databases/caches. # more will usually help for _short_ waits on databases/caches.
# The minimum is 2
worker_processes 2 worker_processes 2
# Since Unicorn is never exposed to outside clients, it does not need to # Since Unicorn is never exposed to outside clients, it does not need to
......
password = if ENV['GITLAB_ROOT_PASSWORD'].blank? if ENV['GITLAB_ROOT_PASSWORD'].blank?
"5iveL!fe" password = '5iveL!fe'
else expire_time = Time.now
ENV['GITLAB_ROOT_PASSWORD'] else
end password = ENV['GITLAB_ROOT_PASSWORD']
expire_time = nil
end
admin = User.create( admin = User.create(
email: "admin@example.com", email: "admin@example.com",
...@@ -10,7 +12,7 @@ admin = User.create( ...@@ -10,7 +12,7 @@ admin = User.create(
username: 'root', username: 'root',
password: password, password: password,
password_confirmation: password, password_confirmation: password,
password_expires_at: Time.now, password_expires_at: expire_time,
theme_id: Gitlab::Theme::MARS theme_id: Gitlab::Theme::MARS
) )
......
...@@ -23,7 +23,7 @@ class SerializeServiceProperties < ActiveRecord::Migration ...@@ -23,7 +23,7 @@ class SerializeServiceProperties < ActiveRecord::Migration
associations[service.type.to_sym].each do |attribute| associations[service.type.to_sym].each do |attribute|
service.send("#{attribute}=", service.attributes[attribute.to_s]) service.send("#{attribute}=", service.attributes[attribute.to_s])
end end
service.save! service.save
end end
remove_column :services, :project_url, :string remove_column :services, :project_url, :string
......
...@@ -10,7 +10,7 @@ class MoveSlackServiceToWebhook < ActiveRecord::Migration ...@@ -10,7 +10,7 @@ class MoveSlackServiceToWebhook < ActiveRecord::Migration
slack_service.properties.delete('subdomain') slack_service.properties.delete('subdomain')
# Room is configured on the Slack side # Room is configured on the Slack side
slack_service.properties.delete('room') slack_service.properties.delete('room')
slack_service.save! slack_service.save
end end
end end
end end
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
- [API](api/README.md) Explore how you can access GitLab via a simple and powerful API. - [API](api/README.md) Explore how you can access GitLab via a simple and powerful API.
- [Markdown](markdown/markdown.md) Learn what you can do with GitLab's advanced formatting system. - [Markdown](markdown/markdown.md) Learn what you can do with GitLab's advanced formatting system.
- [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do. - [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do.
- [Project Services](project_services/project_services.md) Explore how project services can integrate a project with external services, such as for CI.
- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to a project. - [Public access](public_access/public_access.md) Learn how you can allow public and internal access to a project.
- [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects. - [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
- [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project. - [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
...@@ -15,6 +16,7 @@ ...@@ -15,6 +16,7 @@
- [Install](install/README.md) Requirements, directory structures and manual installation. - [Install](install/README.md) Requirements, directory structures and manual installation.
- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter. - [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter.
- [Raketasks](raketasks/README.md) Explore what GitLab has in store for you to make administration easier. - [Raketasks](raketasks/README.md) Explore what GitLab has in store for you to make administration easier.
- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough.
- [System hooks](system_hooks/system_hooks.md) Let GitLab notify you when certain management tasks need to be carried out. - [System hooks](system_hooks/system_hooks.md) Let GitLab notify you when certain management tasks need to be carried out.
- [Security](security/README.md) Learn what you can do to further secure your GitLab instance. - [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
- [Update](update/README.md) Update guides to upgrade your installation. - [Update](update/README.md) Update guides to upgrade your installation.
......
...@@ -21,13 +21,7 @@ ...@@ -21,13 +21,7 @@
## Clients ## Clients
- [php-gitlab-api](https://github.com/m4tthumphrey/php-gitlab-api) - PHP Find API Clients for GitLab [on our website](https://about.gitlab.com/applications/#api-clients).
- [Laravel API Wrapper for GitLab CE](https://github.com/adamgoose/gitlab) - PHP / [Laravel](http://laravel.com)
- [Ruby Wrapper](https://github.com/NARKOZ/gitlab) - Ruby
- [python-gitlab](https://github.com/Itxaka/python-gitlab) - Python
- [java-gitlab-api](https://github.com/timols/java-gitlab-api) - Java
- [node-gitlab](https://github.com/moul/node-gitlab) - Node.js
- [NGitLab](https://github.com/Scooletz/NGitLab) - .NET
## Introduction ## Introduction
......
...@@ -211,3 +211,11 @@ Parameters: ...@@ -211,3 +211,11 @@ Parameters:
It return 200 if succeed, 404 if the branch to be deleted does not exist It return 200 if succeed, 404 if the branch to be deleted does not exist
or 400 for other reasons. In case of an error, an explaining message is provided. or 400 for other reasons. In case of an error, an explaining message is provided.
Success response:
```json
{
"branch_name": "my-removed-branch"
}
```
...@@ -93,3 +93,66 @@ Parameters: ...@@ -93,3 +93,66 @@ Parameters:
} }
] ]
``` ```
## Get the comments of a commit
Get the comments of a commit in a project.
```
GET /projects/:id/repository/commits/:sha/comments
```
Parameters:
- `id` (required) - The ID of a project
- `sha` (required) - The name of a repository branch or tag or if not given the default branch
```json
[
{
"note": "this code is really nice",
"author": {
"id": 11,
"username": "admin",
"email": "admin@local.host",
"name": "Administrator",
"state": "active",
"created_at": "2014-03-06T08:17:35.000Z"
}
}
]
```
## Post comment to commit
Adds a comment to a commit. Optionally you can post comments on a specific line of a commit. Therefor both `path`, `line_new` and `line_old` are required.
```
POST /projects/:id/repository/commits/:sha/comments
```
Parameters:
- `id` (required) - The ID of a project
- `sha` (required) - The name of a repository branch or tag or if not given the default branch
- `note` (required) - Text of comment
- `path` (optional) - The file path
- `line` (optional) - The line number
- `line_type` (optional) - The line type (new or old)
```json
{
"author": {
"id": 1,
"username": "admin",
"email": "admin@local.host",
"name": "Administrator",
"blocked": false,
"created_at": "2012-04-29T08:46:00Z"
},
"note": "text1",
"path": "example.rb",
"line": 5,
"line_type": "new"
}
```
...@@ -186,6 +186,7 @@ Parameters: ...@@ -186,6 +186,7 @@ Parameters:
"target_id": 830, "target_id": 830,
"target_type": "Issue", "target_type": "Issue",
"author_id": 1, "author_id": 1,
"author_username": "john",
"data": null, "data": null,
"target_title": "Public project search field" "target_title": "Public project search field"
}, },
...@@ -196,6 +197,7 @@ Parameters: ...@@ -196,6 +197,7 @@ Parameters:
"target_id": null, "target_id": null,
"target_type": null, "target_type": null,
"author_id": 1, "author_id": 1,
"author_username": "john",
"data": { "data": {
"before": "50d4420237a9de7be1304607147aec22e4a14af7", "before": "50d4420237a9de7be1304607147aec22e4a14af7",
"after": "c5feabde2d8cd023215af4d2ceeb7a64839fc428", "after": "c5feabde2d8cd023215af4d2ceeb7a64839fc428",
...@@ -231,6 +233,7 @@ Parameters: ...@@ -231,6 +233,7 @@ Parameters:
"target_id": 840, "target_id": 840,
"target_type": "Issue", "target_type": "Issue",
"author_id": 1, "author_id": 1,
"author_username": "john",
"data": null, "data": null,
"target_title": "Finish & merge Code search PR" "target_title": "Finish & merge Code search PR"
} }
......
...@@ -56,6 +56,7 @@ Parameters: ...@@ -56,6 +56,7 @@ Parameters:
[ [
{ {
"name": "v1.0.0", "name": "v1.0.0",
"message": "Release 1.0.0",
"commit": { "commit": {
"id": "2695effb5807a22ff3d138d593fd856244e155e7", "id": "2695effb5807a22ff3d138d593fd856244e155e7",
"parents": [], "parents": [],
...@@ -67,10 +68,11 @@ Parameters: ...@@ -67,10 +68,11 @@ Parameters:
"committed_date": "2012-05-28T04:42:42-07:00", "committed_date": "2012-05-28T04:42:42-07:00",
"committer_email": "jack@example.com" "committer_email": "jack@example.com"
}, },
"protected": false
} }
] ]
``` ```
The message will be `nil` when creating a lightweight tag otherwise
it will contain the annotation.
It returns 200 if the operation succeed. In case of an error, It returns 200 if the operation succeed. In case of an error,
405 with an explaining error message is returned. 405 with an explaining error message is returned.
......
# Services
## GitLab CI
### Edit GitLab CI service
Set GitLab CI service for a project.
```
PUT /projects/:id/services/gitlab-ci
```
Parameters:
- `token` (required) - CI project token
- `project_url` (required) - CI project url
### Delete GitLab CI service
Delete GitLab CI service settings for a project.
```
DELETE /projects/:id/services/gitlab-ci
```
## Hipchat
### Edit Hipchat service
Set Hipchat service for project.
```
PUT /projects/:id/services/hipchat
```
Parameters:
- `token` (required) - Hipchat token
- `room` (required) - Hipchat room name
### Delete Hipchat service
Delete Hipchat service for a project.
```
DELETE /projects/:id/services/hipchat
```
...@@ -78,7 +78,8 @@ GET /users ...@@ -78,7 +78,8 @@ GET /users
"is_admin": false, "is_admin": false,
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg",
"can_create_group": true, "can_create_group": true,
"can_create_project": true "can_create_project": true,
"projects_limit": 100
} }
] ]
``` ```
...@@ -140,7 +141,8 @@ Parameters: ...@@ -140,7 +141,8 @@ Parameters:
"color_scheme_id": 2, "color_scheme_id": 2,
"is_admin": false, "is_admin": false,
"can_create_group": true, "can_create_group": true,
"can_create_project": true "can_create_project": true,
"projects_limit": 100
} }
``` ```
...@@ -240,7 +242,8 @@ GET /user ...@@ -240,7 +242,8 @@ GET /user
"color_scheme_id": 2, "color_scheme_id": 2,
"is_admin": false, "is_admin": false,
"can_create_group": true, "can_create_group": true,
"can_create_project": true "can_create_project": true,
"projects_limit": 100
} }
``` ```
......
# Custom Git Hooks
**Note: Custom git hooks must be configured on the filesystem of the GitLab
server. Only GitLab server administrators will be able to complete these tasks.
Please explore webhooks as an option if you do not have filesystem access.**
Git natively supports hooks that are executed on different actions.
Examples of server-side git hooks include pre-receive, post-receive, and update.
See
[Git SCM Server-Side Hooks](http://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks)
for more information about each hook type.
As of gitlab-shell version 2.2.0 (which requires GitLab 7.5+), GitLab
administrators can add custom git hooks to any GitLab project.
## Setup
Normally, git hooks are placed in the repository or project's `hooks` directory.
GitLab creates a symlink from each project's `hooks` directory to the
gitlab-shell `hooks` directory for ease of maintenance between gitlab-shell
upgrades. As such, custom hooks are implemented a little differently. Behavior
is exactly the same once the hook is created, though. Follow these steps to
set up a custom hook.
1. Pick a project that needs a custom git hook.
1. On the GitLab server, navigate to the project's repository directory.
For a manual install the path is usually
`/home/git/repositories/<group>/<project>.git`. For Omnibus installs the path is
usually `/var/opt/gitlab/git-data/repositories/<group>/<project>.git`.
1. Create a new directory in this location called `custom_hooks`.
1. Inside the new `custom_hooks` directory, create a file with a name matching
the hook type. For a pre-receive hook the file name should be `pre-receive` with
no extension.
1. Make the hook file executable and make sure it's owned by git.
1. Write the code to make the git hook function as expected. Hooks can be
in any language. Ensure the 'shebang' at the top properly reflects the language
type. For example, if the script is in Ruby the shebang will probably be
`#!/usr/bin/env ruby`.
That's it! Assuming the hook code is properly implemented the hook will fire
as appropriate.
...@@ -76,6 +76,7 @@ Is the system packaged Git too old? Remove it and compile from source. ...@@ -76,6 +76,7 @@ Is the system packaged Git too old? Remove it and compile from source.
cd /tmp cd /tmp
curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.1.2.tar.gz | tar xz curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.1.2.tar.gz | tar xz
cd git-2.1.2/ cd git-2.1.2/
./configure
make prefix=/usr/local all make prefix=/usr/local all
# Install into /usr/local/bin # Install into /usr/local/bin
...@@ -91,7 +92,7 @@ Then select 'Internet Site' and press enter to confirm the hostname. ...@@ -91,7 +92,7 @@ Then select 'Internet Site' and press enter to confirm the hostname.
## 2. Ruby ## 2. Ruby
The use of ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system ruby. The use of Ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system Ruby.
Remove the old Ruby 1.8 if present Remove the old Ruby 1.8 if present
...@@ -126,7 +127,8 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ...@@ -126,7 +127,8 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
# Login to PostgreSQL # Login to PostgreSQL
sudo -u postgres psql -d template1 sudo -u postgres psql -d template1
# Create a user for GitLab. # Create a user for GitLab
# Do not type the 'template1=#', this is part of the prompt
template1=# CREATE USER git CREATEDB; template1=# CREATE USER git CREATEDB;
# Create the GitLab production database & grant all privileges on database # Create the GitLab production database & grant all privileges on database
...@@ -138,6 +140,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ...@@ -138,6 +140,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
# Try connecting to the new database with the new user # Try connecting to the new database with the new user
sudo -u git -H psql -d gitlabhq_production sudo -u git -H psql -d gitlabhq_production
# Quit the database session
gitlabhq_production> \q
## 5. Redis ## 5. Redis
sudo apt-get install redis-server sudo apt-get install redis-server
...@@ -150,6 +155,17 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ...@@ -150,6 +155,17 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
# Enable Redis socket for default Debian / Ubuntu path # Enable Redis socket for default Debian / Ubuntu path
echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf
# Grant permission to the socket to all members of the redis group
echo 'unixsocketperm 770' | sudo tee -a /etc/redis/redis.conf
# Create the directory which contains the socket
mkdir /var/run/redis
chown redis:redis /var/run/redis
chmod 755 /var/run/redis
# Persist the directory which contains the socket, if applicable
if [ -d /etc/tmpfiles.d ]; then
echo 'd /var/run/redis 0755 redis redis 10d -' | sudo tee -a /etc/tmpfiles.d/redis.conf
fi
# Activate the changes to redis.conf # Activate the changes to redis.conf
sudo service redis-server restart sudo service redis-server restart
...@@ -185,7 +201,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ...@@ -185,7 +201,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
# Make sure GitLab can write to the log/ and tmp/ directories # Make sure GitLab can write to the log/ and tmp/ directories
sudo chown -R git log/ sudo chown -R git log/
sudo chown -R git tmp/ sudo chown -R git tmp/
sudo chmod -R u+rwX log/ sudo chmod -R u+rwX,go-w log/
sudo chmod -R u+rwX tmp/ sudo chmod -R u+rwX tmp/
# Create directory for satellites # Create directory for satellites
......
...@@ -24,7 +24,7 @@ For the installations options please see [the installation page on the GitLab we ...@@ -24,7 +24,7 @@ For the installations options please see [the installation page on the GitLab we
On the above unsupported distributions is still possible to install GitLab yourself. On the above unsupported distributions is still possible to install GitLab yourself.
Please see the [manual installation guide](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md) and the [unofficial installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides) on the public wiki for more information. Please see the [manual installation guide](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md) and the [unofficial installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides) on the public wiki for more information.
### Non Unix operating systems such as Windows ### Non-Unix operating systems such as Windows
GitLab is developed for Unix operating systems. GitLab is developed for Unix operating systems.
GitLab does **not** run on Windows and we have no plans of supporting it in the near future. GitLab does **not** run on Windows and we have no plans of supporting it in the near future.
...@@ -50,11 +50,6 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab ...@@ -50,11 +50,6 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab
### Memory ### Memory
- 512MB is the absolute minimum but we do not recommend this amount of memory.
You will either need to configure 512MB or 1.5GB of swap space.
With 512MB of swap space you must configure only one unicorn worker.
With one unicorn worker only git over ssh access will work because the git over http access requires two running workers (one worker to receive the user request and one worker for the authorization check).
If you use SSD storage and configure 1.5GB of swap space you can use two Unicorn workers, this will allow http access but it will still be slow.
- 1GB RAM + 1GB swap supports up to 100 users - 1GB RAM + 1GB swap supports up to 100 users
- **2GB RAM** is the **recommended** memory size and supports up to 500 users - **2GB RAM** is the **recommended** memory size and supports up to 500 users
- 4GB RAM supports up to 2,000 users - 4GB RAM supports up to 2,000 users
...@@ -67,7 +62,7 @@ Notice: The 25 workers of Sidekiq will show up as separate processes in your pro ...@@ -67,7 +62,7 @@ Notice: The 25 workers of Sidekiq will show up as separate processes in your pro
### Storage ### Storage
The necessary hard drive space largely depends on the size of the repos you want to store in GitLab. But as a *rule of thumb* you should have at least twice as much free space as your all repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo. The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least twice as much free space as all your repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo.
If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them. If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them.
...@@ -90,7 +85,7 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o ...@@ -90,7 +85,7 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o
## Supported web browsers ## Supported web browsers
- Chrome (Latest stable version) - Chrome (Latest stable version)
- Firefox (Latest released version) - Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/))
- Safari 7+ (known problem: required fields in html5 do not work) - Safari 7+ (known problem: required fields in html5 do not work)
- Opera (Latest released version) - Opera (Latest released version)
- IE 10+ - IE 10+
# GitLab buttons in gmail
GitLab supports [Google actions in email](https://developers.google.com/gmail/markup/actions/actions-overview).
If correctly setup, emails that require an action will be marked in Gmail.
![gitlab_actions](gitlab_actions.png)
To get this functioning, you need to be registered with Google.
[See how to register with google in this document.](https://developers.google.com/gmail/markup/registering-with-google)
...@@ -7,6 +7,95 @@ The first time a user signs in with LDAP credentials, GitLab will create a new G ...@@ -7,6 +7,95 @@ The first time a user signs in with LDAP credentials, GitLab will create a new G
GitLab user attributes such as nickname and email will be copied from the LDAP user entry. GitLab user attributes such as nickname and email will be copied from the LDAP user entry.
## Configuring GitLab for LDAP integration
To enable GitLab LDAP integration you need to add your LDAP server settings in `/etc/gitlab/gitlab.rb` or `/home/git/gitlab/config/gitlab.yml`.
In GitLab Enterprise Edition you can have multiple LDAP servers connected to one GitLab server.
Please note that before version 7.4, GitLab used a different syntax for configuring LDAP integration.
The old LDAP integration syntax still works in GitLab 7.4.
If your `gitlab.rb` or `gitlab.yml` file contains LDAP settings in both the old syntax and the new syntax, only the __old__ syntax will be used by GitLab.
```ruby
# For omnibus packages
gitlab_rails['ldap_enabled'] = true
gitlab_rails['ldap_servers'] = YAML.load <<-EOS # remember to close this block with 'EOS' below
main: # 'main' is the GitLab 'provider ID' of this LDAP server
## label
#
# 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.
#
# Example: 'Paris' or 'Acme, Ltd.'
label: 'LDAP'
host: '_your_ldap_server'
port: 636
uid: 'sAMAccountName'
method: 'ssl' # "tls" or "ssl" or "plain"
bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
password: '_the_password_of_the_bind_user'
# This setting specifies if LDAP server is Active Directory LDAP server.
# For non AD servers it skips the AD specific queries.
# If your LDAP server is not AD, set this to false.
active_directory: true
# If allow_username_or_email_login is enabled, GitLab will ignore everything
# after the first '@' in the LDAP username submitted by the user on login.
#
# Example:
# - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials;
# - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'.
#
# If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to
# disable this setting, because the userPrincipalName contains an '@'.
allow_username_or_email_login: false
# Base where we can search for users
#
# Ex. ou=People,dc=gitlab,dc=example
#
base: ''
# Filter LDAP users
#
# Format: RFC 4515 http://tools.ietf.org/search/rfc4515
# Ex. (employeeType=developer)
#
# Note: GitLab does not support omniauth-ldap's custom filter syntax.
#
user_filter: ''
# 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:
# ....
EOS
```
If you are using a GitLab installation from source you can find the LDAP settings in `/home/git/gitlab/config/gitlab.yml`:
```
production:
# snip...
ldap:
enabled: false
servers:
main: # 'main' is the GitLab 'provider ID' of this LDAP server
## label
#
# 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.
#
# Example: 'Paris' or 'Acme, Ltd.'
label: 'LDAP'
# snip...
```
## Enabling LDAP sign-in for existing GitLab users ## Enabling LDAP sign-in for existing GitLab users
When a user signs in to GitLab with LDAP for the first time, and their LDAP email address is the primary email address of an existing GitLab user, then the LDAP DN will be associated with the existing user. When a user signs in to GitLab with LDAP for the first time, and their LDAP email address is the primary email address of an existing GitLab user, then the LDAP DN will be associated with the existing user.
...@@ -117,14 +206,21 @@ If you want to limit all GitLab access to a subset of the LDAP users on your LDA ...@@ -117,14 +206,21 @@ If you want to limit all GitLab access to a subset of the LDAP users on your LDA
The filter must comply with [RFC 4515](http://tools.ietf.org/search/rfc4515). The filter must comply with [RFC 4515](http://tools.ietf.org/search/rfc4515).
```ruby ```ruby
# For omnibus-gitlab # For omnibus packages; new LDAP server syntax
gitlab_rails['ldap_user_filter'] = '(employeeType=developer)' gitlab_rails['ldap_servers'] = YAML.load <<-EOS
main:
# snip...
user_filter: '(employeeType=developer)'
EOS
``` ```
```yaml ```yaml
# For installations from source # For installations from source; new LDAP server syntax
production: production:
ldap: ldap:
servers:
main:
# snip...
user_filter: '(employeeType=developer)' user_filter: '(employeeType=developer)'
``` ```
......
...@@ -4,15 +4,23 @@ ...@@ -4,15 +4,23 @@
To enable Slack integration you must create an Incoming WebHooks integration on Slack; To enable Slack integration you must create an Incoming WebHooks integration on Slack;
1. Sign in to [Slack](https://slack.com) (https://YOURSUBDOMAIN.slack.com/services) 1. [Sign in to Slack](https://slack.com/signin)
1. Click on the Integrations menu at the top of the page.
1. Add a new Integration. 1. Select **Configure Integrations** from the dropdown next to your team name.
1. Select the **All Services** tab
1. Click **Add** next to Incoming Webhooks
1. Pick Incoming WebHooks 1. Pick Incoming WebHooks
1. Choose the channel name you want to send notifications to, in the Settings section
1. Add Integrations.
- Optional step; You can change bot's name and avatar by clicking "change the name of your bot", and "change the icon" after that you have to click "Save settings".
Now, Slack is ready to get external hooks. Before you leave this page don't forget to get the Token that you'll need on GitLab. You can find it by clicking Expand button, located in the "Instructions for creating Incoming WebHooks" section. It's a random alpha-numeric text 24 characters long. 1. Choose the channel name you want to send notifications to
1. Click **Add Incoming WebHooks Integration**Add Integrations.
- Optional step; You can change bot's name and avatar by clicking modifying the bot name or avatar under **Integration Settings**.
1. Copy the **Webhook URL**, we'll need this later for GitLab.
## On GitLab ## On GitLab
...@@ -26,10 +34,8 @@ After Slack is ready we need to setup GitLab. Here are the steps to achieve this ...@@ -26,10 +34,8 @@ After Slack is ready we need to setup GitLab. Here are the steps to achieve this
1. Fill in your Slack details 1. Fill in your Slack details
- Mark as active it - Mark it as active
- Type your subdomain's prefix (If your subdomain is https://somedomain.slack.com you only have to type the somedomain) - Paste in the webhook url you got from Slack
- Type in the token you got from Slack
- Type in the channel name you want to use (eg. #announcements)
Have fun :) Have fun :)
......
# Atlassian Bamboo CI Service
GitLab provides integration with Atlassian Bamboo for continuous integration.
When configured, pushes to a project will trigger a build in Bamboo automatically.
Merge requests will also display CI status showing whether the build is pending,
failed, or completed successfully. It also provides a link to the Bamboo build
page for more information.
Bamboo doesn't quite provide the same features as a traditional build system when
it comes to accepting webhooks and commit data. There are a few things that
need to be configured in a Bamboo build plan before GitLab can integrate.
## Setup
### Complete these steps in Bamboo:
1. Navigate to a Bamboo build plan and choose 'Configure plan' from the 'Actions'
dropdown.
1. Select the 'Triggers' tab.
1. Click 'Add trigger'.
1. Enter a description such as 'GitLab trigger'
1. Choose 'Repository triggers the build when changes are committed'
1. Check one or more repositories checkboxes
1. Enter the GitLab IP address in the 'Trigger IP addresses' box. This is a
whitelist of IP addresses that are allowed to trigger Bamboo builds.
1. Save the trigger.
1. In the left pane, select a build stage. If you have multiple build stages
you want to select the last stage that contains the git checkout task.
1. Select the 'Miscellaneous' tab.
1. Under 'Pattern Match Labelling' put '${bamboo.repository.revision.number}'
in the 'Labels' box.
1. Save
Bamboo is now ready to accept triggers from GitLab. Next, set up the Bamboo
service in GitLab
### Complete these steps in GitLab:
1. Navigate to the project you want to configure to trigger builds.
1. Select 'Settings' in the top navigation.
1. Select 'Services' in the left navigation.
1. Click 'Atlassian Bamboo CI'
1. Select the 'Active' checkbox.
1. Enter the base URL of your Bamboo server. 'https://bamboo.example.com'
1. Enter the build key from your Bamboo build plan. Build keys are a short,
all capital letter, identifier that is unique. It will be something like PR-BLD
1. If necessary, enter username and password for a Bamboo user that has
access to trigger the build plan. Leave these fields blank if you do not require
authentication.
1. Save or optionally click 'Test Settings'. Please note that 'Test Settings'
will actually trigger a build in Bamboo.
## Troubleshooting
If builds are not triggered, these are a couple of things to keep in mind.
1. Ensure you entered the right GitLab IP address in Bamboo under 'Trigger
IP addresses'.
1. Remember that GitLab only triggers builds on push events. A commit via the
web interface will not trigger CI currently.
# Project Services
__Project integrations with external services for continuous integration and more.__
## Services
- Assemblia
- [Atlassian Bamboo CI](bamboo.md) An Atlassian product for continous integration.
- Build box
- Campfire
- Emails on push
- Flowdock
- Gemnasium
- GitLab CI
- Hipchat
- PivotalTracker
- Pushover
- Slack
# Import # Import bare repositories into your GitLab instance
### Import bare repositories into GitLab project instance ## Notes
Notes: - The owner of the project will be the first admin
- The groups will be created as needed
- The owner of the group will be the first admin
- Existing projects will be skipped
* project owner will be a first admin ## How to use
* groups will be created as needed
* group owner will be the first admin
* existing projects will be skipped
How to use: ### Create a new folder inside the git repositories path. This will be the name of the new group.
1. copy your bare repos under git repos_path (see `config/gitlab.yml` gitlab_shell -> repos_path) - For omnibus-gitlab, it is located at: `/var/opt/gitlab/git-data/repositories` by default, unless you changed
2. run the command below it in the `/etc/gitlab/gitlab.rb` file.
- For manual installations, it is usually located at: `/home/git/repositories` or you can see where
your repositories are located by looking at `config/gitlab.yml` under the `gitlab_shell => repos_path` entry.
### Copy your bare repositories inside this newly created folder:
```
$ cp -r /old/git/foo.git/ /home/git/repositories/new_group/
```
### Run the command below depending on your type of installation:
#### Omnibus Installation
```
$ sudo gitlab-rake gitlab:import:repos
``` ```
# omnibus-gitlab
sudo gitlab-rake gitlab:import:repos
# installation from source or cookbook #### Manual Installation
bundle exec rake gitlab:import:repos RAILS_ENV=production
Before running this command you need to change the directory to where your GitLab installation is located:
```
$ cd /home/git/gitlab
$ sudo -u git -H bundle exec rake gitlab:import:repos RAILS_ENV=production
``` ```
Example output: #### Example output
``` ```
Processing abcd.git Processing abcd.git
......
...@@ -2,40 +2,42 @@ ...@@ -2,40 +2,42 @@
NOTE: This is a guide for GitLab developers. NOTE: This is a guide for GitLab developers.
# **15th - Code Freeze & Release Manager** # **7 workdays before release - Code Freeze & Release Manager**
### **1. Stop merging in code, except for important bugfixes** ### **1. Stop merging in code, except for important bug fixes**
### **2. Release Manager** ### **2. Release Manager**
A release manager is selected that coordinates the entire release of this version. The release manager has to make sure all the steps below are done and delegated where necessary. This person should also make sure this document is kept up to date and issues are created and updated. A release manager is selected that coordinates the entire release of this version. The release manager has to make sure all the steps below are done and delegated where necessary. This person should also make sure this document is kept up to date and issues are created and updated.
### **3. Create an overall issue** ### **3. Create an overall issue**
Name it "Release x.x.x" for easier searching.
Create issue for GitLab CE project(internal). Name it "Release x.x.x" for easier searching.
Replace the dates with actual dates based on the number of workdays before the release.
``` ```
15th: Xth:
* Update the changelog (#LINK) * Update the changelog (#LINK)
* Triage the omnibus-gitlab milestone * Triage the omnibus-gitlab milestone
16th: Xth:
* Merge CE in to EE (#LINK) * Merge CE in to EE (#LINK)
* Close the omnibus-gitlab milestone * Close the omnibus-gitlab milestone
17th: Xth:
* Create x.x.0.rc1 (#LINK) * Create x.x.0.rc1 (#LINK)
* Build package for GitLab.com (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#build-a-package) * Build package for GitLab.com (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#build-a-package)
18th: Xth:
* Update GitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package) * Update GitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package)
* Regression issue and tweet about rc1 (#LINK) * Regression issue and tweet about rc1 (#LINK)
* Start blog post (#LINK) * Start blog post (#LINK)
21th: Xth:
* Do QA and fix anything coming out of it (#LINK) * Do QA and fix anything coming out of it (#LINK)
...@@ -43,16 +45,13 @@ Name it "Release x.x.x" for easier searching. ...@@ -43,16 +45,13 @@ Name it "Release x.x.x" for easier searching.
* Release CE and EE (#LINK) * Release CE and EE (#LINK)
23rd: Xth:
* Prepare package for GitLab.com release (#LINK)
24th: * * Deploy to GitLab.com (#LINK)
* Deploy to GitLab.com (#LINK)
``` ```
### **4. Update Changelog** ### **4. Update changelog**
Any changes not yet added to the changelog are added by lead developer and in that merge request the complete team is asked if there is anything missing. Any changes not yet added to the changelog are added by lead developer and in that merge request the complete team is asked if there is anything missing.
...@@ -60,26 +59,26 @@ Any changes not yet added to the changelog are added by lead developer and in th ...@@ -60,26 +59,26 @@ Any changes not yet added to the changelog are added by lead developer and in th
Ensure that there is enough time to incorporate the findings of the release candidate, etc. Ensure that there is enough time to incorporate the findings of the release candidate, etc.
# **16th - Merge the CE into EE** # **6 workdays before release- Merge the CE into EE**
Do this via a merge request. Do this via a merge request.
# **17th - Create RC1** # **5 workdays before release - Create RC1**
The RC1 release comes with the task to update the installation and upgrade docs. Be mindful that there might already be merge requests for this on GitLab or GitHub. The RC1 release comes with the task to update the installation and upgrade docs. Be mindful that there might already be merge requests for this on GitLab or GitHub.
### **1. Update the installation guide** ### **1. Update the installation guide**
1. Check if it references the correct branch `x-x-stable` (doesn't exist yet, but that is okay) 1. Check if it references the correct branch `x-x-stable` (doesn't exist yet, but that is okay)
1. Check the [GitLab Shell version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L782) 1. Check the [GitLab Shell version](/lib/tasks/gitlab/check.rake#L782)
1. Check the [Git version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L794) 1. Check the [Git version](/lib/tasks/gitlab/check.rake#L794)
1. There might be other changes. Ask around. 1. There might be other changes. Ask around.
### **2. Create an update guides** ### **2. Create update guides**
1. Create: CE update guide from previous version. Like `from-6-8-to-6.9` 1. Create: CE update guide from previous version. Like `7.3-to-7.4.md`
1. Create: CE to EE update guide in EE repository for latest version. 1. Create: CE to EE update guide in EE repository for latest version.
1. Update: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/6.0-to-6.x.md to latest version. 1. Update: `6.x-or-7.x-to-7.x.md` to latest version.
It's best to copy paste the previous guide and make changes where necessary. It's best to copy paste the previous guide and make changes where necessary.
The typical steps are listed below with any points you should specifically look at. The typical steps are listed below with any points you should specifically look at.
...@@ -98,9 +97,9 @@ List any major changes here, so the user is aware of them before starting to upg ...@@ -98,9 +97,9 @@ List any major changes here, so the user is aware of them before starting to upg
#### 3. Do users need to update dependencies like `git`? #### 3. Do users need to update dependencies like `git`?
- Check if the [GitLab Shell version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L782) changed since the last release. - Check if the [GitLab Shell version](/lib/tasks/gitlab/check.rake#L782) changed since the last release.
- Check if the [Git version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L794) changed since the last release. - Check if the [Git version](/lib/tasks/gitlab/check.rake#L794) changed since the last release.
#### 4. Get latest code #### 4. Get latest code
...@@ -112,19 +111,19 @@ List any major changes here, so the user is aware of them before starting to upg ...@@ -112,19 +111,19 @@ List any major changes here, so the user is aware of them before starting to upg
Check if any of these changed since last release: Check if any of these changed since last release:
- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/lib/support/nginx/gitlab> - [lib/support/nginx/gitlab](/lib/support/nginx/gitlab)
- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/lib/support/nginx/gitlab-ssl> - [lib/support/nginx/gitlab-ssl](/lib/support/nginx/gitlab-ssl)
- <https://gitlab.com/gitlab-org/gitlab-shell/commits/master/config.yml.example> - <https://gitlab.com/gitlab-org/gitlab-shell/commits/master/config.yml.example>
- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/gitlab.yml.example> - [config/gitlab.yml.example](/config/gitlab.yml.example)
- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/unicorn.rb.example> - [config/unicorn.rb.example](/config/unicorn.rb.example)
- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/database.yml.mysql> - [config/database.yml.mysql](/config/database.yml.mysql)
- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/database.yml.postgresql> - [config/database.yml.postgresql](/config/database.yml.postgresql)
- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/initializers/rack_attack.rb.example> - [config/initializers/rack_attack.rb.example](/config/initializers/rack_attack.rb.example)
- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/resque.yml.example> - [config/resque.yml.example](/config/resque.yml.example)
#### 8. Need to update init script? #### 8. Need to update init script?
Check if the `init.d/gitlab` script changed since last release: <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/lib/support/init.d/gitlab> Check if the `init.d/gitlab` script changed since last release: [lib/support/init.d/gitlab](/lib/support/init.d/gitlab)
#### 9. Start application #### 9. Start application
...@@ -144,38 +143,41 @@ Make sure the code quality indicators are green / good. ...@@ -144,38 +143,41 @@ Make sure the code quality indicators are green / good.
- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq) - [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq)
### **4. Set VERSION** ### **4. Run release tool**
Change version in VERSION to `x.x.0.rc1`. **Make sure EE `master` has latest changes from CE `master`**
### **5. Tag** Get release tools
Create an annotated tag that points to the version change commit:
``` ```
git tag -a vx.x.0.rc1 -m 'Version x.x.0.rc1' git clone git@dev.gitlab.org:gitlab/release-tools.git
cd release-tools
``` ```
### **6. Create stable branches** Release candidate creates stable branch from master.
So we need to sync master branch between all CE remotes. Also do same for EE.
For GitLab EE, append `-ee` to the branch. ```
bundle exec rake sync
```
`x-x-stable-ee` Create release candidate and stable branch:
``` ```
git checkout master bundle exec rake release["x.x.0.rc1"]
git pull
git checkout -b x-x-stable
git push <remote> x-x-stable
``` ```
Now developers can use master for merging new features. Now developers can use master for merging new features.
So you should use stable branch for future code chages related to release. So you should use stable branch for future code chages related to release.
# **18th - Release RC1** # **4 workdays before release - Release RC1**
### **1. Determine QA person
### **1. Update GitLab.com** Notify person of QA day.
### **2. Update GitLab.com**
Merge the RC1 EE code into GitLab.com. Merge the RC1 EE code into GitLab.com.
Once the build is green, create a package. Once the build is green, create a package.
...@@ -183,18 +185,20 @@ If there are big database migrations consider testing them with the production d ...@@ -183,18 +185,20 @@ If there are big database migrations consider testing them with the production d
Try to deploy in the morning. Try to deploy in the morning.
It is important to do this as soon as possible, so we can catch any errors before we release the full version. It is important to do this as soon as possible, so we can catch any errors before we release the full version.
### **2. Prepare the blog post** ### **3. Prepare the blog post**
- Start with a complete copy of the [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md) and fill it out. - Start with a complete copy of the [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md) and fill it out.
- Check the changelog of CE and EE for important changes. - Check the changelog of CE and EE for important changes.
- Create a WIP MR for the blog post - Create a WIP MR for the blog post
- Ask Dmitriy to add screenshots to the WIP MR. - Ask Dmitriy to add screenshots to the WIP MR.
- Decide with team who will be the MVP user. - Decide with team who will be the MVP user.
- Create WIP MR for adding MVP to MVP page on website
- Add a note if there are security fixes: This release fixes an important security issue and we advise everyone to upgrade as soon as possible. - Add a note if there are security fixes: This release fixes an important security issue and we advise everyone to upgrade as soon as possible.
- Create a merge request on [GitLab.com](https://gitlab.com/gitlab-com/www-gitlab-com/tree/master)
- Assign to one reviewer who will fix spelling issues by editing the branch (can use the online editor) - Assign to one reviewer who will fix spelling issues by editing the branch (can use the online editor)
- After the reviewer is finished the whole team will be mentioned to give their suggestions via line comments - After the reviewer is finished the whole team will be mentioned to give their suggestions via line comments
### **3. Create a regressions issue** ### **4. Create a regressions issue**
On [the GitLab CE issue tracker on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues/) create an issue titled "GitLab X.X regressions" add the following text: On [the GitLab CE issue tracker on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues/) create an issue titled "GitLab X.X regressions" add the following text:
...@@ -211,7 +215,7 @@ Tweet about the RC release: ...@@ -211,7 +215,7 @@ Tweet about the RC release:
> GitLab x.x.0.rc1 is out. This release candidate is only suitable for testing. Please link regressions issues from LINK_TO_REGRESSION_ISSUE > GitLab x.x.0.rc1 is out. This release candidate is only suitable for testing. Please link regressions issues from LINK_TO_REGRESSION_ISSUE
# **21st - Preparation** # **1 workdays before release - Preparation**
### **1. Pre QA merge** ### **1. Pre QA merge**
...@@ -234,91 +238,64 @@ create an issue about it in order to discuss the next steps after the release. ...@@ -234,91 +238,64 @@ create an issue about it in order to discuss the next steps after the release.
# **22nd - Release CE and EE** # **22nd - Release CE and EE**
For GitLab EE, append `-ee` to the branches and tags. **Make sure EE `x-x-stable-ee` has latest changes from CE `x-x-stable`**
`x-x-stable-ee`
`v.x.x.0-ee`
Note: Merge CE into EE if needed. ### **1. Release code**
### **1. Set VERSION to x.x.x and push** Get release tools
- Change the GITLAB_SHELL_VERSION file in `master` of the CE repository if the version changed.
- Change the GITLAB_SHELL_VERSION file in `master` of the EE repository if the version changed.
- Change the VERSION file in `master` branch of the CE repository and commit and push to origin.
- Change the VERSION file in `master` branch of the EE repository and commit and push to origin.
### **2. Update installation.md**
Update [installation.md](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) to the newest version in master.
### **3. Push latest changes from x-x-stable branch to dev.gitlab.org**
``` ```
git checkout -b x-x-stable git clone git@dev.gitlab.org:gitlab/release-tools.git
git push origin x-x-stable cd release-tools
``` ```
### **4. Build the Omnibus packages** Bump version, create release tag and push to remotes:
Follow the [release doc in the Omnibus repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md).
This can happen before tagging because Omnibus uses tags in its own repo and SHA1's to refer to the GitLab codebase.
### **5. Create annotated tag vx.x.x**
In `x-x-stable` branch check for the SHA-1 of the commit with VERSION file changed. Tag that commit,
``` ```
git tag -a vx.x.0 -m 'Version x.x.0' xxxxx bundle exec rake release["x.x.0"]
``` ```
where `xxxxx` is SHA-1.
### **6. Push the tag and x-x-stable branch to the remotes** ### **2. Update installation.md**
For GitLab CE, push to dev, GitLab.com and GitHub. Update [installation.md](/doc/install/installation.md) to the newest version in master.
For GitLab EE, push to the subscribers repo.
Make sure the branch is marked 'protected' on each of the remotes you pushed to. ### **3. Build the Omnibus packages**
Follow the [release doc in the Omnibus repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md).
This can happen before tagging because Omnibus uses tags in its own repo and SHA1's to refer to the GitLab codebase.
```
git push <remote> x-x-stable(-ee)
git push <remote> vx.x.0
```
### **7. Publish packages for new release** ### **4. Publish packages for new release**
Update `downloads/index.html` and `downloads/archive/index.html` in `www-gitlab-com` repository. Update `downloads/index.html` and `downloads/archive/index.html` in `www-gitlab-com` repository.
### **8. Publish blog for new release** ### **5. Publish blog for new release**
Merge the [blog merge request](#1-prepare-the-blog-post) in `www-gitlab-com` repository. Merge the [blog merge request](#1-prepare-the-blog-post) in `www-gitlab-com` repository.
### **9. Tweet to blog** ### **6. Tweet to blog**
Send out a tweet to share the good news with the world. Send out a tweet to share the good news with the world.
List the most important features and link to the blog post. List the most important features and link to the blog post.
Proposed tweet for CE "GitLab X.X is released! It brings *** <link-to-blogpost>" Proposed tweet for CE "GitLab X.X is released! It brings *** <link-to-blogpost>"
### **10. Send out the newsletter** # **1 workday after release - Update GitLab.com**
Send out an email to the 'GitLab Newsletter' mailing list on MailChimp.
Replicate the former release newsletter and modify it accordingly.
**Do not forget to edit `Subject line` and regenerate `Plain-Text Email` from HTML source**
Include a link to the blog post and keep it short.
Proposed email text: Update GitLab.com from RC1 to the released package.
"We have released a new version of GitLab. See our blog post(<link>) for more information."
# **25th - Release GitLab CI**
# **23rd - Optional Patch Release** - Create the update guid `doc/x.x-to-x.x.md`.
- Update CHANGELOG
# **24th - Update GitLab.com** - Bump version
- Create annotated tags `git tag -a vx.x.0 -m 'Version x.x.0' xxxxx`
- Create stable branch `x-x-stable`
- Create GitHub release post
- Post to blog about release
- Post to twitter
Merge the stable release into GitLab.com. Once the build is green deploy the next morning.
# **25th - Release GitLab CI**
...@@ -10,6 +10,8 @@ Otherwise include it in the monthly release and note there was a regression fix ...@@ -10,6 +10,8 @@ Otherwise include it in the monthly release and note there was a regression fix
## Release Procedure ## Release Procedure
### Preparation
1. Verify that the issue can be reproduced 1. Verify that the issue can be reproduced
1. Note in the 'GitLab X.X regressions' that you will create a patch 1. Note in the 'GitLab X.X regressions' that you will create a patch
1. Create an issue on private GitLab development server 1. Create an issue on private GitLab development server
...@@ -17,15 +19,36 @@ Otherwise include it in the monthly release and note there was a regression fix ...@@ -17,15 +19,36 @@ Otherwise include it in the monthly release and note there was a regression fix
1. Fix the issue on a feature branch, do this on the private GitLab development server 1. Fix the issue on a feature branch, do this on the private GitLab development server
1. Consider creating and testing workarounds 1. Consider creating and testing workarounds
1. After the branch is merged into master, cherry pick the commit(s) into the current stable branch 1. After the branch is merged into master, cherry pick the commit(s) into the current stable branch
1. Make sure that the build has passed and all tests are passing
1. In a separate commit in the stable branch update the CHANGELOG 1. In a separate commit in the stable branch update the CHANGELOG
1. For EE, update the CHANGELOG-EE if it is EE specific fix. Otherwise, merge the stable CE branch and add to CHANGELOG-EE "Merge community edition changes for version X.X.X" 1. For EE, update the CHANGELOG-EE if it is EE specific fix. Otherwise, merge the stable CE branch and add to CHANGELOG-EE "Merge community edition changes for version X.X.X"
1. In a separate commit in the stable branch update the VERSION
1. Create an annotated tag vX.X.X for CE and another patch release for EE `git tag -a vx.x.x -m 'Version x.x.x'` ### Bump version
1. Make sure that the build has passed and all tests are passing
1. Push the code and the tags to all the CE and EE repositories Get release tools
```
git clone git@dev.gitlab.org:gitlab/release-tools.git
cd release-tools
```
Bump version in stable branch, create release tag and push to remotes:
```
bundle exec rake release["x.x.x"]
```
Or if you need to release only EE:
```
CE=false be rake release['x.x.x']
```
### Release
1. Apply the patch to GitLab Cloud and the private GitLab development server 1. Apply the patch to GitLab Cloud and the private GitLab development server
1. [Build new packages with the latest version](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md) 1. [Build new packages with the latest version](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md)
1. Cherry-pick the changelog update back into master 1. Cherry-pick the changelog update back into master
1. Send tweets about the release from `@gitlabhq`, tweet should include the most important feature that the release is addressing as well as the link to the changelog 1. Create and publish a blog post
1. Send tweets about the release from `@gitlabhq`, tweet should include the most important feature that the release is addressing and link to the blog post
1. Note in the 'GitLab X.X regressions' issue that the patch was published (CE only) 1. Note in the 'GitLab X.X regressions' issue that the patch was published (CE only)
1. Send out an email to the 'GitLab Newsletter' mailing list on MailChimp (or the 'Subscribers' list if the patch is EE only)
...@@ -267,6 +267,7 @@ mysql> \q ...@@ -267,6 +267,7 @@ mysql> \q
# Set production -> username: git # Set production -> username: git
# Set production -> password: the password your replaced $password with earlier # Set production -> password: the password your replaced $password with earlier
sudo -u git -H editor /home/git/gitlab/config/database.yml sudo -u git -H editor /home/git/gitlab/config/database.yml
```
## Things went south? Revert to previous version (6.0) ## Things went south? Revert to previous version (6.0)
......
...@@ -74,7 +74,7 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab ...@@ -74,7 +74,7 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
# Enable Redis socket for default Debian / Ubuntu path # Enable Redis socket for default Debian / Ubuntu path
echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf
# Be sure redis group can write to the socket, enable only if supported (>= redis 2.4.0). # Be sure redis group can write to the socket, enable only if supported (>= redis 2.4.0).
sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf sudo sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf
# Activate the changes to redis.conf # Activate the changes to redis.conf
sudo service redis-server restart sudo service redis-server restart
# Add git to the redis group # Add git to the redis group
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
```bash ```bash
cd /home/git/gitlab cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
### 2. Get latest code ### 2. Get latest code
......
...@@ -26,16 +26,14 @@ sudo -u git -H git checkout LATEST_TAG ...@@ -26,16 +26,14 @@ sudo -u git -H git checkout LATEST_TAG
Replace LATEST_TAG with the latest GitLab tag you want to upgrade to, for example `v6.6.3`. Replace LATEST_TAG with the latest GitLab tag you want to upgrade to, for example `v6.6.3`.
### 3. Update gitlab-shell if it is not the latest version ### 3. Update gitlab-shell to the corresponding version
```bash ```bash
cd /home/git/gitlab-shell cd /home/git/gitlab-shell
sudo -u git -H git fetch sudo -u git -H git fetch
sudo -u git -H git checkout LATEST_TAG sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`
``` ```
Replace LATEST_TAG with the latest GitLab Shell tag you want to upgrade to, for example `v1.7.9`.
### 4. Install libs, migrations, etc. ### 4. Install libs, migrations, etc.
```bash ```bash
......
...@@ -43,28 +43,31 @@ Check if GitLab and its dependencies are configured correctly: ...@@ -43,28 +43,31 @@ Check if GitLab and its dependencies are configured correctly:
If all items are green, then congratulations upgrade is complete! If all items are green, then congratulations upgrade is complete!
## 5. Upgrade GitLab Shell (if needed) ## 5. Upgrade GitLab Shell
If the `gitlab:check` task reports an outdated version of `gitlab-shell` you should upgrade it. GitLab Shell might be outdated, running the commands below ensures you're using a compatible version:
Upgrade it by running the commands below after replacing 2.0.1 with the correct version number:
``` ```
cd /home/git/gitlab-shell cd /home/git/gitlab-shell
sudo -u git -H git fetch sudo -u git -H git fetch
sudo -u git -H git checkout v2.0.1 sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`
``` ```
## One line upgrade command ## One line upgrade command
You've read through the entire guide and probably already did all the steps one by one. You've read through the entire guide and probably already did all the steps one by one.
Here is a one line command with step 1 to 4 for the next time you upgrade: Here is a one line command with step 1 to 5 for the next time you upgrade:
```bash ```bash
cd /home/git/gitlab; sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; \ cd /home/git/gitlab; \
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; \
sudo service gitlab stop; \ sudo service gitlab stop; \
if [ -f bin/upgrade.rb ]; then sudo -u git -H ruby bin/upgrade.rb -y; else sudo -u git -H ruby script/upgrade.rb -y; fi; \ if [ -f bin/upgrade.rb ]; then sudo -u git -H ruby bin/upgrade.rb -y; else sudo -u git -H ruby script/upgrade.rb -y; fi; \
cd /home/git/gitlab-shell; \
sudo -u git -H git fetch; \
sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`; \
cd /home/git/gitlab; \
sudo service gitlab start; \ sudo service gitlab start; \
sudo service nginx restart; sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production sudo service nginx restart; sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
``` ```
...@@ -63,6 +63,11 @@ Triggered when a new issue is created or an existing issue was updated/closed/re ...@@ -63,6 +63,11 @@ Triggered when a new issue is created or an existing issue was updated/closed/re
```json ```json
{ {
"object_kind": "issue", "object_kind": "issue",
"user": {
"name": "Administrator",
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
},
"object_attributes": { "object_attributes": {
"id": 301, "id": 301,
"title": "New API: create/update/delete file", "title": "New API: create/update/delete file",
...@@ -92,6 +97,11 @@ Triggered when a new merge request is created or an existing merge request was u ...@@ -92,6 +97,11 @@ Triggered when a new merge request is created or an existing merge request was u
```json ```json
{ {
"object_kind": "merge_request", "object_kind": "merge_request",
"user": {
"name": "Administrator",
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
},
"object_attributes": { "object_attributes": {
"id": 99, "id": 99,
"target_branch": "master", "target_branch": "master",
......
...@@ -83,3 +83,22 @@ Feature: Profile ...@@ -83,3 +83,22 @@ Feature: Profile
Given I visit profile design page Given I visit profile design page
When I change my code preview theme When I change my code preview theme
Then I should receive feedback that the changes were saved Then I should receive feedback that the changes were saved
@javascript
Scenario: I see the password strength indicator
Given I visit profile password page
When I try to set a weak password
Then I should see the input field yellow
@javascript
Scenario: I see the password strength indicator error
Given I visit profile password page
When I try to set a short password
Then I should see the input field red
And I should see the password error message
@javascript
Scenario: I see the password strength indicator with success
Given I visit profile password page
When I try to set a strong password
Then I should see the input field green
\ No newline at end of file
...@@ -60,3 +60,9 @@ Feature: Project Services ...@@ -60,3 +60,9 @@ Feature: Project Services
And I click jira service link And I click jira service link
And I fill jira settings And I fill jira settings
Then I should see jira service settings saved Then I should see jira service settings saved
Scenario: Activate Atlassian Bamboo CI service
When I visit project "Shop" services page
And I click Atlassian Bamboo CI service link
And I fill Atlassian Bamboo CI settings
Then I should see Atlassian Bamboo CI service settings saved
...@@ -29,7 +29,7 @@ class Spinach::Features::EventFilters < Spinach::FeatureSteps ...@@ -29,7 +29,7 @@ class Spinach::Features::EventFilters < Spinach::FeatureSteps
step 'this project has push event' do step 'this project has push event' do
data = { data = {
before: "0000000000000000000000000000000000000000", before: Gitlab::Git::BLANK_SHA,
after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e", after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e",
ref: "refs/heads/new_design", ref: "refs/heads/new_design",
user_id: @user.id, user_id: @user.id,
......
...@@ -10,6 +10,7 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps ...@@ -10,6 +10,7 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
step 'I should see issues authored by me' do step 'I should see issues authored by me' do
should_see(authored_issue) should_see(authored_issue)
should_see(authored_issue_on_public_project)
should_not_see(assigned_issue) should_not_see(assigned_issue)
should_not_see(other_issue) should_not_see(other_issue)
end end
...@@ -22,6 +23,7 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps ...@@ -22,6 +23,7 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
step 'I have authored issues' do step 'I have authored issues' do
authored_issue authored_issue
authored_issue_on_public_project
end end
step 'I have assigned issues' do step 'I have assigned issues' do
...@@ -64,6 +66,10 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps ...@@ -64,6 +66,10 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
@other_issue ||= create :issue, project: project @other_issue ||= create :issue, project: project
end end
def authored_issue_on_public_project
@authored_issue_on_public_project ||= create :issue, author: current_user, project: public_project
end
def project def project
@project ||= begin @project ||= begin
project =create :project project =create :project
...@@ -71,4 +77,8 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps ...@@ -71,4 +77,8 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
project project
end end
end end
def public_project
@public_project ||= create :project, :public
end
end end
...@@ -4,13 +4,17 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps ...@@ -4,13 +4,17 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
step 'I should see merge requests assigned to me' do step 'I should see merge requests assigned to me' do
should_see(assigned_merge_request) should_see(assigned_merge_request)
should_see(assigned_merge_request_from_fork)
should_not_see(authored_merge_request) should_not_see(authored_merge_request)
should_not_see(authored_merge_request_from_fork)
should_not_see(other_merge_request) should_not_see(other_merge_request)
end end
step 'I should see merge requests authored by me' do step 'I should see merge requests authored by me' do
should_see(authored_merge_request) should_see(authored_merge_request)
should_see(authored_merge_request_from_fork)
should_not_see(assigned_merge_request) should_not_see(assigned_merge_request)
should_not_see(assigned_merge_request_from_fork)
should_not_see(other_merge_request) should_not_see(other_merge_request)
end end
...@@ -22,10 +26,12 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps ...@@ -22,10 +26,12 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
step 'I have authored merge requests' do step 'I have authored merge requests' do
authored_merge_request authored_merge_request
authored_merge_request_from_fork
end end
step 'I have assigned merge requests' do step 'I have assigned merge requests' do
assigned_merge_request assigned_merge_request
assigned_merge_request_from_fork
end end
step 'I have other merge requests' do step 'I have other merge requests' do
...@@ -53,15 +59,41 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps ...@@ -53,15 +59,41 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
end end
def assigned_merge_request def assigned_merge_request
@assigned_merge_request ||= create :merge_request, assignee: current_user, target_project: project, source_project: project @assigned_merge_request ||= create :merge_request,
assignee: current_user,
target_project: project,
source_project: project
end end
def authored_merge_request def authored_merge_request
@authored_merge_request ||= create :merge_request, source_branch: 'simple_merge_request', author: current_user, target_project: project, source_project: project @authored_merge_request ||= create :merge_request,
source_branch: 'simple_merge_request',
author: current_user,
target_project: project,
source_project: project
end end
def other_merge_request def other_merge_request
@other_merge_request ||= create :merge_request, source_branch: '2_3_notes_fix', target_project: project, source_project: project @other_merge_request ||= create :merge_request,
source_branch: '2_3_notes_fix',
target_project: project,
source_project: project
end
def authored_merge_request_from_fork
@authored_merge_request_from_fork ||= create :merge_request,
source_branch: 'basic_page',
author: current_user,
target_project: public_project,
source_project: forked_project
end
def assigned_merge_request_from_fork
@assigned_merge_request_from_fork ||= create :merge_request,
source_branch: 'basic_page_fix',
assignee: current_user,
target_project: public_project,
source_project: forked_project
end end
def project def project
...@@ -71,4 +103,12 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps ...@@ -71,4 +103,12 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
project project
end end
end end
def public_project
@public_project ||= create :project, :public
end
def forked_project
@forked_project ||= Projects::ForkService.new(public_project, current_user).execute
end
end end
...@@ -58,16 +58,34 @@ class Spinach::Features::Profile < Spinach::FeatureSteps ...@@ -58,16 +58,34 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
step 'I try change my password w/o old one' do step 'I try change my password w/o old one' do
within '.update-password' do within '.update-password' do
fill_in "user_password", with: "22233344" fill_in "user_password_profile", with: "22233344"
fill_in "user_password_confirmation", with: "22233344" fill_in "user_password_confirmation", with: "22233344"
click_button "Save" click_button "Save"
end end
end end
step 'I try to set a weak password' do
within '.update-password' do
fill_in "user_password_profile", with: "22233344"
end
end
step 'I try to set a short password' do
within '.update-password' do
fill_in "user_password_profile", with: "short"
end
end
step 'I try to set a strong password' do
within '.update-password' do
fill_in "user_password_profile", with: "Itulvo9z8uud%$"
end
end
step 'I change my password' do step 'I change my password' do
within '.update-password' do within '.update-password' do
fill_in "user_current_password", with: "12345678" fill_in "user_current_password", with: "12345678"
fill_in "user_password", with: "22233344" fill_in "user_password_profile", with: "22233344"
fill_in "user_password_confirmation", with: "22233344" fill_in "user_password_confirmation", with: "22233344"
click_button "Save" click_button "Save"
end end
...@@ -76,7 +94,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps ...@@ -76,7 +94,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
step 'I unsuccessfully change my password' do step 'I unsuccessfully change my password' do
within '.update-password' do within '.update-password' do
fill_in "user_current_password", with: "12345678" fill_in "user_current_password", with: "12345678"
fill_in "user_password", with: "password" fill_in "user_password_profile", with: "password"
fill_in "user_password_confirmation", with: "confirmation" fill_in "user_password_confirmation", with: "confirmation"
click_button "Save" click_button "Save"
end end
...@@ -86,6 +104,22 @@ class Spinach::Features::Profile < Spinach::FeatureSteps ...@@ -86,6 +104,22 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
page.should have_content "You must provide a valid current password" page.should have_content "You must provide a valid current password"
end end
step 'I should see the input field yellow' do
page.should have_css 'div.has-warning'
end
step 'I should see the input field green' do
page.should have_css 'div.has-success'
end
step 'I should see the input field red' do
page.should have_css 'div.has-error'
end
step 'I should see the password error message' do
page.should have_content 'Your password is too short'
end
step "I should see a password error message" do step "I should see a password error message" do
page.should have_content "Password confirmation doesn't match" page.should have_content "Password confirmation doesn't match"
end end
...@@ -146,7 +180,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps ...@@ -146,7 +180,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
step 'I submit new password' do step 'I submit new password' do
fill_in :user_current_password, with: '12345678' fill_in :user_current_password, with: '12345678'
fill_in :user_password, with: '12345678' fill_in :user_password_profile, with: '12345678'
fill_in :user_password_confirmation, with: '12345678' fill_in :user_password_confirmation, with: '12345678'
click_button "Set new password" click_button "Set new password"
end end
......
...@@ -4,7 +4,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps ...@@ -4,7 +4,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
include SharedPaths include SharedPaths
step 'change project settings' do step 'change project settings' do
fill_in 'project_name', with: 'NewName' fill_in 'project_name_edit', with: 'NewName'
uncheck 'project_issues_enabled' uncheck 'project_issues_enabled'
end end
......
...@@ -14,6 +14,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps ...@@ -14,6 +14,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
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' page.should have_content 'Pushover'
page.should have_content 'Atlassian Bamboo'
end end
step 'I click gitlab-ci service link' do step 'I click gitlab-ci service link' do
...@@ -108,12 +109,12 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps ...@@ -108,12 +109,12 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
step 'I fill Slack settings' do step 'I fill Slack settings' do
check 'Active' check 'Active'
fill_in 'Webhook', with: 'https://gitlabhq.slack.com/services/hooks?token=cdIj4r4LfXUOySDUjp0tk3OI' fill_in 'Webhook', with: 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685'
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('Webhook').value.should == 'https://gitlabhq.slack.com/services/hooks?token=cdIj4r4LfXUOySDUjp0tk3OI' find_field('Webhook').value.should == 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685'
end end
step 'I click Pushover service link' do step 'I click Pushover service link' do
...@@ -155,5 +156,23 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps ...@@ -155,5 +156,23 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
find_field('Username').value.should == 'gitlab' find_field('Username').value.should == 'gitlab'
find_field('Password').value.should_not == 'gitlab' find_field('Password').value.should_not == 'gitlab'
find_field('Api version').value.should == '2' find_field('Api version').value.should == '2'
step 'I click Atlassian Bamboo CI service link' do
click_link 'Atlassian Bamboo CI'
end
step 'I fill Atlassian Bamboo CI settings' do
check 'Active'
fill_in 'Bamboo url', with: 'http://bamboo.example.com'
fill_in 'Build key', with: 'KEY'
fill_in 'Username', with: 'user'
fill_in 'Password', with: 'verySecret'
click_button 'Save'
end
step 'I should see Atlassian Bamboo CI service settings saved' do
find_field('Bamboo url').value.should == 'http://bamboo.example.com'
find_field('Build key').value.should == 'KEY'
find_field('Username').value.should == 'user'
end end
end end
...@@ -78,7 +78,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -78,7 +78,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end end
step 'I click on "Remove"' do step 'I click on "Remove"' do
click_link 'Remove' click_button 'Remove'
end end
step 'I click on "Remove file"' do step 'I click on "Remove file"' do
......
...@@ -32,7 +32,7 @@ module SharedProject ...@@ -32,7 +32,7 @@ module SharedProject
@project = Project.find_by(name: "Shop") @project = Project.find_by(name: "Shop")
data = { data = {
before: "0000000000000000000000000000000000000000", before: Gitlab::Git::BLANK_SHA,
after: "6d394385cf567f80a8fd85055db1ab4c5295806f", after: "6d394385cf567f80a8fd85055db1ab4c5295806f",
ref: "refs/heads/fix", ref: "refs/heads/fix",
user_id: @user.id, user_id: @user.id,
......
...@@ -82,6 +82,7 @@ module API ...@@ -82,6 +82,7 @@ module API
authorize_push_project authorize_push_project
result = CreateBranchService.new(user_project, current_user). result = CreateBranchService.new(user_project, current_user).
execute(params[:branch_name], params[:ref]) execute(params[:branch_name], params[:ref])
if result[:status] == :success if result[:status] == :success
present result[:branch], present result[:branch],
with: Entities::RepoObject, with: Entities::RepoObject,
...@@ -104,7 +105,9 @@ module API ...@@ -104,7 +105,9 @@ module API
execute(params[:branch]) execute(params[:branch])
if result[:status] == :success if result[:status] == :success
true {
branch_name: params[:branch]
}
else else
render_api_error!(result[:message], result[:return_code]) render_api_error!(result[:message], result[:return_code])
end end
......
...@@ -50,6 +50,67 @@ module API ...@@ -50,6 +50,67 @@ module API
not_found! "Commit" unless commit not_found! "Commit" unless commit
commit.diffs commit.diffs
end end
# Get a commit's comments
#
# Parameters:
# id (required) - The ID of a project
# sha (required) - The commit hash
# Examples:
# GET /projects/:id/repository/commits/:sha/comments
get ':id/repository/commits/:sha/comments' do
sha = params[:sha]
commit = user_project.repository.commit(sha)
not_found! 'Commit' unless commit
notes = Note.where(commit_id: commit.id)
present paginate(notes), with: Entities::CommitNote
end
# Post comment to commit
#
# Parameters:
# id (required) - The ID of a project
# sha (required) - The commit hash
# note (required) - Text of comment
# path (optional) - The file path
# line (optional) - The line number
# line_type (optional) - The type of line (new or old)
# Examples:
# POST /projects/:id/repository/commits/:sha/comments
post ':id/repository/commits/:sha/comments' do
required_attributes! [:note]
sha = params[:sha]
commit = user_project.repository.commit(sha)
not_found! 'Commit' unless commit
opts = {
note: params[:note],
noteable_type: 'Commit',
commit_id: commit.id
}
if params[:path] && params[:line] && params[:line_type]
commit.diffs.each do |diff|
next unless diff.new_path == params[:path]
lines = Gitlab::Diff::Parser.new.parse(diff.diff.lines.to_a)
lines.each do |line|
next unless line.new_pos == params[:line].to_i && line.type == params[:line_type]
break opts[:line_code] = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos)
end
break if opts[:line_code]
end
end
note = ::Notes::CreateService.new(user_project, current_user, opts).execute
if note.save
present note, with: Entities::CommitNote
else
not_found!
end
end
end end
end end
end end
...@@ -16,7 +16,8 @@ module API ...@@ -16,7 +16,8 @@ module API
class UserFull < User class UserFull < User
expose :email expose :email
expose :theme_id, :color_scheme_id, :extern_uid, :provider expose :theme_id, :color_scheme_id, :extern_uid, :provider, \
:projects_limit
expose :can_create_group?, as: :can_create_group expose :can_create_group?, as: :can_create_group
expose :can_create_project?, as: :can_create_project expose :can_create_project?, as: :can_create_project
end end
...@@ -83,6 +84,25 @@ module API ...@@ -83,6 +84,25 @@ module API
end end
end end
class RepoTag < Grape::Entity
expose :name
expose :message do |repo_obj, _options|
if repo_obj.respond_to?(:message)
repo_obj.message
else
nil
end
end
expose :commit do |repo_obj, options|
if repo_obj.respond_to?(:commit)
repo_obj.commit
elsif options[:project]
options[:project].repository.commit(repo_obj.target)
end
end
end
class RepoObject < Grape::Entity class RepoObject < Grape::Entity
expose :name expose :name
...@@ -169,11 +189,25 @@ module API ...@@ -169,11 +189,25 @@ module API
expose :author, using: Entities::UserBasic expose :author, using: Entities::UserBasic
end end
class CommitNote < Grape::Entity
expose :note
expose(:path) { |note| note.diff_file_name }
expose(:line) { |note| note.diff_new_line }
expose(:line_type) { |note| note.diff_line_type }
expose :author, using: Entities::UserBasic
end
class Event < Grape::Entity class Event < Grape::Entity
expose :title, :project_id, :action_name expose :title, :project_id, :action_name
expose :target_id, :target_type, :author_id expose :target_id, :target_type, :author_id
expose :data, :target_title expose :data, :target_title
expose :created_at expose :created_at
expose :author_username do |event, options|
if event.author
event.author.username
end
end
end end
class LdapGroup < Grape::Entity class LdapGroup < Grape::Entity
......
...@@ -178,7 +178,7 @@ module API ...@@ -178,7 +178,7 @@ module API
# DELETE /projects/:id # DELETE /projects/:id
delete ":id" do delete ":id" do
authorize! :remove_project, user_project authorize! :remove_project, user_project
user_project.destroy ::Projects::DestroyService.new(user_project, current_user, {}).execute
end end
# Mark this project as forked from another # Mark this project as forked from another
......
...@@ -23,7 +23,8 @@ module API ...@@ -23,7 +23,8 @@ module API
# Example Request: # Example Request:
# GET /projects/:id/repository/tags # GET /projects/:id/repository/tags
get ":id/repository/tags" do get ":id/repository/tags" do
present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject, project: user_project present user_project.repo.tags.sort_by(&:name).reverse,
with: Entities::RepoTag, project: user_project
end end
# Create tag # Create tag
...@@ -43,7 +44,7 @@ module API ...@@ -43,7 +44,7 @@ module API
if result[:status] == :success if result[:status] == :success
present result[:tag], present result[:tag],
with: Entities::RepoObject, with: Entities::RepoTag,
project: user_project project: user_project
else else
render_api_error!(result[:message], 400) render_api_error!(result[:message], 400)
......
...@@ -28,7 +28,7 @@ module API ...@@ -28,7 +28,7 @@ module API
# Delete GitLab CI service settings # Delete GitLab CI service settings
# #
# Example Request: # Example Request:
# DELETE /projects/:id/keys/:id # DELETE /projects/:id/services/gitlab-ci
delete ":id/services/gitlab-ci" do delete ":id/services/gitlab-ci" do
if user_project.gitlab_ci_service if user_project.gitlab_ci_service
user_project.gitlab_ci_service.update_attributes( user_project.gitlab_ci_service.update_attributes(
...@@ -38,7 +38,41 @@ module API ...@@ -38,7 +38,41 @@ module API
) )
end end
end end
# Set Hipchat service for project
#
# Parameters:
# token (required) - Hipchat token
# room (required) - Hipchat room name
#
# Example Request:
# PUT /projects/:id/services/hipchat
put ':id/services/hipchat' do
required_attributes! [:token, :room]
attrs = attributes_for_keys [:token, :room]
user_project.build_missing_services
if user_project.hipchat_service.update_attributes(
attrs.merge(active: true))
true
else
not_found!
end end
end end
end
# Delete Hipchat service settings
#
# Example Request:
# DELETE /projects/:id/services/hipchat
delete ':id/services/hipchat' do
if user_project.hipchat_service
user_project.hipchat_service.update_attributes(
active: false,
token: nil,
room: nil
)
end
end
end
end
end
...@@ -34,6 +34,7 @@ module Backup ...@@ -34,6 +34,7 @@ module Backup
# Drop all tables because PostgreSQL DB dumps do not contain DROP TABLE # Drop all tables because PostgreSQL DB dumps do not contain DROP TABLE
# statements like MySQL. # statements like MySQL.
Rake::Task["gitlab:db:drop_all_tables"].invoke Rake::Task["gitlab:db:drop_all_tables"].invoke
Rake::Task["gitlab:db:drop_all_postgres_sequences"].invoke
pg_env pg_env
system('psql', config['database'], '-f', db_file_name) system('psql', config['database'], '-f', db_file_name)
end end
......
...@@ -91,7 +91,7 @@ module Backup ...@@ -91,7 +91,7 @@ module Backup
protected protected
def path_to_repo(project) def path_to_repo(project)
File.join(repos_path, project.path_with_namespace + '.git') project.repository.path_to_repo
end end
def path_to_bundle(project) def path_to_bundle(project)
......
# Read about interceptors in http://guides.rubyonrails.org/action_mailer_basics.html#intercepting-emails
class DisableEmailInterceptor
def self.delivering_email(message)
message.perform_deliveries = false
Rails.logger.info "Emails disabled! Interceptor prevented sending mail #{message.subject}"
end
end
module Gitlab module Gitlab
class AppLogger < Gitlab::Logger class AppLogger < Gitlab::Logger
def self.file_name def self.file_name_noext
'application.log' 'application'
end end
def format_message(severity, timestamp, progname, msg) def format_message(severity, timestamp, progname, msg)
......
module Gitlab module Gitlab
class Auth class Auth
def find(login, password) def find(login, password)
user = User.find_by(email: login) || User.find_by(username: login) user = User.by_login(login)
# If no user is found, or it's an LDAP server, try LDAP. # If no user is found, or it's an LDAP server, try LDAP.
# LDAP users are only authenticated via LDAP # LDAP users are only authenticated via LDAP
......
...@@ -90,7 +90,7 @@ module Grack ...@@ -90,7 +90,7 @@ module Grack
when *Gitlab::GitAccess::PUSH_COMMANDS when *Gitlab::GitAccess::PUSH_COMMANDS
if user if user
# Skip user authorization on upload request. # Skip user authorization on upload request.
# It will be serverd by update hook in repository # It will be done by the pre-receive hook in the repository.
true true
else else
false false
......
...@@ -8,6 +8,13 @@ module Gitlab ...@@ -8,6 +8,13 @@ module Gitlab
end end
end end
class << self
def version_required
@version_required ||= File.read(Rails.root.
join('GITLAB_SHELL_VERSION')).strip
end
end
# Init new repository # Init new repository
# #
# name - project path with namespace # name - project path with namespace
...@@ -16,7 +23,8 @@ module Gitlab ...@@ -16,7 +23,8 @@ module Gitlab
# add_repository("gitlab/gitlab-ci") # add_repository("gitlab/gitlab-ci")
# #
def add_repository(name) def add_repository(name)
system "#{gitlab_shell_path}/bin/gitlab-projects", "add-project", "#{name}.git" Gitlab::Utils.system_silent([gitlab_shell_projects_path,
'add-project', "#{name}.git"])
end end
# Import repository # Import repository
...@@ -27,7 +35,8 @@ module Gitlab ...@@ -27,7 +35,8 @@ module Gitlab
# import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git") # import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git")
# #
def import_repository(name, url) def import_repository(name, url)
system "#{gitlab_shell_path}/bin/gitlab-projects", "import-project", "#{name}.git", url, '240' Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'import-project',
"#{name}.git", url, '240'])
end end
# Move repository # Move repository
...@@ -39,7 +48,8 @@ module Gitlab ...@@ -39,7 +48,8 @@ module Gitlab
# mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git") # mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git")
# #
def mv_repository(path, new_path) def mv_repository(path, new_path)
system "#{gitlab_shell_path}/bin/gitlab-projects", "mv-project", "#{path}.git", "#{new_path}.git" Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project',
"#{path}.git", "#{new_path}.git"])
end end
# Update HEAD for repository # Update HEAD for repository
...@@ -51,7 +61,8 @@ module Gitlab ...@@ -51,7 +61,8 @@ module Gitlab
# update_repository_head("gitlab/gitlab-ci", "3-1-stable") # update_repository_head("gitlab/gitlab-ci", "3-1-stable")
# #
def update_repository_head(path, branch) def update_repository_head(path, branch)
system "#{gitlab_shell_path}/bin/gitlab-projects", "update-head", "#{path}.git", branch Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'update-head',
"#{path}.git", branch])
end end
# Fork repository to new namespace # Fork repository to new namespace
...@@ -63,7 +74,8 @@ module Gitlab ...@@ -63,7 +74,8 @@ module Gitlab
# fork_repository("gitlab/gitlab-ci", "randx") # fork_repository("gitlab/gitlab-ci", "randx")
# #
def fork_repository(path, fork_namespace) def fork_repository(path, fork_namespace)
system "#{gitlab_shell_path}/bin/gitlab-projects", "fork-project", "#{path}.git", fork_namespace Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'fork-project',
"#{path}.git", fork_namespace])
end end
# Remove repository from file system # Remove repository from file system
...@@ -74,7 +86,8 @@ module Gitlab ...@@ -74,7 +86,8 @@ module Gitlab
# remove_repository("gitlab/gitlab-ci") # remove_repository("gitlab/gitlab-ci")
# #
def remove_repository(name) def remove_repository(name)
system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-project", "#{name}.git" Gitlab::Utils.system_silent([gitlab_shell_projects_path,
'rm-project', "#{name}.git"])
end end
# Add repository branch from passed ref # Add repository branch from passed ref
...@@ -87,7 +100,8 @@ module Gitlab ...@@ -87,7 +100,8 @@ module Gitlab
# add_branch("gitlab/gitlab-ci", "4-0-stable", "master") # add_branch("gitlab/gitlab-ci", "4-0-stable", "master")
# #
def add_branch(path, branch_name, ref) def add_branch(path, branch_name, ref)
system "#{gitlab_shell_path}/bin/gitlab-projects", "create-branch", "#{path}.git", branch_name, ref Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'create-branch',
"#{path}.git", branch_name, ref])
end end
# Remove repository branch # Remove repository branch
...@@ -99,7 +113,8 @@ module Gitlab ...@@ -99,7 +113,8 @@ module Gitlab
# rm_branch("gitlab/gitlab-ci", "4-0-stable") # rm_branch("gitlab/gitlab-ci", "4-0-stable")
# #
def rm_branch(path, branch_name) def rm_branch(path, branch_name)
system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-branch", "#{path}.git", branch_name Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-branch',
"#{path}.git", branch_name])
end end
# Add repository tag from passed ref # Add repository tag from passed ref
...@@ -117,7 +132,7 @@ module Gitlab ...@@ -117,7 +132,7 @@ module Gitlab
cmd = %W(#{gitlab_shell_path}/bin/gitlab-projects create-tag #{path}.git cmd = %W(#{gitlab_shell_path}/bin/gitlab-projects create-tag #{path}.git
#{tag_name} #{ref}) #{tag_name} #{ref})
cmd << message unless message.nil? || message.empty? cmd << message unless message.nil? || message.empty?
system *cmd Gitlab::Utils.system_silent(cmd)
end end
# Remove repository tag # Remove repository tag
...@@ -129,7 +144,8 @@ module Gitlab ...@@ -129,7 +144,8 @@ module Gitlab
# rm_tag("gitlab/gitlab-ci", "v4.0") # rm_tag("gitlab/gitlab-ci", "v4.0")
# #
def rm_tag(path, tag_name) def rm_tag(path, tag_name)
system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-tag", "#{path}.git", tag_name Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-tag',
"#{path}.git", tag_name])
end end
# Add new key to gitlab-shell # Add new key to gitlab-shell
...@@ -138,7 +154,8 @@ module Gitlab ...@@ -138,7 +154,8 @@ module Gitlab
# add_key("key-42", "sha-rsa ...") # add_key("key-42", "sha-rsa ...")
# #
def add_key(key_id, key_content) def add_key(key_id, key_content)
system "#{gitlab_shell_path}/bin/gitlab-keys", "add-key", key_id, key_content Gitlab::Utils.system_silent([gitlab_shell_keys_path,
'add-key', key_id, key_content])
end end
# Batch-add keys to authorized_keys # Batch-add keys to authorized_keys
...@@ -157,7 +174,8 @@ module Gitlab ...@@ -157,7 +174,8 @@ module Gitlab
# remove_key("key-342", "sha-rsa ...") # remove_key("key-342", "sha-rsa ...")
# #
def remove_key(key_id, key_content) def remove_key(key_id, key_content)
system "#{gitlab_shell_path}/bin/gitlab-keys", "rm-key", key_id, key_content Gitlab::Utils.system_silent([gitlab_shell_keys_path,
'rm-key', key_id, key_content])
end end
# Remove all ssh keys from gitlab shell # Remove all ssh keys from gitlab shell
...@@ -166,7 +184,7 @@ module Gitlab ...@@ -166,7 +184,7 @@ module Gitlab
# remove_all_keys # remove_all_keys
# #
def remove_all_keys def remove_all_keys
system "#{gitlab_shell_path}/bin/gitlab-keys", "clear" Gitlab::Utils.system_silent([gitlab_shell_keys_path, 'clear'])
end end
# Add empty directory for storing repositories # Add empty directory for storing repositories
...@@ -249,5 +267,13 @@ module Gitlab ...@@ -249,5 +267,13 @@ module Gitlab
def exists?(dir_name) def exists?(dir_name)
File.exists?(full_path(dir_name)) File.exists?(full_path(dir_name))
end end
def gitlab_shell_projects_path
File.join(gitlab_shell_path, 'bin', 'gitlab-projects')
end
def gitlab_shell_keys_path
File.join(gitlab_shell_path, 'bin', 'gitlab-keys')
end
end end
end end
module Gitlab
module Git
BLANK_SHA = '0' * 40
end
end
...@@ -67,7 +67,7 @@ module Gitlab ...@@ -67,7 +67,7 @@ module Gitlab
if forced_push?(project, oldrev, newrev) if forced_push?(project, oldrev, newrev)
:force_push_code_to_protected_branches :force_push_code_to_protected_branches
# and we dont allow remove of protected branch # and we dont allow remove of protected branch
elsif newrev =~ /0000000/ elsif newrev == Gitlab::Git::BLANK_SHA
:remove_protected_branches :remove_protected_branches
else else
:push_code_to_protected_branches :push_code_to_protected_branches
...@@ -86,7 +86,7 @@ module Gitlab ...@@ -86,7 +86,7 @@ module Gitlab
def forced_push?(project, oldrev, newrev) def forced_push?(project, oldrev, newrev)
return false if project.empty_repo? return false if project.empty_repo?
if oldrev !~ /00000000/ && newrev !~ /00000000/ if oldrev != Gitlab::Git::BLANK_SHA && newrev != Gitlab::Git::BLANK_SHA
missed_refs = IO.popen(%W(git --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev})).read missed_refs = IO.popen(%W(git --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev})).read
missed_refs.split("\n").size > 0 missed_refs.split("\n").size > 0
else else
......
module Gitlab module Gitlab
class GitLogger < Gitlab::Logger class GitLogger < Gitlab::Logger
def self.file_name def self.file_name_noext
'githost.log' 'githost'
end end
def format_message(severity, timestamp, progname, msg) def format_message(severity, timestamp, progname, msg)
......
...@@ -5,7 +5,8 @@ module Gitlab ...@@ -5,7 +5,8 @@ module Gitlab
# #
# Returns true for a valid reference name, false otherwise # Returns true for a valid reference name, false otherwise
def validate(ref_name) def validate(ref_name)
system *%W(git check-ref-format refs/#{ref_name}) Gitlab::Utils.system_silent(
%W(git check-ref-format refs/#{ref_name}))
end end
end end
end end
...@@ -15,7 +15,6 @@ module Gitlab ...@@ -15,7 +15,6 @@ module Gitlab
{ title: "support", color: yellow }, { title: "support", color: yellow },
{ title: "discussion", color: blue }, { title: "discussion", color: blue },
{ title: "suggestion", color: blue }, { title: "suggestion", color: blue },
{ title: "feature", color: green },
{ title: "enhancement", color: green } { title: "enhancement", color: green }
] ]
......
module Gitlab module Gitlab
class Logger < ::Logger class Logger < ::Logger
def self.file_name
file_name_noext + '.log'
end
def self.error(message) def self.error(message)
build.error(message) build.error(message)
end end
......
...@@ -202,7 +202,7 @@ module Gitlab ...@@ -202,7 +202,7 @@ module Gitlab
if identifier == "all" if identifier == "all"
link_to("@all", project_url(project), options) link_to("@all", project_url(project), options)
elsif user = User.find_by(username: identifier) elsif User.find_by(username: identifier)
link_to("@#{identifier}", user_url(identifier), options) link_to("@#{identifier}", user_url(identifier), options)
end end
end end
......
module Gitlab
class ProductionLogger < Gitlab::Logger
def self.file_name_noext
'production'
end
end
end
...@@ -67,8 +67,7 @@ module Gitlab ...@@ -67,8 +67,7 @@ module Gitlab
def default_regex_message def default_regex_message
"can contain only letters, digits, '_', '-' and '.'. " \ "can contain only letters, digits, '_', '-' and '.'. " \
"It must start with letter, digit or '_', optionally preceeded by '.'. " \ "Cannot start with '-' or end in '.git'" \
"It must not end in '.git'."
end end
def default_regex def default_regex
......
module Gitlab
class SidekiqLogger < Gitlab::Logger
def self.file_name_noext
'sidekiq'
end
end
end
...@@ -19,7 +19,7 @@ module Gitlab ...@@ -19,7 +19,7 @@ module Gitlab
issue = Issue.find(id) issue = Issue.find(id)
project_issue_url(id: issue.iid, project_issue_url(id: issue.iid,
project_id: issue.project, project_id: issue.project,
host: Settings.gitlab['url']) host: Gitlab.config.gitlab['url'])
end end
end end
end end
module Gitlab
module Utils
extend self
# Run system command without outputting to stdout.
#
# @param cmd [Array<String>]
# @return [Boolean]
def system_silent(cmd)
Popen::popen(cmd).last.zero?
end
end
end
## GitLab ## GitLab
## Maintainer: @randx ## Contributors: randx, yin8086, sashkab, orkoden, axilleas, bbodenmiller
## ##
## Lines starting with two hashes (##) are comments with information. ## Lines starting with two hashes (##) are comments with information.
## Lines starting with one hash (#) are configuration parameters that can be uncommented. ## Lines starting with one hash (#) are configuration parameters that can be uncommented.
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
## configuration ## ## configuration ##
################################### ###################################
## ##
## See installation.md#using-https for additional HTTPS configuration details.
upstream gitlab { upstream gitlab {
server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0; server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0;
...@@ -33,7 +34,8 @@ upstream gitlab { ...@@ -33,7 +34,8 @@ upstream gitlab {
## Normal HTTP host ## Normal HTTP host
server { server {
listen *:80 default_server; listen 0.0.0.0:80 default_server;
listen [::]:80 default_server;
server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com
server_tokens off; ## Don't show the nginx version number, a security best practice server_tokens off; ## Don't show the nginx version number, a security best practice
root /home/git/gitlab/public; root /home/git/gitlab/public;
...@@ -42,6 +44,8 @@ server { ...@@ -42,6 +44,8 @@ server {
## Or if you want to accept large git objects over http ## Or if you want to accept large git objects over http
client_max_body_size 20m; client_max_body_size 20m;
## See app/controllers/application_controller.rb for headers set
## Individual nginx logs for this GitLab vhost ## Individual nginx logs for this GitLab vhost
access_log /var/log/nginx/gitlab_access.log; access_log /var/log/nginx/gitlab_access.log;
error_log /var/log/nginx/gitlab_error.log; error_log /var/log/nginx/gitlab_error.log;
......
## GitLab ## GitLab
## Contributors: randx, yin8086, sashkab, orkoden, axilleas ## Contributors: randx, yin8086, sashkab, orkoden, axilleas, bbodenmiller
## ##
## Modified from nginx http version ## Modified from nginx http version
## Modified from http://blog.phusion.nl/2012/04/21/tutorial-setting-up-gitlab-on-debian-6/ ## Modified from http://blog.phusion.nl/2012/04/21/tutorial-setting-up-gitlab-on-debian-6/
...@@ -26,9 +26,8 @@ ...@@ -26,9 +26,8 @@
## [1] https://github.com/agentzh/chunkin-nginx-module#status ## [1] https://github.com/agentzh/chunkin-nginx-module#status
## [2] https://github.com/agentzh/chunkin-nginx-module ## [2] https://github.com/agentzh/chunkin-nginx-module
## ##
##
################################### ###################################
## SSL configuration ## ## configuration ##
################################### ###################################
## ##
## See installation.md#using-https for additional HTTPS configuration details. ## See installation.md#using-https for additional HTTPS configuration details.
...@@ -37,22 +36,24 @@ upstream gitlab { ...@@ -37,22 +36,24 @@ upstream gitlab {
server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0; server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0;
} }
## Normal HTTP host ## Redirects all HTTP traffic to the HTTPS host
server { server {
listen *:80 default_server; listen 0.0.0.0:80;
listen [::]:80 default_server;
server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com
server_tokens off; ## Don't show the nginx version number, a security best practice server_tokens off; ## Don't show the nginx version number, a security best practice
return 301 https://$server_name$request_uri;
## Redirects all traffic to the HTTPS host access_log /var/log/nginx/gitlab_access.log;
root /nowhere; ## root doesn't have to be a valid path since we are redirecting error_log /var/log/nginx/gitlab_error.log;
rewrite ^ https://$server_name$request_uri? permanent;
} }
## HTTPS host ## HTTPS host
server { server {
listen 443 ssl; listen 0.0.0.0:443 ssl;
listen [::]:443 ssl default_server;
server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com
server_tokens off; server_tokens off; ## Don't show the nginx version number, a security best practice
root /home/git/gitlab/public; root /home/git/gitlab/public;
## Increase this if you want to upload large attachments ## Increase this if you want to upload large attachments
...@@ -60,24 +61,19 @@ server { ...@@ -60,24 +61,19 @@ server {
client_max_body_size 20m; client_max_body_size 20m;
## Strong SSL Security ## Strong SSL Security
## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html & https://cipherli.st/
ssl on; ssl on;
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;
# GitLab needs backwards compatible ciphers to retain compatibility with Java IDEs # 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_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:!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_prefer_server_ciphers on; ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 5m;
## [WARNING] The following header states that the browser should only communicate ## See app/controllers/application_controller.rb for headers set
## with your server over a secure connection for the next 24 months.
add_header Strict-Transport-Security max-age=63072000;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
## [Optional] If your certficate has OCSP, enable OCSP stapling to reduce the overhead and latency of running SSL. ## [Optional] If your certficate has OCSP, enable OCSP stapling to reduce the overhead and latency of running SSL.
## Replace with your ssl_trusted_certificate. For more info see: ## Replace with your ssl_trusted_certificate. For more info see:
...@@ -88,7 +84,7 @@ server { ...@@ -88,7 +84,7 @@ server {
# ssl_stapling_verify on; # ssl_stapling_verify on;
# ssl_trusted_certificate /etc/nginx/ssl/stapling.trusted.crt; # ssl_trusted_certificate /etc/nginx/ssl/stapling.trusted.crt;
# resolver 208.67.222.222 208.67.222.220 valid=300s; # Can change to your DNS resolver if desired # resolver 208.67.222.222 208.67.222.220 valid=300s; # Can change to your DNS resolver if desired
# resolver_timeout 10s; # resolver_timeout 5s;
## [Optional] Generate a stronger DHE parameter: ## [Optional] Generate a stronger DHE parameter:
## sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096 ## sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096
......
...@@ -574,20 +574,16 @@ namespace :gitlab do ...@@ -574,20 +574,16 @@ namespace :gitlab do
Gitlab::Shell.new.version Gitlab::Shell.new.version
end end
def required_gitlab_shell_version
File.read(File.join(Rails.root, "GITLAB_SHELL_VERSION")).strip
end
def gitlab_shell_major_version def gitlab_shell_major_version
required_gitlab_shell_version.split(".")[0].to_i Gitlab::Shell.version_required.split('.')[0].to_i
end end
def gitlab_shell_minor_version def gitlab_shell_minor_version
required_gitlab_shell_version.split(".")[1].to_i Gitlab::Shell.version_required.split('.')[1].to_i
end end
def gitlab_shell_patch_version def gitlab_shell_patch_version
required_gitlab_shell_version.split(".")[2].to_i Gitlab::Shell.version_required.split('.')[2].to_i
end end
def has_gitlab_shell3? def has_gitlab_shell3?
......
...@@ -92,11 +92,11 @@ namespace :gitlab do ...@@ -92,11 +92,11 @@ namespace :gitlab do
User.ldap.each do |ldap_user| User.ldap.each do |ldap_user|
print "#{ldap_user.name} (#{ldap_user.extern_uid}) ..." print "#{ldap_user.name} (#{ldap_user.extern_uid}) ..."
if Gitlab::LDAP::Access.open { |access| access.allowed?(ldap_user) } if Gitlab::LDAP::Access.allowed?(ldap_user)
puts " [OK]".green puts " [OK]".green
else else
if block_flag if block_flag
ldap_user.block! ldap_user.block! unless ldap_user.blocked?
puts " [BLOCKED]".red puts " [BLOCKED]".red
else else
puts " [NOT IN LDAP]".yellow puts " [NOT IN LDAP]".yellow
......
namespace :gitlab do
namespace :db do
task drop_all_postgres_sequences: :environment do
connection = ActiveRecord::Base.connection
connection.execute("SELECT c.relname FROM pg_class c WHERE c.relkind = 'S';").each do |sequence|
connection.execute("DROP SEQUENCE #{sequence['relname']}")
end
end
end
end
...@@ -15,26 +15,17 @@ namespace :gitlab do ...@@ -15,26 +15,17 @@ namespace :gitlab do
git_base_path = Gitlab.config.gitlab_shell.repos_path git_base_path = Gitlab.config.gitlab_shell.repos_path
repos_to_import = Dir.glob(git_base_path + '/**/*.git') repos_to_import = Dir.glob(git_base_path + '/**/*.git')
namespaces = Namespace.pluck(:path)
repos_to_import.each do |repo_path| repos_to_import.each do |repo_path|
# strip repo base path # strip repo base path
repo_path[0..git_base_path.length] = '' repo_path[0..git_base_path.length] = ''
path = repo_path.sub(/\.git$/, '') path = repo_path.sub(/\.git$/, '')
name = File.basename path group_name, name = File.split(path)
group_name = File.dirname path
group_name = nil if group_name == '.' group_name = nil if group_name == '.'
# Skip if group or user
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
if path =~ /.wiki\Z/ if path =~ /\.wiki\Z/
puts " * Skipping wiki repo" puts " * Skipping wiki repo"
next next
end end
...@@ -53,9 +44,9 @@ namespace :gitlab do ...@@ -53,9 +44,9 @@ namespace :gitlab do
# find group namespace # find group namespace
if group_name if group_name
group = Group.find_by(path: group_name) group = Namespace.find_by(path: group_name)
# create group namespace # create group namespace
if !group unless group
group = Group.new(:name => group_name) group = Group.new(:name => group_name)
group.path = group_name group.path = group_name
group.owner = user group.owner = user
......
...@@ -4,20 +4,20 @@ namespace :gitlab do ...@@ -4,20 +4,20 @@ namespace :gitlab do
task :install, [:tag, :repo] => :environment do |t, args| task :install, [:tag, :repo] => :environment do |t, args|
warn_user_is_not_gitlab warn_user_is_not_gitlab
default_version = File.read(File.join(Rails.root, "GITLAB_SHELL_VERSION")).strip default_version = Gitlab::Shell.version_required
args.with_defaults(tag: 'v' + default_version, repo: "https://gitlab.com/gitlab-org/gitlab-shell.git") args.with_defaults(tag: 'v' + default_version, repo: "https://gitlab.com/gitlab-org/gitlab-shell.git")
user = Settings.gitlab.user user = Gitlab.config.gitlab.user
home_dir = Rails.env.test? ? Rails.root.join('tmp/tests') : Settings.gitlab.user_home home_dir = Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home
gitlab_url = Settings.gitlab.url gitlab_url = Gitlab.config.gitlab.url
# gitlab-shell requires a / at the end of the url # gitlab-shell requires a / at the end of the url
gitlab_url += "/" unless gitlab_url.match(/\/$/) gitlab_url += '/' unless gitlab_url.end_with?('/')
repos_path = Gitlab.config.gitlab_shell.repos_path repos_path = Gitlab.config.gitlab_shell.repos_path
target_dir = Gitlab.config.gitlab_shell.path target_dir = Gitlab.config.gitlab_shell.path
# Clone if needed # Clone if needed
unless File.directory?(target_dir) unless File.directory?(target_dir)
sh "git clone '#{args.repo}' '#{target_dir}'" sh(*%W(git clone #{args.repo} #{target_dir}))
end end
# Make sure we're on the right tag # Make sure we're on the right tag
...@@ -76,7 +76,7 @@ namespace :gitlab do ...@@ -76,7 +76,7 @@ namespace :gitlab do
desc "GITLAB | Build missing projects" desc "GITLAB | Build missing projects"
task build_missing_projects: :environment do task build_missing_projects: :environment do
Project.find_each(batch_size: 1000) do |project| Project.find_each(batch_size: 1000) do |project|
path_to_repo = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git") path_to_repo = project.repository.path_to_repo
if File.exists?(path_to_repo) if File.exists?(path_to_repo)
print '-' print '-'
else else
......
require 'spec_helper' require 'spec_helper'
describe "Projects", feature: true do describe "Projects", feature: true, js: true do
before { login_as :user } before { login_as :user }
describe "DELETE /projects/:id" do describe "DELETE /projects/:id" do
...@@ -10,12 +10,23 @@ describe "Projects", feature: true do ...@@ -10,12 +10,23 @@ describe "Projects", feature: true do
visit edit_project_path(@project) visit edit_project_path(@project)
end end
it "should be correct path", js: true do it "should remove project" do
expect { expect { remove_project }.to change {Project.count}.by(-1)
end
it 'should delete the project from disk' do
expect(GitlabShellWorker).to(
receive(:perform_async).with(:remove_repository,
/#{@project.path_with_namespace}/)
).twice
remove_project
end
end
def remove_project
click_link "Remove project" click_link "Remove project"
fill_in 'confirm_name_input', with: @project.path fill_in 'confirm_name_input', with: @project.path
click_button 'Confirm' click_button 'Confirm'
}.to change {Project.count}.by(-1)
end
end end
end end
...@@ -11,7 +11,7 @@ describe 'Users', feature: true do ...@@ -11,7 +11,7 @@ describe 'Users', feature: true do
fill_in "user_name", with: "Name Surname" fill_in "user_name", with: "Name Surname"
fill_in "user_username", with: "Great" fill_in "user_username", with: "Great"
fill_in "user_email", with: "name@mail.com" fill_in "user_email", with: "name@mail.com"
fill_in "user_password", with: "password1234" fill_in "user_password_sign_up", with: "password1234"
fill_in "user_password_confirmation", with: "password1234" fill_in "user_password_confirmation", with: "password1234"
expect { click_button "Sign up" }.to change {User.count}.by(1) expect { click_button "Sign up" }.to change {User.count}.by(1)
end end
......
...@@ -640,7 +640,9 @@ describe GitlabMarkdownHelper do ...@@ -640,7 +640,9 @@ 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=\"http://localhost/assets/emoji/smile.png") markdown(':smile:').should(
include(%(src="#{Gitlab.config.gitlab.url}/assets/emoji/smile.png))
)
end end
it "should generate absolute urls for emoji if relative url is present" do it "should generate absolute urls for emoji if relative url is present" do
......
require 'spec_helper'
describe DisableEmailInterceptor do
before do
ActionMailer::Base.register_interceptor(DisableEmailInterceptor)
end
it 'should not send emails' do
Gitlab.config.gitlab.stub(:email_enabled).and_return(false)
expect {
deliver_mail
}.not_to change(ActionMailer::Base.deliveries, :count)
end
after do
# Removing interceptor from the list because unregister_interceptor is
# implemented in later version of mail gem
# See: https://github.com/mikel/mail/pull/705
Mail.class_variable_set(:@@delivery_interceptors, [])
end
def deliver_mail
key = create :personal_key
Notify.new_ssh_key_email(key.id)
end
end
...@@ -10,13 +10,21 @@ describe Gitlab::Auth do ...@@ -10,13 +10,21 @@ describe Gitlab::Auth do
password: password, password: password,
password_confirmation: password) password_confirmation: password)
end end
let(:username) { 'john' } let(:username) { 'John' } # username isn't lowercase, test this
let(:password) { 'my-secret' } let(:password) { 'my-secret' }
it "should find user by valid login/password" do it "should find user by valid login/password" do
expect( gl_auth.find(username, password) ).to eql user expect( gl_auth.find(username, password) ).to eql user
end end
it 'should find user by valid email/password with case-insensitive email' do
expect(gl_auth.find(user.email.upcase, password)).to eql user
end
it 'should find user by valid username/password with case-insensitive username' do
expect(gl_auth.find(username.upcase, password)).to eql user
end
it "should not find user with invalid password" do it "should not find user with invalid password" do
password = 'wrong' password = 'wrong'
expect( gl_auth.find(username, password) ).to_not eql user expect( gl_auth.find(username, password) ).to_not eql user
......
...@@ -55,12 +55,13 @@ describe Gitlab::GitAccess do ...@@ -55,12 +55,13 @@ describe Gitlab::GitAccess do
def changes def changes
{ {
push_new_branch: '000000000 570e7b2ab refs/heads/wow', push_new_branch: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/heads/wow",
push_master: '6f6d7e7ed 570e7b2ab refs/heads/master', push_master: '6f6d7e7ed 570e7b2ab refs/heads/master',
push_protected_branch: '6f6d7e7ed 570e7b2ab refs/heads/feature', push_protected_branch: '6f6d7e7ed 570e7b2ab refs/heads/feature',
push_remove_protected_branch: '570e7b2ab 000000000 refs/heads/feature', push_remove_protected_branch: "570e7b2ab #{Gitlab::Git::BLANK_SHA} "\
'refs/heads/feature',
push_tag: '6f6d7e7ed 570e7b2ab refs/tags/v1.0.0', push_tag: '6f6d7e7ed 570e7b2ab refs/tags/v1.0.0',
push_new_tag: '000000000 570e7b2ab refs/tags/v7.8.9', push_new_tag: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/tags/v7.8.9",
push_all: ['6f6d7e7ed 570e7b2ab refs/heads/master', '6f6d7e7ed 570e7b2ab refs/heads/feature'] push_all: ['6f6d7e7ed 570e7b2ab refs/heads/master', '6f6d7e7ed 570e7b2ab refs/heads/feature']
} }
end end
......
...@@ -12,3 +12,16 @@ describe Mentionable do ...@@ -12,3 +12,16 @@ describe Mentionable do
end end
end end
end end
describe Issue, "Mentionable" do
describe :mentioned_users do
let!(:user) { create(:user, username: 'stranger') }
let!(:user2) { create(:user, username: 'john') }
let!(:issue) { create(:issue, description: '@stranger mentioned') }
subject { issue.mentioned_users }
it { should include(user) }
it { should_not include(user2) }
end
end
...@@ -36,7 +36,7 @@ describe Event do ...@@ -36,7 +36,7 @@ describe Event do
@user = project.owner @user = project.owner
data = { data = {
before: "0000000000000000000000000000000000000000", before: Gitlab::Git::BLANK_SHA,
after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e", after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e",
ref: "refs/heads/master", ref: "refs/heads/master",
user_id: @user.id, user_id: @user.id,
...@@ -60,7 +60,6 @@ describe Event do ...@@ -60,7 +60,6 @@ describe Event do
it { @event.push?.should be_true } it { @event.push?.should be_true }
it { @event.proper?.should be_true } it { @event.proper?.should be_true }
it { @event.new_branch?.should be_true }
it { @event.tag?.should be_false } it { @event.tag?.should be_false }
it { @event.branch_name.should == "master" } it { @event.branch_name.should == "master" }
it { @event.author.should == @user } it { @event.author.should == @user }
......
...@@ -34,11 +34,11 @@ describe GitlabCiService do ...@@ -34,11 +34,11 @@ describe GitlabCiService do
end end
describe :commit_status_path do describe :commit_status_path do
it { @service.commit_status_path("2ab7834c").should == "http://ci.gitlab.org/projects/2/builds/2ab7834c/status.json?token=verySecret"} it { @service.commit_status_path("2ab7834c").should == "http://ci.gitlab.org/projects/2/commits/2ab7834c/status.json?token=verySecret"}
end end
describe :build_page do describe :build_page do
it { @service.build_page("2ab7834c").should == "http://ci.gitlab.org/projects/2/builds/2ab7834c"} it { @service.build_page("2ab7834c").should == "http://ci.gitlab.org/projects/2/commits/2ab7834c"}
end end
end end
end end
...@@ -249,6 +249,12 @@ describe Note do ...@@ -249,6 +249,12 @@ describe Note do
its(:note) { should == "_mentioned in merge request !#{mergereq.iid}_" } its(:note) { should == "_mentioned in merge request !#{mergereq.iid}_" }
end end
context 'commit contained in a merge request' do
subject { Note.create_cross_reference_note(mergereq.commits.first, mergereq, author, project) }
it { should be_nil }
end
context 'commit from issue' do context 'commit from issue' do
subject { Note.create_cross_reference_note(commit, issue, author, project) } subject { Note.create_cross_reference_note(commit, issue, author, project) }
......
...@@ -145,63 +145,6 @@ describe Project do ...@@ -145,63 +145,6 @@ describe Project do
end end
end end
describe 'comment merge requests with commits' do
before do
@user = create(:user)
group = create(:group)
group.add_owner(@user)
@project = create(:project, namespace: group)
@fork_project = Projects::ForkService.new(@project, @user).execute
@merge_request = create(:merge_request, source_project: @project,
source_branch: 'master',
target_branch: 'feature',
target_project: @project)
@fork_merge_request = create(:merge_request, source_project: @fork_project,
source_branch: 'master',
target_branch: 'feature',
target_project: @project)
@commits = @merge_request.commits
end
context 'push to origin repo source branch' do
before do
@project.comment_mr_with_commits('master', @commits, @user)
end
it { @merge_request.notes.should_not be_empty }
it { @fork_merge_request.notes.should be_empty }
end
context 'push to origin repo target branch' do
before do
@project.comment_mr_with_commits('feature', @commits, @user)
end
it { @merge_request.notes.should be_empty }
it { @fork_merge_request.notes.should be_empty }
end
context 'push to fork repo source branch' do
before do
@fork_project.comment_mr_with_commits('master', @commits, @user)
end
it { @merge_request.notes.should be_empty }
it { @fork_merge_request.notes.should_not be_empty }
end
context 'push to fork repo target branch' do
before do
@fork_project.comment_mr_with_commits('feature', @commits, @user)
end
it { @merge_request.notes.should be_empty }
it { @fork_merge_request.notes.should be_empty }
end
end
describe :find_with_namespace do describe :find_with_namespace do
context 'with namespace' do context 'with namespace' do
before do before do
......
require_relative '../../app/models/project_services/slack_message' require 'spec_helper'
describe SlackMessage do describe SlackMessage do
subject { SlackMessage.new(args) } subject { SlackMessage.new(args) }
...@@ -26,11 +26,11 @@ describe SlackMessage do ...@@ -26,11 +26,11 @@ describe SlackMessage do
it 'returns a message regarding pushes' do it 'returns a message regarding pushes' do
subject.pretext.should == subject.pretext.should ==
'user_name pushed to branch <url/commits/master|master> of ' << 'user_name pushed to branch <url/commits/master|master> of '\
'<url|project_name> (<url/compare/before...after|Compare changes>)' '<url|project_name> (<url/compare/before...after|Compare changes>)'
subject.attachments.should == [ subject.attachments.should == [
{ {
text: "<url1|abcdefghi>: message1 - author1\n" << text: "<url1|abcdefghi>: message1 - author1\n"\
"<url2|123456789>: message2 - author2", "<url2|123456789>: message2 - author2",
color: color, color: color,
} }
...@@ -45,7 +45,7 @@ describe SlackMessage do ...@@ -45,7 +45,7 @@ describe SlackMessage do
it 'returns a message regarding a new branch' do it 'returns a message regarding a new branch' do
subject.pretext.should == subject.pretext.should ==
'user_name pushed new branch <url/commits/master|master> to ' << 'user_name pushed new branch <url/commits/master|master> to '\
'<url|project_name>' '<url|project_name>'
subject.attachments.should be_empty subject.attachments.should be_empty
end end
......
...@@ -32,70 +32,26 @@ describe SlackService do ...@@ -32,70 +32,26 @@ describe SlackService do
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(:webhook) { 'https://gitlabhq.slack.com/services/hooks?token=cdIj4r4LfXUOySDUjp0tk3OI' } let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' }
let(:new_webhook) { 'https://hooks.gitlabhq.slack.com/services/cdIj4r4LfXUOySDUjp0tk3OI' }
let(:api_url) {
'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,
service_hook: true, service_hook: true,
webhook: webhook webhook: webhook_url
) )
WebMock.stub_request(:post, api_url) WebMock.stub_request(:post, webhook_url)
end end
it "should call Slack API" do it "should call Slack API" do
slack.execute(sample_data) slack.execute(sample_data)
WebMock.should have_requested(:post, api_url).once WebMock.should have_requested(:post, webhook_url).once
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
context 'with new webhook syntax with slack allowed team name' do
before do
@allowed_webhook = 'https://gitlab-hq-123.slack.com/services/hooks/incoming-webhook?token=cdIj4r4LfXUOySDUjp0tk3OI'
slack_service.stub(
project: project,
project_id: project.id,
service_hook: true,
webhook: @allowed_webhook
)
WebMock.stub_request(:post, @allowed_webhook)
end
it "should call Slack API" do
slack_service.execute(sample_data)
WebMock.should have_requested(:post, @allowed_webhook).once
end
end end
end end
end end
...@@ -287,6 +287,20 @@ describe User do ...@@ -287,6 +287,20 @@ describe User do
end end
end end
describe '.by_login' do
let(:username) { 'John' }
let!(:user) { create(:user, username: username) }
it 'should get the correct user' do
expect(User.by_login(user.email.upcase)).to eq user
expect(User.by_login(user.email)).to eq user
expect(User.by_login(username.downcase)).to eq user
expect(User.by_login(username)).to eq user
expect(User.by_login(nil)).to be_nil
expect(User.by_login('')).to be_nil
end
end
describe 'all_ssh_keys' do describe 'all_ssh_keys' do
it { should have_many(:keys).dependent(:destroy) } it { should have_many(:keys).dependent(:destroy) }
......
...@@ -146,6 +146,7 @@ describe API::API, api: true do ...@@ -146,6 +146,7 @@ describe API::API, api: true do
it "should remove branch" do it "should remove branch" do
delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user) delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user)
response.status.should == 200 response.status.should == 200
json_response['branch_name'].should == branch_name
end end
it 'should return 404 if branch not exists' do it 'should return 404 if branch not exists' do
......
...@@ -8,6 +8,7 @@ describe API::API, api: true do ...@@ -8,6 +8,7 @@ describe API::API, api: true do
let!(:project) { create(:project, creator_id: user.id) } let!(:project) { create(:project, creator_id: user.id) }
let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) } let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') }
before { project.team << [user, :reporter] } before { project.team << [user, :reporter] }
...@@ -81,4 +82,68 @@ describe API::API, api: true do ...@@ -81,4 +82,68 @@ describe API::API, api: true do
end end
end end
end end
describe 'GET /projects:id/repository/commits/:sha/comments' do
context 'authorized user' do
it 'should return merge_request comments' do
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user)
response.status.should == 200
json_response.should be_an Array
json_response.length.should == 1
json_response.first['note'].should == 'a comment on a commit'
json_response.first['author']['id'].should == user.id
end
it 'should return a 404 error if merge_request_id not found' do
get api("/projects/#{project.id}/repository/commits/1234ab/comments", user)
response.status.should == 404
end
end
context 'unauthorized user' do
it 'should not return the diff of the selected commit' do
get api("/projects/#{project.id}/repository/commits/1234ab/comments")
response.status.should == 401
end
end
end
describe 'POST /projects:id/repository/commits/:sha/comments' do
context 'authorized user' do
it 'should return comment' do
post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment'
response.status.should == 201
json_response['note'].should == 'My comment'
json_response['path'].should be_nil
json_response['line'].should be_nil
json_response['line_type'].should be_nil
end
it 'should return the inline comment' do
post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment', path: project.repository.commit.diffs.first.new_path, line: 7, line_type: 'new'
response.status.should == 201
json_response['note'].should == 'My comment'
json_response['path'].should == project.repository.commit.diffs.first.new_path
json_response['line'].should == 7
json_response['line_type'].should == 'new'
end
it 'should return 400 if note is missing' do
post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user)
response.status.should == 400
end
it 'should return 404 if note is attached to non existent commit' do
post api("/projects/#{project.id}/repository/commits/1234ab/comments", user), note: 'My comment'
response.status.should == 404
end
end
context 'unauthorized user' do
it 'should not return the diff of the selected commit' do
post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments")
response.status.should == 401
end
end
end
end end
...@@ -203,15 +203,12 @@ describe API::API, api: true do ...@@ -203,15 +203,12 @@ describe API::API, api: true do
json_response['message']['name'].should == [ json_response['message']['name'].should == [
'can\'t be blank', 'can\'t be blank',
'is too short (minimum is 0 characters)', 'is too short (minimum is 0 characters)',
'can contain only letters, digits, \'_\', \'-\' and \'.\' and '\ Gitlab::Regex.project_regex_message
'space. It must start with letter, digit or \'_\'.'
] ]
json_response['message']['path'].should == [ json_response['message']['path'].should == [
'can\'t be blank', 'can\'t be blank',
'is too short (minimum is 0 characters)', 'is too short (minimum is 0 characters)',
'can contain only letters, digits, \'_\', \'-\' and \'.\'. It must '\ Gitlab::Regex.send(:default_regex_message)
'start with letter, digit or \'_\', optionally preceeded by \'.\'. '\
'It must not end in \'.git\'.'
] ]
end end
...@@ -339,6 +336,7 @@ describe API::API, api: true do ...@@ -339,6 +336,7 @@ describe API::API, api: true do
json_event['action_name'].should == 'joined' json_event['action_name'].should == 'joined'
json_event['project_id'].to_i.should == project.id json_event['project_id'].to_i.should == project.id
json_event['author_username'].should == user.username
end end
it "should return a 404 error if not found" do it "should return a 404 error if not found" do
...@@ -663,6 +661,11 @@ describe API::API, api: true do ...@@ -663,6 +661,11 @@ describe API::API, api: true do
describe "DELETE /projects/:id" do describe "DELETE /projects/:id" do
context "when authenticated as user" do context "when authenticated as user" do
it "should remove project" do it "should remove project" do
expect(GitlabShellWorker).to(
receive(:perform_async).with(:remove_repository,
/#{project.path_with_namespace}/)
).twice
delete api("/projects/#{project.id}", user) delete api("/projects/#{project.id}", user)
response.status.should == 200 response.status.should == 200
end end
......
...@@ -34,21 +34,23 @@ describe API::API, api: true do ...@@ -34,21 +34,23 @@ describe API::API, api: true do
end end
end end
# TODO: fix this test for CI context 'annotated tag' do
#context 'annotated tag' do it 'should create a new annotated tag' do
#it 'should create a new annotated tag' do # Identity must be set in .gitconfig to create annotated tag.
#post api("/projects/#{project.id}/repository/tags", user), repo_path = project.repository.path_to_repo
#tag_name: 'v7.1.0', system(*%W(git --git-dir=#{repo_path} config user.name #{user.name}))
#ref: 'master', system(*%W(git --git-dir=#{repo_path} config user.email #{user.email}))
#message: 'tag message'
post api("/projects/#{project.id}/repository/tags", user),
#response.status.should == 201 tag_name: 'v7.1.0',
#json_response['name'].should == 'v7.1.0' ref: 'master',
# The message is not part of the JSON response. message: 'Release 7.1.0'
# Additional changes to the gitlab_git gem may be required.
# json_response['message'].should == 'tag message' response.status.should == 201
#end json_response['name'].should == 'v7.1.0'
#end json_response['message'].should == 'Release 7.1.0'
end
end
it 'should deny for user without push access' do it 'should deny for user without push access' do
post api("/projects/#{project.id}/repository/tags", user2), post api("/projects/#{project.id}/repository/tags", user2),
......
...@@ -27,4 +27,30 @@ describe API::API, api: true do ...@@ -27,4 +27,30 @@ describe API::API, api: true do
project.gitlab_ci_service.should be_nil project.gitlab_ci_service.should be_nil
end end
end end
describe 'PUT /projects/:id/services/hipchat' do
it 'should update hipchat settings' do
put api("/projects/#{project.id}/services/hipchat", user),
token: 'secret-token', room: 'test'
response.status.should == 200
project.hipchat_service.should_not be_nil
end
it 'should return if required fields missing' do
put api("/projects/#{project.id}/services/gitlab-ci", user),
token: 'secret-token', active: true
response.status.should == 400
end
end
describe 'DELETE /projects/:id/services/hipchat' do
it 'should delete hipchat settings' do
delete api("/projects/#{project.id}/services/hipchat", user)
response.status.should == 200
project.hipchat_service.should be_nil
end
end
end end
...@@ -19,6 +19,32 @@ describe API::API, api: true do ...@@ -19,6 +19,32 @@ describe API::API, api: true do
end end
end end
context 'when email has case-typo and password is valid' do
it 'should return private token' do
post api('/session'), email: user.email.upcase, password: '12345678'
expect(response.status).to eq 201
expect(json_response['email']).to eq user.email
expect(json_response['private_token']).to eq user.private_token
expect(json_response['is_admin']).to eq user.is_admin?
expect(json_response['can_create_project']).to eq user.can_create_project?
expect(json_response['can_create_group']).to eq user.can_create_group?
end
end
context 'when login has case-typo and password is valid' do
it 'should return private token' do
post api('/session'), login: user.username.upcase, password: '12345678'
expect(response.status).to eq 201
expect(json_response['email']).to eq user.email
expect(json_response['private_token']).to eq user.private_token
expect(json_response['is_admin']).to eq user.is_admin?
expect(json_response['can_create_project']).to eq user.can_create_project?
expect(json_response['can_create_group']).to eq user.can_create_group?
end
end
context "when invalid password" do context "when invalid password" do
it "should return authentication error" do it "should return authentication error" do
post api("/session"), email: user.email, password: '123' post api("/session"), email: user.email, password: '123'
......
...@@ -140,9 +140,7 @@ describe API::API, api: true do ...@@ -140,9 +140,7 @@ describe API::API, api: true do
json_response['message']['projects_limit']. json_response['message']['projects_limit'].
should == ['must be greater than or equal to 0'] should == ['must be greater than or equal to 0']
json_response['message']['username']. json_response['message']['username'].
should == ['can contain only letters, digits, '\ should == [Gitlab::Regex.send(:default_regex_message)]
'\'_\', \'-\' and \'.\'. It must start with letter, digit or '\
'\'_\', optionally preceeded by \'.\'. It must not end in \'.git\'.']
end end
it "shouldn't available for non admin users" do it "shouldn't available for non admin users" do
...@@ -284,9 +282,7 @@ describe API::API, api: true do ...@@ -284,9 +282,7 @@ describe API::API, api: true do
json_response['message']['projects_limit']. json_response['message']['projects_limit'].
should == ['must be greater than or equal to 0'] should == ['must be greater than or equal to 0']
json_response['message']['username']. json_response['message']['username'].
should == ['can contain only letters, digits, '\ should == [Gitlab::Regex.send(:default_regex_message)]
'\'_\', \'-\' and \'.\'. It must start with letter, digit or '\
'\'_\', optionally preceeded by \'.\'. It must not end in \'.git\'.']
end end
context "with existing user" do context "with existing user" do
...@@ -433,6 +429,7 @@ describe API::API, api: true do ...@@ -433,6 +429,7 @@ describe API::API, api: true do
json_response['is_admin'].should == user.is_admin? json_response['is_admin'].should == user.is_admin?
json_response['can_create_project'].should == user.can_create_project? json_response['can_create_project'].should == user.can_create_project?
json_response['can_create_group'].should == user.can_create_group? json_response['can_create_group'].should == user.can_create_group?
json_response['projects_limit'].should == user.projects_limit
end end
it "should return 401 error if user is unauthenticated" do it "should return 401 error if user is unauthenticated" do
......
...@@ -8,7 +8,7 @@ describe GitPushService do ...@@ -8,7 +8,7 @@ describe GitPushService do
let (:service) { GitPushService.new } let (:service) { GitPushService.new }
before do before do
@blankrev = '0000000000000000000000000000000000000000' @blankrev = Gitlab::Git::BLANK_SHA
@oldrev = sample_commit.parent_id @oldrev = sample_commit.parent_id
@newrev = sample_commit.id @newrev = sample_commit.id
@ref = 'refs/heads/master' @ref = 'refs/heads/master'
......
require 'spec_helper'
describe MergeRequests::RefreshService do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:service) { MergeRequests::RefreshService }
describe :execute do
before do
@user = create(:user)
group = create(:group)
group.add_owner(@user)
@project = create(:project, namespace: group)
@fork_project = Projects::ForkService.new(@project, @user).execute
@merge_request = create(:merge_request, source_project: @project,
source_branch: 'master',
target_branch: 'feature',
target_project: @project)
@fork_merge_request = create(:merge_request, source_project: @fork_project,
source_branch: 'master',
target_branch: 'feature',
target_project: @project)
@commits = @merge_request.commits
@oldrev = @commits.last.id
@newrev = @commits.first.id
end
context 'push to origin repo source branch' do
before do
service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master')
reload_mrs
end
it { @merge_request.notes.should_not be_empty }
it { @merge_request.should be_open }
it { @fork_merge_request.should be_open }
it { @fork_merge_request.notes.should be_empty }
end
context 'push to origin repo target branch' do
before do
service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
reload_mrs
end
it { @merge_request.notes.should be_empty }
it { @merge_request.should be_merged }
it { @fork_merge_request.should be_merged }
it { @fork_merge_request.notes.should be_empty }
end
context 'push to fork repo source branch' do
before do
service.new(@fork_project, @user).execute(@oldrev, @newrev, 'refs/heads/master')
reload_mrs
end
it { @merge_request.notes.should be_empty }
it { @merge_request.should be_open }
it { @fork_merge_request.notes.should_not be_empty }
it { @fork_merge_request.should be_open }
end
context 'push to fork repo target branch' do
before do
service.new(@fork_project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
reload_mrs
end
it { @merge_request.notes.should be_empty }
it { @merge_request.should be_open }
it { @fork_merge_request.notes.should be_empty }
it { @fork_merge_request.should be_open }
end
context 'push to origin repo target branch after fork project was removed' do
before do
@fork_project.destroy
service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
reload_mrs
end
it { @merge_request.notes.should be_empty }
it { @merge_request.should be_merged }
it { @fork_merge_request.should be_open }
it { @fork_merge_request.notes.should be_empty }
end
def reload_mrs
@merge_request.reload
@fork_merge_request.reload
end
end
end
...@@ -3,15 +3,12 @@ require 'spec_helper' ...@@ -3,15 +3,12 @@ require 'spec_helper'
describe Projects::TransferService do describe Projects::TransferService do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:group) { create(:group) } let(:group) { create(:group) }
let(:group2) { create(:group) }
let(:project) { create(:project, namespace: user.namespace) } let(:project) { create(:project, namespace: user.namespace) }
context 'namespace -> namespace' do context 'namespace -> namespace' do
before do before do
group.add_owner(user) group.add_owner(user)
@service = Projects::TransferService.new(project, user, namespace_id: group.id) @result = transfer_project(project, user, namespace_id: group.id)
@service.gitlab_shell.stub(mv_repository: true)
@result = @service.execute
end end
it { @result.should be_true } it { @result.should be_true }
...@@ -20,24 +17,25 @@ describe Projects::TransferService do ...@@ -20,24 +17,25 @@ describe Projects::TransferService do
context 'namespace -> no namespace' do context 'namespace -> no namespace' do
before do before do
group.add_owner(user) @result = transfer_project(project, user, namespace_id: nil)
@service = Projects::TransferService.new(project, user, namespace_id: nil)
@service.gitlab_shell.stub(mv_repository: true)
@result = @service.execute
end end
it { @result.should_not be_nil } # { result.should be_false } passes on nil
it { @result.should be_false } it { @result.should be_false }
it { project.namespace.should == user.namespace } it { project.namespace.should == user.namespace }
end end
context 'namespace -> not allowed namespace' do context 'namespace -> not allowed namespace' do
before do before do
@service = Projects::TransferService.new(project, user, namespace_id: group2.id) @result = transfer_project(project, user, namespace_id: group.id)
@service.gitlab_shell.stub(mv_repository: true)
@result = @service.execute
end end
it { @result.should_not be_nil } # { result.should be_false } passes on nil
it { @result.should be_false } it { @result.should be_false }
it { project.namespace.should == user.namespace } it { project.namespace.should == user.namespace }
end end
def transfer_project(project, user, params)
Projects::TransferService.new(project, user, params).execute
end
end end
...@@ -3,6 +3,16 @@ require 'rspec/mocks' ...@@ -3,6 +3,16 @@ require 'rspec/mocks'
module TestEnv module TestEnv
extend self extend self
# When developing the seed repository, comment out the branch you will modify.
BRANCH_SHA = {
'feature' => '0b4bc9a',
'feature_conflict' => 'bb5206f',
'fix' => '12d65c8',
'improve/awesome' => '5937ac0',
'markdown' => '0ed8c6c',
'master' => '5937ac0'
}
# Test environment # Test environment
# #
# See gitlab.yml.example test section for paths # See gitlab.yml.example test section for paths
...@@ -18,13 +28,13 @@ module TestEnv ...@@ -18,13 +28,13 @@ module TestEnv
if File.directory?(tmp_test_path) if File.directory?(tmp_test_path)
Dir.entries(tmp_test_path).each do |entry| Dir.entries(tmp_test_path).each do |entry|
unless ['.', '..', 'gitlab-shell'].include?(entry) unless ['.', '..', 'gitlab-shell', factory_repo_name].include?(entry)
FileUtils.rm_r(File.join(tmp_test_path, entry)) FileUtils.rm_r(File.join(tmp_test_path, entry))
end end
end end
end end
FileUtils.mkdir_p(tmp_test_path) FileUtils.mkdir_p(repos_path)
# Setup GitLab shell for test instance # Setup GitLab shell for test instance
setup_gitlab_shell setup_gitlab_shell
...@@ -49,13 +59,32 @@ module TestEnv ...@@ -49,13 +59,32 @@ module TestEnv
clone_url = "https://gitlab.com/gitlab-org/#{factory_repo_name}.git" clone_url = "https://gitlab.com/gitlab-org/#{factory_repo_name}.git"
unless File.directory?(factory_repo_path) unless File.directory?(factory_repo_path)
git_cmd = %W(git clone --bare #{clone_url} #{factory_repo_path}) system(*%W(git clone #{clone_url} #{factory_repo_path}))
system(*git_cmd) end
Dir.chdir(factory_repo_path) do
BRANCH_SHA.each do |branch, sha|
# Try to reset without fetching to avoid using the network.
reset = %W(git update-ref refs/heads/#{branch} #{sha})
unless system(*reset)
if system(*%w(git fetch origin))
unless system(*reset)
raise 'The fetched test seed '\
'does not contain the required revision.'
end
else
raise 'Could not fetch test seed repository.'
end
end end
end end
end
# We must copy bare repositories because we will push to them.
system(*%W(git clone --bare #{factory_repo_path} #{factory_repo_path_bare}))
end
def copy_repo(project) def copy_repo(project)
base_repo_path = File.expand_path(factory_repo_path) base_repo_path = File.expand_path(factory_repo_path_bare)
target_repo_path = File.expand_path(repos_path + "/#{project.namespace.path}/#{project.path}.git") target_repo_path = File.expand_path(repos_path + "/#{project.namespace.path}/#{project.path}.git")
FileUtils.mkdir_p(target_repo_path) FileUtils.mkdir_p(target_repo_path)
FileUtils.cp_r("#{base_repo_path}/.", target_repo_path) FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
...@@ -69,7 +98,11 @@ module TestEnv ...@@ -69,7 +98,11 @@ module TestEnv
private private
def factory_repo_path def factory_repo_path
@factory_repo_path ||= repos_path + "/root/#{factory_repo_name}.git" @factory_repo_path ||= Rails.root.join('tmp', 'tests', factory_repo_name)
end
def factory_repo_path_bare
factory_repo_path.to_s + '_bare'
end end
def factory_repo_name def factory_repo_name
......
/*!
* jQuery Password Strength plugin for Twitter Bootstrap
*
* Copyright (c) 2008-2013 Tane Piper
* Copyright (c) 2013 Alejandro Blanco
* Dual licensed under the MIT and GPL licenses.
*/
(function (jQuery) {
// Source: src/rules.js
var rulesEngine = {};
try {
if (!jQuery && module && module.exports) {
var jQuery = require("jquery"),
jsdom = require("jsdom").jsdom;
jQuery = jQuery(jsdom().parentWindow);
}
} catch (ignore) {}
(function ($, rulesEngine) {
"use strict";
var validation = {};
rulesEngine.forbiddenSequences = [
"0123456789", "abcdefghijklmnopqrstuvwxyz", "qwertyuiop", "asdfghjkl",
"zxcvbnm", "!@#$%^&*()_+"
];
validation.wordNotEmail = function (options, word, score) {
if (word.match(/^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$/i)) {
return score;
}
return 0;
};
validation.wordLength = function (options, word, score) {
var wordlen = word.length,
lenScore = Math.pow(wordlen, options.rules.raisePower);
if (wordlen < options.common.minChar) {
lenScore = (lenScore + score);
}
return lenScore;
};
validation.wordSimilarToUsername = function (options, word, score) {
var username = $(options.common.usernameField).val();
if (username && word.toLowerCase().match(username.toLowerCase())) {
return score;
}
return 0;
};
validation.wordTwoCharacterClasses = function (options, word, score) {
if (word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) ||
(word.match(/([a-zA-Z])/) && word.match(/([0-9])/)) ||
(word.match(/(.[!,@,#,$,%,\^,&,*,?,_,~])/) && word.match(/[a-zA-Z0-9_]/))) {
return score;
}
return 0;
};
validation.wordRepetitions = function (options, word, score) {
if (word.match(/(.)\1\1/)) { return score; }
return 0;
};
validation.wordSequences = function (options, word, score) {
var found = false,
j;
if (word.length > 2) {
$.each(rulesEngine.forbiddenSequences, function (idx, seq) {
var sequences = [seq, seq.split('').reverse().join('')];
$.each(sequences, function (idx, sequence) {
for (j = 0; j < (word.length - 2); j += 1) { // iterate the word trough a sliding window of size 3:
if (sequence.indexOf(word.toLowerCase().substring(j, j + 3)) > -1) {
found = true;
}
}
});
});
if (found) { return score; }
}
return 0;
};
validation.wordLowercase = function (options, word, score) {
return word.match(/[a-z]/) && score;
};
validation.wordUppercase = function (options, word, score) {
return word.match(/[A-Z]/) && score;
};
validation.wordOneNumber = function (options, word, score) {
return word.match(/\d+/) && score;
};
validation.wordThreeNumbers = function (options, word, score) {
return word.match(/(.*[0-9].*[0-9].*[0-9])/) && score;
};
validation.wordOneSpecialChar = function (options, word, score) {
return word.match(/.[!,@,#,$,%,\^,&,*,?,_,~]/) && score;
};
validation.wordTwoSpecialChar = function (options, word, score) {
return word.match(/(.*[!,@,#,$,%,\^,&,*,?,_,~].*[!,@,#,$,%,\^,&,*,?,_,~])/) && score;
};
validation.wordUpperLowerCombo = function (options, word, score) {
return word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) && score;
};
validation.wordLetterNumberCombo = function (options, word, score) {
return word.match(/([a-zA-Z])/) && word.match(/([0-9])/) && score;
};
validation.wordLetterNumberCharCombo = function (options, word, score) {
return word.match(/([a-zA-Z0-9].*[!,@,#,$,%,\^,&,*,?,_,~])|([!,@,#,$,%,\^,&,*,?,_,~].*[a-zA-Z0-9])/) && score;
};
rulesEngine.validation = validation;
rulesEngine.executeRules = function (options, word) {
var totalScore = 0;
$.each(options.rules.activated, function (rule, active) {
if (active) {
var score = options.rules.scores[rule],
funct = rulesEngine.validation[rule],
result,
errorMessage;
if (!$.isFunction(funct)) {
funct = options.rules.extra[rule];
}
if ($.isFunction(funct)) {
result = funct(options, word, score);
if (result) {
totalScore += result;
}
if (result < 0 || (!$.isNumeric(result) && !result)) {
errorMessage = options.ui.spanError(options, rule);
if (errorMessage.length > 0) {
options.instances.errors.push(errorMessage);
}
}
}
}
});
return totalScore;
};
}(jQuery, rulesEngine));
try {
if (module && module.exports) {
module.exports = rulesEngine;
}
} catch (ignore) {}
// Source: src/options.js
var defaultOptions = {};
defaultOptions.common = {};
defaultOptions.common.minChar = 6;
defaultOptions.common.usernameField = "#username";
defaultOptions.common.userInputs = [
// Selectors for input fields with user input
];
defaultOptions.common.onLoad = undefined;
defaultOptions.common.onKeyUp = undefined;
defaultOptions.common.zxcvbn = false;
defaultOptions.common.debug = false;
defaultOptions.rules = {};
defaultOptions.rules.extra = {};
defaultOptions.rules.scores = {
wordNotEmail: -100,
wordLength: -50,
wordSimilarToUsername: -100,
wordSequences: -50,
wordTwoCharacterClasses: 2,
wordRepetitions: -25,
wordLowercase: 1,
wordUppercase: 3,
wordOneNumber: 3,
wordThreeNumbers: 5,
wordOneSpecialChar: 3,
wordTwoSpecialChar: 5,
wordUpperLowerCombo: 2,
wordLetterNumberCombo: 2,
wordLetterNumberCharCombo: 2
};
defaultOptions.rules.activated = {
wordNotEmail: true,
wordLength: true,
wordSimilarToUsername: true,
wordSequences: true,
wordTwoCharacterClasses: false,
wordRepetitions: false,
wordLowercase: true,
wordUppercase: true,
wordOneNumber: true,
wordThreeNumbers: true,
wordOneSpecialChar: true,
wordTwoSpecialChar: true,
wordUpperLowerCombo: true,
wordLetterNumberCombo: true,
wordLetterNumberCharCombo: true
};
defaultOptions.rules.raisePower = 1.4;
defaultOptions.ui = {};
defaultOptions.ui.bootstrap2 = false;
defaultOptions.ui.showProgressBar = true;
defaultOptions.ui.showPopover = false;
defaultOptions.ui.showStatus = false;
defaultOptions.ui.spanError = function (options, key) {
"use strict";
var text = options.ui.errorMessages[key];
if (!text) { return ''; }
return '<span style="color: #d52929">' + text + '</span>';
};
defaultOptions.ui.errorMessages = {
wordLength: "Your password is too short",
wordNotEmail: "Do not use your email as your password",
wordSimilarToUsername: "Your password cannot contain your username",
wordTwoCharacterClasses: "Use different character classes",
wordRepetitions: "Too many repetitions",
wordSequences: "Your password contains sequences"
};
defaultOptions.ui.verdicts = ["Weak", "Normal", "Medium", "Strong", "Very Strong"];
defaultOptions.ui.showVerdicts = true;
defaultOptions.ui.showVerdictsInsideProgressBar = false;
defaultOptions.ui.showErrors = false;
defaultOptions.ui.container = undefined;
defaultOptions.ui.viewports = {
progress: undefined,
verdict: undefined,
errors: undefined
};
defaultOptions.ui.scores = [14, 26, 38, 50];
// Source: src/ui.js
var ui = {};
(function ($, ui) {
"use strict";
var barClasses = ["danger", "warning", "success"],
statusClasses = ["error", "warning", "success"];
ui.getContainer = function (options, $el) {
var $container;
$container = $(options.ui.container);
if (!($container && $container.length === 1)) {
$container = $el.parent();
}
return $container;
};
ui.findElement = function ($container, viewport, cssSelector) {
if (viewport) {
return $container.find(viewport).find(cssSelector);
}
return $container.find(cssSelector);
};
ui.getUIElements = function (options, $el) {
var $container, result;
if (options.instances.viewports) {
return options.instances.viewports;
}
$container = ui.getContainer(options, $el);
result = {};
result.$progressbar = ui.findElement($container, options.ui.viewports.progress, "div.progress");
if (options.ui.showVerdictsInsideProgressBar) {
result.$verdict = result.$progressbar.find("span.password-verdict");
}
if (!options.ui.showPopover) {
if (!options.ui.showVerdictsInsideProgressBar) {
result.$verdict = ui.findElement($container, options.ui.viewports.verdict, "span.password-verdict");
}
result.$errors = ui.findElement($container, options.ui.viewports.errors, "ul.error-list");
}
options.instances.viewports = result;
return result;
};
ui.initProgressBar = function (options, $el) {
var $container = ui.getContainer(options, $el),
progressbar = "<div class='progress'><div class='";
if (!options.ui.bootstrap2) {
progressbar += "progress-";
}
progressbar += "bar'>";
if (options.ui.showVerdictsInsideProgressBar) {
progressbar += "<span class='password-verdict'></span>";
}
progressbar += "</div></div>";
if (options.ui.viewports.progress) {
$container.find(options.ui.viewports.progress).append(progressbar);
} else {
$(progressbar).insertAfter($el);
}
};
ui.initHelper = function (options, $el, html, viewport) {
var $container = ui.getContainer(options, $el);
if (viewport) {
$container.find(viewport).append(html);
} else {
$(html).insertAfter($el);
}
};
ui.initVerdict = function (options, $el) {
ui.initHelper(options, $el, "<span class='password-verdict'></span>",
options.ui.viewports.verdict);
};
ui.initErrorList = function (options, $el) {
ui.initHelper(options, $el, "<ul class='error-list'></ul>",
options.ui.viewports.errors);
};
ui.initPopover = function (options, $el) {
$el.popover("destroy");
$el.popover({
html: true,
placement: "top",
trigger: "manual",
content: " "
});
};
ui.initUI = function (options, $el) {
if (options.ui.showPopover) {
ui.initPopover(options, $el);
} else {
if (options.ui.showErrors) { ui.initErrorList(options, $el); }
if (options.ui.showVerdicts && !options.ui.showVerdictsInsideProgressBar) {
ui.initVerdict(options, $el);
}
}
if (options.ui.showProgressBar) {
ui.initProgressBar(options, $el);
}
};
ui.possibleProgressBarClasses = ["danger", "warning", "success"];
ui.updateProgressBar = function (options, $el, cssClass, percentage) {
var $progressbar = ui.getUIElements(options, $el).$progressbar,
$bar = $progressbar.find(".progress-bar"),
cssPrefix = "progress-";
if (options.ui.bootstrap2) {
$bar = $progressbar.find(".bar");
cssPrefix = "";
}
$.each(ui.possibleProgressBarClasses, function (idx, value) {
$bar.removeClass(cssPrefix + "bar-" + value);
});
$bar.addClass(cssPrefix + "bar-" + barClasses[cssClass]);
$bar.css("width", percentage + '%');
};
ui.updateVerdict = function (options, $el, text) {
var $verdict = ui.getUIElements(options, $el).$verdict;
$verdict.text(text);
};
ui.updateErrors = function (options, $el) {
var $errors = ui.getUIElements(options, $el).$errors,
html = "";
$.each(options.instances.errors, function (idx, err) {
html += "<li>" + err + "</li>";
});
$errors.html(html);
};
ui.updatePopover = function (options, $el, verdictText) {
var popover = $el.data("bs.popover"),
html = "",
hide = true;
if (options.ui.showVerdicts &&
!options.ui.showVerdictsInsideProgressBar &&
verdictText.length > 0) {
html = "<h5><span class='password-verdict'>" + verdictText +
"</span></h5>";
hide = false;
}
if (options.ui.showErrors) {
html += "<div><ul class='error-list' style='margin-bottom: 0; margin-left: -20px'>";
$.each(options.instances.errors, function (idx, err) {
html += "<li>" + err + "</li>";
hide = false;
});
html += "</ul></div>";
}
if (hide) {
$el.popover("hide");
return;
}
if (options.ui.bootstrap2) { popover = $el.data("popover"); }
if (popover.$arrow && popover.$arrow.parents("body").length > 0) {
$el.find("+ .popover .popover-content").html(html);
} else {
// It's hidden
popover.options.content = html;
$el.popover("show");
}
};
ui.updateFieldStatus = function (options, $el, cssClass) {
var targetClass = options.ui.bootstrap2 ? ".control-group" : ".form-group",
$container = $el.parents(targetClass).first();
$.each(statusClasses, function (idx, css) {
if (!options.ui.bootstrap2) { css = "has-" + css; }
$container.removeClass(css);
});
cssClass = statusClasses[cssClass];
if (!options.ui.bootstrap2) { cssClass = "has-" + cssClass; }
$container.addClass(cssClass);
};
ui.percentage = function (score, maximun) {
var result = Math.floor(100 * score / maximun);
result = result < 0 ? 0 : result;
result = result > 100 ? 100 : result;
return result;
};
ui.getVerdictAndCssClass = function (options, score) {
var cssClass, verdictText, level;
if (score <= 0) {
cssClass = 0;
level = -1;
verdictText = options.ui.verdicts[0];
} else if (score < options.ui.scores[0]) {
cssClass = 0;
level = 0;
verdictText = options.ui.verdicts[0];
} else if (score < options.ui.scores[1]) {
cssClass = 0;
level = 1;
verdictText = options.ui.verdicts[1];
} else if (score < options.ui.scores[2]) {
cssClass = 1;
level = 2;
verdictText = options.ui.verdicts[2];
} else if (score < options.ui.scores[3]) {
cssClass = 1;
level = 3;
verdictText = options.ui.verdicts[3];
} else {
cssClass = 2;
level = 4;
verdictText = options.ui.verdicts[4];
}
return [verdictText, cssClass, level];
};
ui.updateUI = function (options, $el, score) {
var cssClass, barPercentage, verdictText;
cssClass = ui.getVerdictAndCssClass(options, score);
verdictText = cssClass[0];
cssClass = cssClass[1];
if (options.ui.showProgressBar) {
barPercentage = ui.percentage(score, options.ui.scores[3]);
ui.updateProgressBar(options, $el, cssClass, barPercentage);
if (options.ui.showVerdictsInsideProgressBar) {
ui.updateVerdict(options, $el, verdictText);
}
}
if (options.ui.showStatus) {
ui.updateFieldStatus(options, $el, cssClass);
}
if (options.ui.showPopover) {
ui.updatePopover(options, $el, verdictText);
} else {
if (options.ui.showVerdicts && !options.ui.showVerdictsInsideProgressBar) {
ui.updateVerdict(options, $el, verdictText);
}
if (options.ui.showErrors) {
ui.updateErrors(options, $el);
}
}
};
}(jQuery, ui));
// Source: src/methods.js
var methods = {};
(function ($, methods) {
"use strict";
var onKeyUp, applyToAll;
onKeyUp = function (event) {
var $el = $(event.target),
options = $el.data("pwstrength-bootstrap"),
word = $el.val(),
userInputs,
verdictText,
verdictLevel,
score;
if (options === undefined) { return; }
options.instances.errors = [];
if (options.common.zxcvbn) {
userInputs = [];
$.each(options.common.userInputs, function (idx, selector) {
userInputs.push($(selector).val());
});
userInputs.push($(options.common.usernameField).val());
score = zxcvbn(word, userInputs).entropy;
} else {
score = rulesEngine.executeRules(options, word);
}
ui.updateUI(options, $el, score);
verdictText = ui.getVerdictAndCssClass(options, score);
verdictLevel = verdictText[2];
verdictText = verdictText[0];
if (options.common.debug) { console.log(score + ' - ' + verdictText); }
if ($.isFunction(options.common.onKeyUp)) {
options.common.onKeyUp(event, {
score: score,
verdictText: verdictText,
verdictLevel: verdictLevel
});
}
};
methods.init = function (settings) {
this.each(function (idx, el) {
// Make it deep extend (first param) so it extends too the
// rules and other inside objects
var clonedDefaults = $.extend(true, {}, defaultOptions),
localOptions = $.extend(true, clonedDefaults, settings),
$el = $(el);
localOptions.instances = {};
$el.data("pwstrength-bootstrap", localOptions);
$el.on("keyup", onKeyUp);
$el.on("change", onKeyUp);
$el.on("onpaste", onKeyUp);
ui.initUI(localOptions, $el);
if ($.trim($el.val())) { // Not empty, calculate the strength
$el.trigger("keyup");
}
if ($.isFunction(localOptions.common.onLoad)) {
localOptions.common.onLoad();
}
});
return this;
};
methods.destroy = function () {
this.each(function (idx, el) {
var $el = $(el),
options = $el.data("pwstrength-bootstrap"),
elements = ui.getUIElements(options, $el);
elements.$progressbar.remove();
elements.$verdict.remove();
elements.$errors.remove();
$el.removeData("pwstrength-bootstrap");
});
};
methods.forceUpdate = function () {
this.each(function (idx, el) {
var event = { target: el };
onKeyUp(event);
});
};
methods.addRule = function (name, method, score, active) {
this.each(function (idx, el) {
var options = $(el).data("pwstrength-bootstrap");
options.rules.activated[name] = active;
options.rules.scores[name] = score;
options.rules.extra[name] = method;
});
};
applyToAll = function (rule, prop, value) {
this.each(function (idx, el) {
$(el).data("pwstrength-bootstrap").rules[prop][rule] = value;
});
};
methods.changeScore = function (rule, score) {
applyToAll.call(this, rule, "scores", score);
};
methods.ruleActive = function (rule, active) {
applyToAll.call(this, rule, "activated", active);
};
$.fn.pwstrength = function (method) {
var result;
if (methods[method]) {
result = methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === "object" || !method) {
result = methods.init.apply(this, arguments);
} else {
$.error("Method " + method + " does not exist on jQuery.pwstrength-bootstrap");
}
return result;
};
}(jQuery, methods));
}(jQuery));
\ No newline at end of file
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