Commit 10528341 authored by Patricio Cano's avatar Patricio Cano

Merge branch 'master' into feature_password_strength_indicator

parents 91c96b37 c6efa56e
...@@ -39,3 +39,4 @@ public/assets/ ...@@ -39,3 +39,4 @@ public/assets/
.envrc .envrc
dump.rdb dump.rdb
tags tags
.gitlab_shell_secret
v 7.5.0
- API: Add support for Hipchat (Kevin Houdebert)
- Add time zone configuration on gitlab.yml (Sullivan Senechal)
v 7.4.0 v 7.4.0
- Refactored membership logic - Refactored membership logic
- Improve error reporting on users API (Julien Bianchi) - Improve error reporting on users API (Julien Bianchi)
...@@ -20,6 +24,21 @@ v 7.4.0 ...@@ -20,6 +24,21 @@ v 7.4.0
- Add select field type for services options (Sullivan Senechal) - Add select field type for services options (Sullivan Senechal)
- Add cross-project references to the Markdown parser (Vinnie Okada) - Add cross-project references to the Markdown parser (Vinnie Okada)
- Add task lists to issue and merge request descriptions (Vinnie Okada) - Add task lists to issue and merge request descriptions (Vinnie Okada)
- Snippets can be public, internal or private
- Improve danger zone: ask project path to confirm data-loss action
- Raise exception on forgery
- Show build coverage in Merge Requests (requires GitLab CI v5.1)
- New milestone and label links on issue edit form
- Improved repository graphs
- Improve event note display in dashboard and project activity views (Vinnie Okada)
- Add users sorting to admin area
- UI improvements
- Fix ambiguous sha problem with mentioned commit
- Fixed bug with apostrophe when at mentioning users
- Add active directory ldap option
- Developers can push to wiki repo. Protected branches does not affect wiki repo any more
- Faster rev list
- Fix branch removal
v 7.3.2 v 7.3.2
- Fix creating new file via web editor - Fix creating new file via web editor
......
...@@ -92,6 +92,7 @@ For examples of feedback on merge requests please look at already [closed merge ...@@ -92,6 +92,7 @@ For examples of feedback on merge requests please look at already [closed merge
1. The change is as small as possible (see the above paragraph for details) 1. The change is as small as possible (see the above paragraph for details)
1. Include proper tests and make all tests pass (unless it contains a test exposing a bug in existing code) 1. Include proper tests and make all tests pass (unless it contains a test exposing a bug in existing code)
1. All tests have to pass, if you suspect a failing CI build is unrelated to your contribution ask for tests to be restarted. See [the CI setup document](http://doc.gitlab.com/ce/development/ci_setup.html) on who you can ask for test restart.
1. Initially contains a single commit (please use `git rebase -i` to squash commits) 1. Initially contains a single commit (please use `git rebase -i` to squash commits)
1. Can merge without problems (if not please merge `master`, never rebase commits pushed to the remote server) 1. Can merge without problems (if not please merge `master`, never rebase commits pushed to the remote server)
1. Does not break any existing functionality 1. Does not break any existing functionality
...@@ -100,7 +101,11 @@ For examples of feedback on merge requests please look at already [closed merge ...@@ -100,7 +101,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
......
...@@ -168,7 +168,7 @@ GEM ...@@ -168,7 +168,7 @@ GEM
multi_json multi_json
gitlab-grack (2.0.0.pre) gitlab-grack (2.0.0.pre)
rack (~> 1.5.1) rack (~> 1.5.1)
gitlab-grit (2.6.11) gitlab-grit (2.6.12)
charlock_holmes (~> 0.6) charlock_holmes (~> 0.6)
diff-lcs (~> 1.1) diff-lcs (~> 1.1)
mime-types (~> 1.15) mime-types (~> 1.15)
...@@ -241,7 +241,8 @@ GEM ...@@ -241,7 +241,8 @@ GEM
html-pipeline (1.11.0) html-pipeline (1.11.0)
activesupport (>= 2) activesupport (>= 2)
nokogiri (~> 1.4) nokogiri (~> 1.4)
html-pipeline-gitlab (0.1.3) html-pipeline-gitlab (0.1.5)
actionpack (~> 4)
gitlab_emoji (~> 0.0.1) gitlab_emoji (~> 0.0.1)
html-pipeline (~> 1.11.0) html-pipeline (~> 1.11.0)
sanitize (~> 2.1) sanitize (~> 2.1)
......
...@@ -51,7 +51,7 @@ On [about.gitlab.com](https://about.gitlab.com/) you can find more information a ...@@ -51,7 +51,7 @@ On [about.gitlab.com](https://about.gitlab.com/) you can find more information a
## Installation ## Installation
Please see [the installation page on the GitLab website](https://about.gitlab.com/installation/) for the various options. Please see [the installation page on the GitLab website](https://about.gitlab.com/installation/) for the various options.
Since a manual installation is a lot of work and error prone we strongly recommend fast and reliable Omnibus package installation (deb/rpm) on that page. Since a manual installation is a lot of work and error prone we strongly recommend the fast and reliable [Omnibus package installation](https://about.gitlab.com/downloads/) (deb/rpm).
## Third-party applications ## Third-party applications
......
7.4.0-pre 7.5.0.pre
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
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
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 GroupMembers class @GroupMembers
constructor: -> constructor: ->
$('li.group_member').bind 'ajax:success', -> $('li.group_member').bind 'ajax:success', ->
$(this).fadeOut() $(this).fadeOut()
@GroupMembers = GroupMembers
$ -> $ ->
# avatar # avatar
$('.js-choose-group-avatar-button').bind "click", -> $('.js-choose-group-avatar-button').bind "click", ->
......
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 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
class Project class @Project
constructor: -> constructor: ->
$('.project-edit-container').on 'ajax:before', => $('.project-edit-container').on 'ajax:before', =>
$('.project-edit-container').hide() $('.project-edit-container').hide()
...@@ -24,9 +24,6 @@ class Project ...@@ -24,9 +24,6 @@ class Project
else else
$('#project_issues_tracker_id').removeAttr('disabled') $('#project_issues_tracker_id').removeAttr('disabled')
@Project = Project
$ -> $ ->
# Git clone panel switcher # Git clone panel switcher
scope = $ '.git-clone-holder' scope = $ '.git-clone-holder'
......
class ProjectImport class @ProjectImport
constructor: -> constructor: ->
setTimeout -> setTimeout ->
Turbolinks.visit(location.href) Turbolinks.visit(location.href)
, 5000 , 5000
@ProjectImport = ProjectImport
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 class @TeamMembers
constructor: -> constructor: ->
$('.team-members .project-access-select').on "change", -> $('.team-members .project-access-select').on "change", ->
$(this.form).submit() $(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 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
/** 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;
...@@ -75,7 +75,7 @@ ...@@ -75,7 +75,7 @@
} }
.participants { .participants {
margin-bottom: 10px; margin-bottom: 20px;
} }
.issues_bulk_update { .issues_bulk_update {
......
...@@ -111,31 +111,38 @@ ...@@ -111,31 +111,38 @@
.ci_widget { .ci_widget {
padding: 10px 15px; padding: 10px 15px;
font-size: 15px; font-size: 15px;
border-bottom: 1px dashed #AAA; border-bottom: 1px solid #BBB;
color: #777;
background-color: #F5F5F5;
&.ci-success { &.ci-success {
color: $bg_success; color: $bg_success;
border-color: $border_success; border-color: $border_success;
background-color: #F1FAF1;
} }
&.ci-pending { &.ci-pending {
color: #548; color: #548;
border-color: #548; border-color: #548;
background-color: #F4F1FA;
} }
&.ci-running { &.ci-running {
color: $bg_warning; color: $bg_warning;
border-color: $border_warning; border-color: $border_warning;
background-color: #FAF5F1;
} }
&.ci-failed { &.ci-failed {
color: $bg_danger; color: $bg_danger;
border-color: $border_danger; border-color: $border_danger;
background-color: #FAF1F1;
} }
&.ci-error { &.ci-error {
color: $bg_danger; color: $bg_danger;
border-color: $border_danger; border-color: $border_danger;
background-color: #FAF1F1;
} }
} }
...@@ -143,7 +150,8 @@ ...@@ -143,7 +150,8 @@
padding: 10px 15px; padding: 10px 15px;
h4 { h4 {
margin-top: 0px; font-size: 20px;
font-weight: normal;
} }
p:last-child { p:last-child {
......
...@@ -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
...@@ -4,6 +4,7 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -4,6 +4,7 @@ class Admin::UsersController < Admin::ApplicationController
def index def index
@users = User.filter(params[:filter]) @users = User.filter(params[:filter])
@users = @users.search(params[:name]) if params[:name].present? @users = @users.search(params[:name]) if params[:name].present?
@users = @users.sort(@sort = params[:sort])
@users = @users.alphabetically.page(params[:page]) @users = @users.alphabetically.page(params[:page])
end end
......
...@@ -7,7 +7,6 @@ class ApplicationController < ActionController::Base ...@@ -7,7 +7,6 @@ class ApplicationController < ActionController::Base
before_filter :check_password_expiration before_filter :check_password_expiration
before_filter :add_abilities 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?
...@@ -81,28 +80,31 @@ class ApplicationController < ActionController::Base ...@@ -81,28 +80,31 @@ class ApplicationController < ActionController::Base
end end
def project def project
id = params[:project_id] || params[:id] unless @project
id = params[:project_id] || params[:id]
# Redirect from
# localhost/group/project.git # Redirect from
# to # localhost/group/project.git
# localhost/group/project # to
# # localhost/group/project
if id =~ /\.git\Z/ #
redirect_to request.original_url.gsub(/\.git\Z/, '') and return if id =~ /\.git\Z/
end redirect_to request.original_url.gsub(/\.git\Z/, '') and return
end
@project = Project.find_with_namespace(id) @project = Project.find_with_namespace(id)
if @project and can?(current_user, :read_project, @project) if @project and can?(current_user, :read_project, @project)
@project @project
elsif current_user.nil? elsif current_user.nil?
@project = nil @project = nil
authenticate_user! authenticate_user!
else else
@project = nil @project = nil
render_404 and return render_404 and return
end
end end
@project
end end
def repository def repository
...@@ -119,14 +121,6 @@ class ApplicationController < ActionController::Base ...@@ -119,14 +121,6 @@ class ApplicationController < ActionController::Base
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 +164,6 @@ class ApplicationController < ActionController::Base ...@@ -170,9 +164,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'
......
...@@ -19,6 +19,7 @@ class Groups::GroupMembersController < ApplicationController ...@@ -19,6 +19,7 @@ class Groups::GroupMembersController < ApplicationController
def destroy def destroy
@users_group = @group.group_members.find(params[:id]) @users_group = @group.group_members.find(params[:id])
if can?(current_user, :destroy, @users_group) # May fail if last owner. if can?(current_user, :destroy, @users_group) # May fail if last owner.
@users_group.destroy @users_group.destroy
respond_to do |format| respond_to do |format|
......
...@@ -15,15 +15,17 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -15,15 +15,17 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
error.to_s.humanize if error error.to_s.humanize if error
end end
# We only find ourselves here
# if the authentication to LDAP was successful.
def ldap def ldap
# We only find ourselves here @user = Gitlab::LDAP::User.new(oauth)
# if the authentication to LDAP was successful. @user.save if @user.changed? # will also save new users
@user = Gitlab::LDAP::User.find_or_create(oauth) gl_user = @user.gl_user
@user.remember_me = true if @user.persisted? gl_user.remember_me = true if @user.persisted?
# Do additional LDAP checks for the user filter and EE features # Do additional LDAP checks for the user filter and EE features
if Gitlab::LDAP::Access.allowed?(@user) if @user.allowed?
sign_in_and_redirect(@user) sign_in_and_redirect(gl_user)
else else
flash[:alert] = "Access denied for your LDAP account." flash[:alert] = "Access denied for your LDAP account."
redirect_to new_user_session_path redirect_to new_user_session_path
...@@ -46,26 +48,28 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -46,26 +48,28 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
current_user.save current_user.save
redirect_to profile_path redirect_to profile_path
else else
@user = Gitlab::OAuth::User.find(oauth) @user = Gitlab::OAuth::User.new(oauth)
@user.save
# Create user if does not exist # Only allow properly saved users to login.
# and allow_single_sign_on is true if @user.persisted? && @user.valid?
if Gitlab.config.omniauth['allow_single_sign_on'] && !@user sign_in_and_redirect(@user.gl_user)
@user, errors = Gitlab::OAuth::User.create(oauth)
end
if @user && !errors
sign_in_and_redirect(@user)
else else
if errors error_message =
error_message = errors.map{ |attribute, message| "#{attribute} #{message}" }.join(", ") if @user.gl_user.errors.any?
redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return @user.gl_user.errors.map do |attribute, message|
else "#{attribute} #{message}"
flash[:notice] = "There's no such user!" end.join(", ")
end else
redirect_to new_user_session_path ''
end
redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
end end
end end
rescue StandardError
flash[:notice] = "There's no such user!"
redirect_to new_user_session_path
end end
def oauth def oauth
......
...@@ -2,7 +2,7 @@ class Projects::BaseTreeController < Projects::ApplicationController ...@@ -2,7 +2,7 @@ class Projects::BaseTreeController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
before_filter :authorize_read_project! before_filter :authorize_read_project!
before_filter :authorize_code_access! before_filter :authorize_download_code!
before_filter :require_non_empty_project before_filter :require_non_empty_project
end end
...@@ -4,7 +4,7 @@ class Projects::BlameController < Projects::ApplicationController ...@@ -4,7 +4,7 @@ class Projects::BlameController < Projects::ApplicationController
# Authorize # Authorize
before_filter :authorize_read_project! before_filter :authorize_read_project!
before_filter :authorize_code_access! before_filter :authorize_download_code!
before_filter :require_non_empty_project before_filter :require_non_empty_project
def show def show
......
...@@ -4,9 +4,9 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -4,9 +4,9 @@ class Projects::BlobController < Projects::ApplicationController
# Authorize # Authorize
before_filter :authorize_read_project! before_filter :authorize_read_project!
before_filter :authorize_code_access! before_filter :authorize_download_code!
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
...@@ -20,7 +20,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -20,7 +20,7 @@ class Projects::BlobController < Projects::ApplicationController
flash[:notice] = "Your changes have been successfully committed" flash[:notice] = "Your changes have been successfully committed"
redirect_to project_tree_path(@project, @ref) redirect_to project_tree_path(@project, @ref)
else else
flash[:alert] = result[:error] flash[:alert] = result[:message]
render :show render :show
end end
end end
......
...@@ -3,8 +3,8 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -3,8 +3,8 @@ class Projects::BranchesController < Projects::ApplicationController
before_filter :authorize_read_project! 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'
......
...@@ -4,19 +4,19 @@ ...@@ -4,19 +4,19 @@
class Projects::CommitController < Projects::ApplicationController class Projects::CommitController < Projects::ApplicationController
# Authorize # Authorize
before_filter :authorize_read_project! before_filter :authorize_read_project!
before_filter :authorize_code_access! before_filter :authorize_download_code!
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 +32,6 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -32,6 +32,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
...@@ -5,7 +5,7 @@ class Projects::CommitsController < Projects::ApplicationController ...@@ -5,7 +5,7 @@ class Projects::CommitsController < Projects::ApplicationController
# Authorize # Authorize
before_filter :authorize_read_project! before_filter :authorize_read_project!
before_filter :authorize_code_access! before_filter :authorize_download_code!
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_read_project!
before_filter :authorize_code_access! before_filter :authorize_download_code!
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
...@@ -22,7 +22,7 @@ class Projects::EditTreeController < Projects::BaseTreeController ...@@ -22,7 +22,7 @@ class Projects::EditTreeController < Projects::BaseTreeController
redirect_to after_edit_path redirect_to after_edit_path
else else
flash[:alert] = result[:error] flash[:alert] = result[:message]
render :show render :show
end end
end end
......
class Projects::GraphsController < Projects::ApplicationController class Projects::GraphsController < Projects::ApplicationController
# Authorize # Authorize
before_filter :authorize_read_project! before_filter :authorize_read_project!
before_filter :authorize_code_access! before_filter :authorize_download_code!
before_filter :require_non_empty_project before_filter :require_non_empty_project
def show def show
......
...@@ -4,7 +4,7 @@ class Projects::NetworkController < Projects::ApplicationController ...@@ -4,7 +4,7 @@ class Projects::NetworkController < Projects::ApplicationController
# Authorize # Authorize
before_filter :authorize_read_project! before_filter :authorize_read_project!
before_filter :authorize_code_access! before_filter :authorize_download_code!
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
......
...@@ -4,7 +4,7 @@ class Projects::RawController < Projects::ApplicationController ...@@ -4,7 +4,7 @@ class Projects::RawController < Projects::ApplicationController
# Authorize # Authorize
before_filter :authorize_read_project! before_filter :authorize_read_project!
before_filter :authorize_code_access! before_filter :authorize_download_code!
before_filter :require_non_empty_project before_filter :require_non_empty_project
def show def show
......
...@@ -3,7 +3,7 @@ class Projects::RefsController < Projects::ApplicationController ...@@ -3,7 +3,7 @@ class Projects::RefsController < Projects::ApplicationController
# Authorize # Authorize
before_filter :authorize_read_project! before_filter :authorize_read_project!
before_filter :authorize_code_access! before_filter :authorize_download_code!
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_read_project!
before_filter :authorize_code_access! before_filter :authorize_download_code!
before_filter :require_non_empty_project before_filter :require_non_empty_project
def archive def archive
......
...@@ -17,7 +17,10 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -17,7 +17,10 @@ class Projects::SnippetsController < Projects::ApplicationController
respond_to :html respond_to :html
def index def index
@snippets = @project.snippets.fresh.non_expired @snippets = SnippetsFinder.new.execute(current_user, {
filter: :by_project,
project: @project
})
end end
def new def new
...@@ -88,6 +91,6 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -88,6 +91,6 @@ class Projects::SnippetsController < Projects::ApplicationController
end end
def snippet_params def snippet_params
params.require(:project_snippet).permit(:title, :content, :file_name, :private) params.require(:project_snippet).permit(:title, :content, :file_name, :private, :visibility_level)
end end
end end
...@@ -3,8 +3,8 @@ class Projects::TagsController < Projects::ApplicationController ...@@ -3,8 +3,8 @@ class Projects::TagsController < Projects::ApplicationController
before_filter :authorize_read_project! 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] before_filter :authorize_push_code!, only: [:create]
before_filter :authorize_admin_project!, only: [:destroy] before_filter :authorize_admin_project!, only: [:destroy]
def index def index
......
...@@ -10,7 +10,7 @@ class Projects::TeamMembersController < Projects::ApplicationController ...@@ -10,7 +10,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
...@@ -26,7 +26,7 @@ class Projects::TeamMembersController < Projects::ApplicationController ...@@ -26,7 +26,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?
...@@ -36,7 +36,7 @@ class Projects::TeamMembersController < Projects::ApplicationController ...@@ -36,7 +36,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|
...@@ -46,7 +46,7 @@ class Projects::TeamMembersController < Projects::ApplicationController ...@@ -46,7 +46,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 }
......
...@@ -6,7 +6,6 @@ class ProjectsController < ApplicationController ...@@ -6,7 +6,6 @@ class ProjectsController < ApplicationController
# Authorize # Authorize
before_filter :authorize_read_project!, except: [:index, :new, :create] 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]
...@@ -76,7 +75,7 @@ class ProjectsController < ApplicationController ...@@ -76,7 +75,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 +97,7 @@ class ProjectsController < ApplicationController ...@@ -98,7 +97,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 +147,8 @@ class ProjectsController < ApplicationController ...@@ -148,8 +147,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 +156,8 @@ class ProjectsController < ApplicationController ...@@ -157,8 +156,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 }
......
...@@ -18,6 +18,10 @@ class SessionsController < Devise::SessionsController ...@@ -18,6 +18,10 @@ class SessionsController < Devise::SessionsController
store_location_for(:redirect, redirect_path) store_location_for(:redirect, redirect_path)
end end
if Gitlab.config.ldap.enabled
@ldap_servers = Gitlab::LDAP::Config.servers
end
super super
end end
......
...@@ -9,12 +9,14 @@ class SnippetsController < ApplicationController ...@@ -9,12 +9,14 @@ class SnippetsController < ApplicationController
before_filter :set_title before_filter :set_title
skip_before_filter :authenticate_user!, only: [:index, :user_index]
respond_to :html respond_to :html
layout 'navless' layout :determine_layout
def index def index
@snippets = Snippet.are_internal.fresh.non_expired.page(params[:page]).per(20) @snippets = SnippetsFinder.new.execute(current_user, filter: :all).page(params[:page]).per(20)
end end
def user_index def user_index
...@@ -22,22 +24,11 @@ class SnippetsController < ApplicationController ...@@ -22,22 +24,11 @@ class SnippetsController < ApplicationController
render_404 and return unless @user render_404 and return unless @user
@snippets = @user.snippets.fresh.non_expired @snippets = SnippetsFinder.new.execute(current_user, {
filter: :by_user,
if @user == current_user user: @user,
@snippets = case params[:scope] scope: params[:scope]}).
when 'are_internal' then page(params[:page]).per(20)
@snippets.are_internal
when 'are_private' then
@snippets.are_private
else
@snippets
end
else
@snippets = @snippets.are_internal
end
@snippets = @snippets.page(params[:page]).per(20)
if @user == current_user if @user == current_user
render 'current_user_index' render 'current_user_index'
...@@ -95,7 +86,14 @@ class SnippetsController < ApplicationController ...@@ -95,7 +86,14 @@ class SnippetsController < ApplicationController
protected protected
def snippet def snippet
@snippet ||= PersonalSnippet.where('author_id = :user_id or private is false', user_id: current_user.id).find(params[:id]) @snippet ||= if current_user
PersonalSnippet.where("author_id = ? OR visibility_level IN (?)",
current_user.id,
[Snippet::PUBLIC, Snippet::INTERNAL]).
find(params[:id])
else
PersonalSnippet.are_public.find(params[:id])
end
end end
def authorize_modify_snippet! def authorize_modify_snippet!
...@@ -111,6 +109,10 @@ class SnippetsController < ApplicationController ...@@ -111,6 +109,10 @@ class SnippetsController < ApplicationController
end end
def snippet_params def snippet_params
params.require(:personal_snippet).permit(:title, :content, :file_name, :private) params.require(:personal_snippet).permit(:title, :content, :file_name, :private, :visibility_level)
end
def determine_layout
current_user ? 'navless' : 'public_users'
end end
end end
class SnippetsFinder
def execute(current_user, params = {})
filter = params[:filter]
case filter
when :all then
snippets(current_user).fresh.non_expired
when :by_user then
by_user(current_user, params[:user], params[:scope])
when :by_project
by_project(current_user, params[:project])
end
end
private
def snippets(current_user)
if current_user
Snippet.public_and_internal
else
# Not authenticated
#
# Return only:
# public snippets
Snippet.are_public
end
end
def by_user(current_user, user, scope)
snippets = user.snippets.fresh.non_expired
if user == current_user
case scope
when 'are_internal' then
snippets.are_internal
when 'are_private' then
snippets.are_private
when 'are_public' then
snippets.are_public
else
snippets
end
else
snippets.public_and_internal
end
end
def by_project(current_user, project)
snippets = project.snippets.fresh.non_expired
if current_user
if project.team.member?(current_user.id)
snippets
else
snippets.public_and_internal
end
else
snippets.are_public
end
end
end
...@@ -120,4 +120,8 @@ module CommitsHelper ...@@ -120,4 +120,8 @@ module CommitsHelper
class: 'commit-short-id') class: 'commit-short-id')
end end
end end
def truncate_sha(sha)
Commit.truncate_sha(sha)
end
end end
...@@ -136,9 +136,8 @@ module EventsHelper ...@@ -136,9 +136,8 @@ module EventsHelper
end end
def event_note(text) def event_note(text)
text = first_line_in_markdown(text) text = first_line_in_markdown(text, 150)
text = truncate(text, length: 150) sanitize(text, tags: %w(a img b pre code p))
sanitize(markdown(text), tags: %w(a img b pre p))
end end
def event_commit_title(message) def event_commit_title(message)
......
...@@ -51,12 +51,14 @@ module GitlabMarkdownHelper ...@@ -51,12 +51,14 @@ module GitlabMarkdownHelper
@markdown.render(text).html_safe @markdown.render(text).html_safe
end end
def first_line_in_markdown(text) # Return the first line of +text+, up to +max_chars+, after parsing the line
line = text.split("\n").detect do |i| # as Markdown. HTML tags in the parsed output are not counted toward the
i.present? && markdown(i).present? # +max_chars+ limit. If the length limit falls within a tag's contents, then
end # the tag contents are truncated without removing the closing tag.
line += '...' unless line.nil? def first_line_in_markdown(text, max_chars = nil)
line md = markdown(text).strip
truncate_visible(md, max_chars || md.length) if md.present?
end end
def render_wiki_content(wiki_page) def render_wiki_content(wiki_page)
...@@ -204,4 +206,52 @@ module GitlabMarkdownHelper ...@@ -204,4 +206,52 @@ module GitlabMarkdownHelper
def correct_ref def correct_ref
@ref ? @ref : "master" @ref ? @ref : "master"
end end
private
# Return +text+, truncated to +max_chars+ characters, excluding any HTML
# tags.
def truncate_visible(text, max_chars)
doc = Nokogiri::HTML.fragment(text)
content_length = 0
truncated = false
doc.traverse do |node|
if node.text? || node.content.empty?
if truncated
node.remove
next
end
# Handle line breaks within a node
if node.content.strip.lines.length > 1
node.content = "#{node.content.lines.first.chomp}..."
truncated = true
end
num_remaining = max_chars - content_length
if node.content.length > num_remaining
node.content = node.content.truncate(num_remaining)
truncated = true
end
content_length += node.content.length
end
truncated = truncate_if_block(node, truncated)
end
doc.to_html
end
# Used by #truncate_visible. If +node+ is the first block element, and the
# text hasn't already been truncated, then append "..." to the node contents
# and return true. Otherwise return false.
def truncate_if_block(node, truncated)
if node.element? && node.description.block? && !truncated
node.content = "#{node.content}..." if node.next_sibling
true
else
truncated
end
end
end end
module OauthHelper module OauthHelper
def ldap_enabled? def ldap_enabled?
Devise.omniauth_providers.include?(:ldap) Gitlab.config.ldap.enabled
end end
def default_providers def default_providers
......
...@@ -28,6 +28,23 @@ module VisibilityLevelHelper ...@@ -28,6 +28,23 @@ module VisibilityLevelHelper
end end
end end
def snippet_visibility_level_description(level)
capture_haml do
haml_tag :span do
case level
when Gitlab::VisibilityLevel::PRIVATE
haml_concat "The snippet is visible only for me"
when Gitlab::VisibilityLevel::INTERNAL
haml_concat "The snippet is visible for any logged in user."
when Gitlab::VisibilityLevel::PUBLIC
haml_concat "The snippet can be accessed"
haml_concat "without any"
haml_concat "authentication."
end
end
end
end
def visibility_level_icon(level) def visibility_level_icon(level)
case level case level
when Gitlab::VisibilityLevel::PRIVATE when Gitlab::VisibilityLevel::PRIVATE
......
...@@ -19,13 +19,24 @@ class Commit ...@@ -19,13 +19,24 @@ class Commit
class << self class << self
def decorate(commits) def decorate(commits)
commits.map { |c| self.new(c) } commits.map do |commit|
if commit.kind_of?(Commit)
commit
else
self.new(commit)
end
end
end end
# Calculate number of lines to render for diffs # Calculate number of lines to render for diffs
def diff_line_count(diffs) def diff_line_count(diffs)
diffs.reduce(0) { |sum, d| sum + d.diff.lines.count } diffs.reduce(0) { |sum, d| sum + d.diff.lines.count }
end end
# Truncate sha to 8 characters
def truncate_sha(sha)
sha[0..7]
end
end end
attr_accessor :raw attr_accessor :raw
...@@ -111,7 +122,7 @@ class Commit ...@@ -111,7 +122,7 @@ class Commit
# Mentionable override. # Mentionable override.
def gfm_reference def gfm_reference
"commit #{sha[0..5]}" "commit #{id}"
end end
def method_missing(m, *args, &block) def method_missing(m, *args, &block)
...@@ -124,6 +135,11 @@ class Commit ...@@ -124,6 +135,11 @@ class Commit
super super
end end
# Truncate sha to 8 characters
def short_id
@raw.short_id(7)
end
def parents def parents
@parents ||= Commit.decorate(super) @parents ||= Commit.decorate(super)
end end
......
...@@ -266,7 +266,7 @@ class Event < ActiveRecord::Base ...@@ -266,7 +266,7 @@ class Event < ActiveRecord::Base
end end
def note_short_commit_id def note_short_commit_id
note_commit_id[0..8] Commit.truncate_sha(note_commit_id)
end end
def note_commit? def note_commit?
......
# == Schema Information
#
# Table name: members
#
# id :integer not null, primary key
# access_level :integer not null
# source_id :integer not null
# source_type :string(255) not null
# user_id :integer not null
# notification_level :integer not null
# type :string(255)
# created_at :datetime
# updated_at :datetime
#
class Member < ActiveRecord::Base class Member < ActiveRecord::Base
include Notifiable include Notifiable
include Gitlab::Access include Gitlab::Access
......
# == Schema Information
#
# Table name: members
#
# id :integer not null, primary key
# access_level :integer not null
# source_id :integer not null
# source_type :string(255) not null
# user_id :integer not null
# notification_level :integer not null
# type :string(255)
# created_at :datetime
# updated_at :datetime
#
class GroupMember < Member class GroupMember < Member
SOURCE_TYPE = 'Namespace' SOURCE_TYPE = 'Namespace'
......
# == Schema Information
#
# Table name: members
#
# id :integer not null, primary key
# access_level :integer not null
# source_id :integer not null
# source_type :string(255) not null
# user_id :integer not null
# notification_level :integer not null
# type :string(255)
# created_at :datetime
# updated_at :datetime
#
class ProjectMember < Member class ProjectMember < Member
SOURCE_TYPE = 'Project' SOURCE_TYPE = 'Project'
......
...@@ -55,7 +55,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -55,7 +55,7 @@ class MergeRequestDiff < ActiveRecord::Base
end end
def last_commit_short_sha def last_commit_short_sha
@last_commit_short_sha ||= last_commit.sha[0..10] @last_commit_short_sha ||= last_commit.short_id
end end
private private
......
...@@ -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
} }
...@@ -174,7 +174,7 @@ class Note < ActiveRecord::Base ...@@ -174,7 +174,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 +182,16 @@ class Note < ActiveRecord::Base ...@@ -182,8 +182,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.
...@@ -249,6 +257,10 @@ class Note < ActiveRecord::Base ...@@ -249,6 +257,10 @@ class Note < ActiveRecord::Base
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?
......
...@@ -2,17 +2,17 @@ ...@@ -2,17 +2,17 @@
# #
# Table name: snippets # Table name: snippets
# #
# id :integer not null, primary key # id :integer not null, primary key
# title :string(255) # title :string(255)
# content :text # content :text
# author_id :integer not null # author_id :integer not null
# project_id :integer # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# file_name :string(255) # file_name :string(255)
# expires_at :datetime # expires_at :datetime
# private :boolean default(TRUE), not null # type :string(255)
# type :string(255) # visibility_level :integer default(0), not null
# #
class PersonalSnippet < Snippet class PersonalSnippet < Snippet
......
...@@ -173,7 +173,7 @@ class Project < ActiveRecord::Base ...@@ -173,7 +173,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
......
...@@ -2,14 +2,14 @@ ...@@ -2,14 +2,14 @@
# #
# Table name: services # Table name: services
# #
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# #
class AssemblaService < Service class AssemblaService < Service
......
...@@ -2,14 +2,14 @@ ...@@ -2,14 +2,14 @@
# #
# Table name: services # Table name: services
# #
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# property :text # properties :text
# #
class BuildboxService < CiService class BuildboxService < CiService
......
...@@ -2,14 +2,14 @@ ...@@ -2,14 +2,14 @@
# #
# Table name: services # Table name: services
# #
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# #
class CampfireService < Service class CampfireService < Service
......
...@@ -2,14 +2,14 @@ ...@@ -2,14 +2,14 @@
# #
# Table name: services # Table name: services
# #
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# #
# Base class for CI services # Base class for CI services
......
...@@ -2,14 +2,14 @@ ...@@ -2,14 +2,14 @@
# #
# Table name: services # Table name: services
# #
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# #
class EmailsOnPushService < Service class EmailsOnPushService < Service
......
...@@ -2,14 +2,14 @@ ...@@ -2,14 +2,14 @@
# #
# Table name: services # Table name: services
# #
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# #
require "flowdock-git-hook" require "flowdock-git-hook"
......
...@@ -2,14 +2,14 @@ ...@@ -2,14 +2,14 @@
# #
# Table name: services # Table name: services
# #
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# #
require "gemnasium/gitlab_service" require "gemnasium/gitlab_service"
......
...@@ -2,14 +2,14 @@ ...@@ -2,14 +2,14 @@
# #
# Table name: services # Table name: services
# #
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# property :text # properties :text
# #
class GitlabCiService < CiService class GitlabCiService < CiService
......
...@@ -2,14 +2,14 @@ ...@@ -2,14 +2,14 @@
# #
# Table name: services # Table name: services
# #
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# #
class HipchatService < Service class HipchatService < Service
......
...@@ -2,14 +2,14 @@ ...@@ -2,14 +2,14 @@
# #
# Table name: services # Table name: services
# #
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# #
class PivotaltrackerService < Service class PivotaltrackerService < Service
......
...@@ -2,14 +2,14 @@ ...@@ -2,14 +2,14 @@
# #
# Table name: services # Table name: services
# #
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# #
class SlackService < Service class SlackService < Service
...@@ -40,7 +40,8 @@ class SlackService < Service ...@@ -40,7 +40,8 @@ class SlackService < Service
project_name: project_name project_name: project_name
)) ))
credentials = webhook.match(/(\w*).slack.com.*services\/(.*)/) credentials = webhook.match(/([\w-]*).slack.com.*services\/(.*)/)
if credentials.present? if credentials.present?
subdomain = credentials[1] subdomain = credentials[1]
token = credentials[2].split("token=").last token = credentials[2].split("token=").last
......
...@@ -2,17 +2,17 @@ ...@@ -2,17 +2,17 @@
# #
# Table name: snippets # Table name: snippets
# #
# id :integer not null, primary key # id :integer not null, primary key
# title :string(255) # title :string(255)
# content :text # content :text
# author_id :integer not null # author_id :integer not null
# project_id :integer # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# file_name :string(255) # file_name :string(255)
# expires_at :datetime # expires_at :datetime
# private :boolean default(TRUE), not null # type :string(255)
# type :string(255) # visibility_level :integer default(0), not null
# #
class ProjectSnippet < Snippet class ProjectSnippet < Snippet
......
...@@ -133,6 +133,10 @@ class ProjectTeam ...@@ -133,6 +133,10 @@ class ProjectTeam
max_tm_access(user.id) == Gitlab::Access::MASTER max_tm_access(user.id) == Gitlab::Access::MASTER
end end
def member?(user_id)
!!find_tm(user_id)
end
def max_tm_access(user_id) def max_tm_access(user_id)
access = [] access = []
access << project.project_members.find_by(user_id: user_id).try(:access_field) access << project.project_members.find_by(user_id: user_id).try(:access_field)
......
...@@ -30,6 +30,8 @@ class Repository ...@@ -30,6 +30,8 @@ class Repository
commit = Gitlab::Git::Commit.find(raw_repository, id) commit = Gitlab::Git::Commit.find(raw_repository, id)
commit = Commit.new(commit) if commit commit = Commit.new(commit) if commit
commit commit
rescue Rugged::OdbError => ex
nil
end end
def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false) def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false)
......
...@@ -2,14 +2,15 @@ ...@@ -2,14 +2,15 @@
# #
# Table name: services # Table name: services
# #
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
#
# To add new service you should build a class inherited from Service # To add new service you should build a class inherited from Service
# and implement a set of methods # and implement a set of methods
......
...@@ -2,23 +2,24 @@ ...@@ -2,23 +2,24 @@
# #
# Table name: snippets # Table name: snippets
# #
# id :integer not null, primary key # id :integer not null, primary key
# title :string(255) # title :string(255)
# content :text # content :text
# author_id :integer not null # author_id :integer not null
# project_id :integer # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# file_name :string(255) # file_name :string(255)
# expires_at :datetime # expires_at :datetime
# private :boolean default(TRUE), not null # type :string(255)
# type :string(255) # visibility_level :integer default(0), not null
# #
class Snippet < ActiveRecord::Base class Snippet < ActiveRecord::Base
include Linguist::BlobHelper include Linguist::BlobHelper
include Gitlab::VisibilityLevel
default_value_for :private, true default_value_for :visibility_level, Snippet::PRIVATE
belongs_to :author, class_name: "User" belongs_to :author, class_name: "User"
...@@ -30,10 +31,13 @@ class Snippet < ActiveRecord::Base ...@@ -30,10 +31,13 @@ class Snippet < ActiveRecord::Base
validates :title, presence: true, length: { within: 0..255 } validates :title, presence: true, length: { within: 0..255 }
validates :file_name, presence: true, length: { within: 0..255 } validates :file_name, presence: true, length: { within: 0..255 }
validates :content, presence: true validates :content, presence: true
validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values }
# Scopes # Scopes
scope :are_internal, -> { where(private: false) } scope :are_internal, -> { where(visibility_level: Snippet::INTERNAL) }
scope :are_private, -> { where(private: true) } scope :are_private, -> { where(visibility_level: Snippet::PRIVATE) }
scope :are_public, -> { where(visibility_level: Snippet::PUBLIC) }
scope :public_and_internal, -> { where(visibility_level: [Snippet::PUBLIC, Snippet::INTERNAL]) }
scope :fresh, -> { order("created_at DESC") } scope :fresh, -> { order("created_at DESC") }
scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) } scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) }
scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) } scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) }
...@@ -66,6 +70,10 @@ class Snippet < ActiveRecord::Base ...@@ -66,6 +70,10 @@ class Snippet < ActiveRecord::Base
expires_at && expires_at < Time.current expires_at && expires_at < Time.current
end end
def visibility_level_field
visibility_level
end
class << self class << self
def search(query) def search(query)
where('(title LIKE :query OR file_name LIKE :query)', query: "%#{query}%") where('(title LIKE :query OR file_name LIKE :query)', query: "%#{query}%")
...@@ -76,7 +84,7 @@ class Snippet < ActiveRecord::Base ...@@ -76,7 +84,7 @@ class Snippet < ActiveRecord::Base
end end
def accessible_to(user) def accessible_to(user)
where('private = ? OR author_id = ?', false, user) where('visibility_level IN (?) OR author_id = ?', [Snippet::INTERNAL, Snippet::PUBLIC], user)
end end
end end
end end
...@@ -178,8 +178,7 @@ class User < ActiveRecord::Base ...@@ -178,8 +178,7 @@ class User < ActiveRecord::Base
scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) } scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all } scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') } scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') }
scope :ldap, -> { where(provider: 'ldap') } scope :ldap, -> { where('provider LIKE ?', 'ldap%') }
scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active } scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active }
# #
...@@ -196,6 +195,16 @@ class User < ActiveRecord::Base ...@@ -196,6 +195,16 @@ class User < ActiveRecord::Base
end end
end end
def sort(method)
case method.to_s
when 'recent_sign_in' then reorder('users.last_sign_in_at DESC')
when 'oldest_sign_in' then reorder('users.last_sign_in_at ASC')
when 'recently_created' then reorder('users.created_at DESC')
when 'late_created' then reorder('users.created_at ASC')
else reorder("users.name ASC")
end
end
def find_for_commit(email, name) def find_for_commit(email, name)
# Prefer email match over name match # Prefer email match over name match
User.where(email: email).first || User.where(email: email).first ||
...@@ -397,7 +406,7 @@ class User < ActiveRecord::Base ...@@ -397,7 +406,7 @@ class User < ActiveRecord::Base
end end
def ldap_user? def ldap_user?
extern_uid && provider == 'ldap' extern_uid && provider.start_with?('ldap')
end end
def accessible_deploy_keys def accessible_deploy_keys
......
...@@ -10,12 +10,6 @@ module Files ...@@ -10,12 +10,6 @@ module Files
private private
def success
out = super()
out[:error] = ''
out
end
def repository def repository
project.repository project.repository
end end
......
...@@ -33,12 +33,5 @@ module Issues ...@@ -33,12 +33,5 @@ module Issues
issue issue
end end
private
def update_task(issue, params, checked)
issue.update_nth_task(params[:task_num].to_i, checked)
params.except!(:task_num)
end
end end
end end
...@@ -119,7 +119,7 @@ class NotificationService ...@@ -119,7 +119,7 @@ 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 }
......
...@@ -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"
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment