Commit 2e34a6d3c40a60ed689de5d7870fe663b1959e88

Authored by miks
2 parents fdb5c82c 8674fba1

Merge branch 'master' into project_hooks_api

Showing 58 changed files with 468 additions and 335 deletions   Show diff stats
Gemfile
1 1 source "http://rubygems.org"
2 2  
  3 +def darwin_only(require_as)
  4 + RUBY_PLATFORM.include?('darwin') && require_as
  5 +end
  6 +
  7 +def linux_only(require_as)
  8 + RUBY_PLATFORM.include?('linux') && require_as
  9 +end
  10 +
3 11 gem "rails", "3.2.8"
4 12  
5 13 # Supported DBs
... ... @@ -44,7 +52,8 @@ gem "ffaker"
44 52 gem "seed-fu"
45 53  
46 54 # Markdown to HTML
47   -gem "redcarpet", "~> 2.1.1"
  55 +gem "redcarpet", "~> 2.1.1"
  56 +gem "github-markup", "~> 0.7.4"
48 57  
49 58 # Servers
50 59 gem "thin"
... ... @@ -101,13 +110,20 @@ group :development, :test do
101 110 gem "capybara"
102 111 gem "capybara-webkit"
103 112 gem "headless"
104   - gem "autotest"
105   - gem "autotest-rails"
106 113 gem "pry"
107 114 gem "awesome_print"
108 115 gem "database_cleaner"
109 116 gem "launchy"
110 117 gem 'factory_girl_rails'
  118 +
  119 + # Guard
  120 + gem 'guard-rspec'
  121 + gem 'guard-cucumber'
  122 +
  123 + # Notification
  124 + gem 'rb-fsevent', :require => darwin_only('rb-fsevent')
  125 + gem 'growl', :require => darwin_only('growl')
  126 + gem 'rb-inotify', :require => linux_only('rb-inotify')
111 127 end
112 128  
113 129 group :test do
... ...
Gemfile.lock
... ... @@ -68,7 +68,6 @@ GIT
68 68 GEM
69 69 remote: http://rubygems.org/
70 70 specs:
71   - ZenTest (4.8.1)
72 71 actionmailer (3.2.8)
73 72 actionpack (= 3.2.8)
74 73 mail (~> 2.4.4)
... ... @@ -100,10 +99,6 @@ GEM
100 99 rails (~> 3.0)
101 100 addressable (2.2.8)
102 101 arel (3.0.2)
103   - autotest (4.4.6)
104   - ZenTest (>= 4.4.1)
105   - autotest-rails (4.1.2)
106   - ZenTest (~> 4.5)
107 102 awesome_print (1.0.2)
108 103 bcrypt-ruby (3.0.1)
109 104 blankslate (2.1.2.4)
... ... @@ -178,6 +173,7 @@ GEM
178 173 gherkin (2.11.0)
179 174 json (>= 1.4.6)
180 175 git (1.2.5)
  176 + github-markup (0.7.4)
181 177 gitlab_meta (2.9)
182 178 grape (0.2.1)
183 179 hashie (~> 1.2)
... ... @@ -185,6 +181,15 @@ GEM
185 181 multi_xml
186 182 rack
187 183 rack-mount
  184 + growl (1.0.3)
  185 + guard (1.3.2)
  186 + listen (>= 0.4.2)
  187 + thor (>= 0.14.6)
  188 + guard-cucumber (1.2.0)
  189 + cucumber (>= 1.2.0)
  190 + guard (>= 1.1.0)
  191 + guard-rspec (1.2.1)
  192 + guard (>= 1.1)
188 193 haml (3.1.6)
189 194 haml-rails (0.3.4)
190 195 actionpack (~> 3.0)
... ... @@ -218,6 +223,7 @@ GEM
218 223 libv8 (3.3.10.4)
219 224 libwebsocket (0.1.3)
220 225 addressable
  226 + listen (0.5.0)
221 227 mail (2.4.4)
222 228 i18n (>= 0.4.0)
223 229 mime-types (~> 1.16)
... ... @@ -273,6 +279,9 @@ GEM
273 279 raindrops (0.9.0)
274 280 rake (0.9.2.2)
275 281 raphael-rails (1.5.2)
  282 + rb-fsevent (0.9.1)
  283 + rb-inotify (0.8.8)
  284 + ffi (>= 0.5.0)
276 285 rdoc (3.12)
277 286 json (~> 1.4)
278 287 redcarpet (2.1.1)
... ... @@ -376,8 +385,6 @@ PLATFORMS
376 385 DEPENDENCIES
377 386 acts-as-taggable-on (= 2.3.1)
378 387 annotate!
379   - autotest
380   - autotest-rails
381 388 awesome_print
382 389 bootstrap-sass (= 2.0.4)
383 390 capybara
... ... @@ -396,11 +403,15 @@ DEPENDENCIES
396 403 ffaker
397 404 foreman
398 405 git
  406 + github-markup (~> 0.7.4)
399 407 gitlab_meta (= 2.9)
400 408 gitolite!
401 409 grack!
402 410 grape (~> 0.2.1)
403 411 grit!
  412 + growl
  413 + guard-cucumber
  414 + guard-rspec
404 415 haml-rails
405 416 headless
406 417 httparty
... ... @@ -418,6 +429,8 @@ DEPENDENCIES
418 429 rack-mini-profiler
419 430 rails (= 3.2.8)
420 431 raphael-rails (= 1.5.2)
  432 + rb-fsevent
  433 + rb-inotify
421 434 redcarpet (~> 2.1.1)
422 435 resque (~> 1.20.0)
423 436 resque_mailer
... ...
Guardfile 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +# A sample Guardfile
  2 +# More info at https://github.com/guard/guard#readme
  3 +
  4 +guard 'rspec', :version => 2 do
  5 + watch(%r{^spec/.+_spec\.rb$})
  6 + watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
  7 + watch('spec/spec_helper.rb') { "spec" }
  8 +
  9 + # Rails example
  10 + watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
  11 + watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
  12 + watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
  13 + watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
  14 + watch('config/routes.rb') { "spec/routing" }
  15 + watch('app/controllers/application_controller.rb') { "spec/controllers" }
  16 +
  17 + # Capybara request specs
  18 + watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
  19 +
  20 + # Turnip features and steps
  21 + watch(%r{^spec/acceptance/(.+)\.feature$})
  22 + watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
  23 +end
  24 +
  25 +
  26 +guard 'cucumber' do
  27 + watch(%r{^features/.+\.feature$})
  28 + watch(%r{^features/support/.+$}) { 'features' }
  29 + watch(%r{^features/step_definitions/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'features' }
  30 +end
... ...
app/assets/javascripts/issues.js
... ... @@ -5,7 +5,7 @@ function switchToNewIssue(form){
5 5 $('select#issue_milestone_id').chosen();
6 6 $("#new_issue_dialog").show("fade", { direction: "right" }, 150);
7 7 $('.top-tabs .add_new').hide();
8   - disableButtonIfEmtpyField("#issue_title", ".save-btn");
  8 + disableButtonIfEmptyField("#issue_title", ".save-btn");
9 9 });
10 10 }
11 11  
... ... @@ -16,7 +16,7 @@ function switchToEditIssue(form){
16 16 $('select#issue_milestone_id').chosen();
17 17 $("#edit_issue_dialog").show("fade", { direction: "right" }, 150);
18 18 $('.add_new').hide();
19   - disableButtonIfEmtpyField("#issue_title", ".save-btn");
  19 + disableButtonIfEmptyField("#issue_title", ".save-btn");
20 20 });
21 21 }
22 22  
... ...
app/assets/javascripts/main.js
... ... @@ -1,130 +0,0 @@
1   -$(document).ready(function(){
2   -
3   - $(".one_click_select").live("click", function(){
4   - $(this).select();
5   - });
6   -
7   - $('body').on('ajax:complete, ajax:beforeSend, submit', 'form', function(e){
8   - var buttons = $('[type="submit"]', this);
9   - switch( e.type ){
10   - case 'ajax:beforeSend':
11   - case 'submit':
12   - buttons.attr('disabled', 'disabled');
13   - break;
14   - case ' ajax:complete':
15   - default:
16   - buttons.removeAttr('disabled');
17   - break;
18   - }
19   - })
20   -
21   - $(".account-box").mouseenter(showMenu);
22   - $(".account-box").mouseleave(resetMenu);
23   -
24   - $("#projects-list .project").live('click', function(e){
25   - if(e.target.nodeName != "A" && e.target.nodeName != "INPUT") {
26   - location.href = $(this).attr("url");
27   - e.stopPropagation();
28   - return false;
29   - }
30   - });
31   -
32   - /**
33   - * Focus search field by pressing 's' key
34   - */
35   - $(document).keypress(function(e) {
36   - if( $(e.target).is(":input") ) return;
37   - switch(e.which) {
38   - case 115: focusSearch();
39   - e.preventDefault();
40   - }
41   - });
42   -
43   - /**
44   - * Commit show suppressed diff
45   - *
46   - */
47   - $(".supp_diff_link").bind("click", function() {
48   - showDiff(this);
49   - });
50   -
51   - /**
52   - * Note markdown preview
53   - *
54   - */
55   - $(document).on('click', '#preview-link', function(e) {
56   - $('#preview-note').text('Loading...');
57   -
58   - var previewLinkText = ($(this).text() == 'Preview' ? 'Edit' : 'Preview');
59   - $(this).text(previewLinkText);
60   -
61   - var note = $('#note_note').val();
62   - if (note.trim().length === 0) { note = 'Nothing to preview'; }
63   - $.post($(this).attr('href'), {note: note}, function(data) {
64   - $('#preview-note').html(data);
65   - });
66   -
67   - $('#preview-note, #note_note').toggle();
68   - e.preventDefault();
69   - });
70   -});
71   -
72   -function focusSearch() {
73   - $("#search").focus();
74   -}
75   -
76   -function updatePage(data){
77   - $.ajax({type: "GET", url: location.href, data: data, dataType: "script"});
78   -}
79   -
80   -function showMenu() {
81   - $(this).toggleClass('hover');
82   -}
83   -
84   -function resetMenu() {
85   - $(this).removeClass("hover");
86   -}
87   -
88   -function slugify(text) {
89   - return text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase();
90   -}
91   -
92   -function showDiff(link) {
93   - $(link).next('table').show();
94   - $(link).remove();
95   -}
96   -
97   -(function($){
98   - var _chosen = $.fn.chosen;
99   - $.fn.extend({
100   - chosen: function(options) {
101   - var default_options = {'search_contains' : 'true'};
102   - $.extend(default_options, options);
103   - return _chosen.apply(this, [default_options]);
104   - }})
105   -})(jQuery);
106   -
107   -
108   -function ajaxGet(url) {
109   - $.ajax({type: "GET", url: url, dataType: "script"});
110   -}
111   -
112   -/**
113   - * Disable button if text field is empty
114   - */
115   -function disableButtonIfEmtpyField(field_selector, button_selector) {
116   - field = $(field_selector);
117   - if(field.val() == "") {
118   - field.closest("form").find(button_selector).attr("disabled", "disabled").addClass("disabled");
119   - }
120   -
121   - field.on('keyup', function(){
122   - var field = $(this);
123   - var closest_submit = field.closest("form").find(button_selector);
124   - if(field.val() == "") {
125   - closest_submit.attr("disabled", "disabled").addClass("disabled");
126   - } else {
127   - closest_submit.removeAttr("disabled").removeClass("disabled");
128   - }
129   - })
130   -}
app/assets/javascripts/main.js.coffee 0 → 100644
... ... @@ -0,0 +1,92 @@
  1 +window.updatePage = (data) ->
  2 + $.ajax({type: "GET", url: location.href, data: data, dataType: "script"})
  3 +
  4 +window.slugify = (text) ->
  5 + text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
  6 +
  7 +window.ajaxGet = (url) ->
  8 + $.ajax({type: "GET", url: url, dataType: "script"})
  9 +
  10 + # Disable button if text field is empty
  11 +window.disableButtonIfEmptyField = (field_selector, button_selector) ->
  12 + field = $(field_selector)
  13 + closest_submit = field.closest("form").find(button_selector)
  14 +
  15 + closest_submit.disable() if field.val() is ""
  16 +
  17 + field.on "keyup", ->
  18 + if $(this).val() is ""
  19 + closest_submit.disable()
  20 + else
  21 + closest_submit.enable()
  22 +
  23 +$ ->
  24 + # Click a .one_click_select field, select the contents
  25 + $(".one_click_select").live 'click', -> $(this).select()
  26 +
  27 + # Initialize chosen selects
  28 + $('select.chosen').chosen()
  29 +
  30 + # Disable form buttons while a form is submitting
  31 + $('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
  32 + buttons = $('[type="submit"]', this)
  33 +
  34 + switch e.type
  35 + when 'ajax:beforeSend', 'submit'
  36 + buttons.disable()
  37 + else
  38 + buttons.enable()
  39 +
  40 + # Show/Hide the profile menu when hovering the account box
  41 + $('.account-box').hover -> $(this).toggleClass('hover')
  42 +
  43 + # Focus search field by pressing 's' key
  44 + $(document).keypress (e) ->
  45 + # Don't do anything if typing in an input
  46 + return if $(e.target).is(":input")
  47 +
  48 + switch e.which
  49 + when 115
  50 + $("#search").focus()
  51 + e.preventDefault()
  52 +
  53 + # Commit show suppressed diff
  54 + $(".supp_diff_link").bind "click", ->
  55 + $(this).next('table').show()
  56 + $(this).remove()
  57 +
  58 + # Note markdown preview
  59 + $(document).on 'click', '#preview-link', (e) ->
  60 + $('#preview-note').text('Loading...')
  61 +
  62 + previewLinkText = if $(this).text() == 'Preview' then 'Edit' else 'Preview'
  63 + $(this).text(previewLinkText)
  64 +
  65 + note = $('#note_note').val()
  66 +
  67 + if note.trim().length == 0
  68 + $('#preview-note').text("Nothing to preview.")
  69 + else
  70 + $.post $(this).attr('href'), {note: note}, (data) ->
  71 + $('#preview-note').html(data)
  72 +
  73 + $('#preview-note, #note_note').toggle()
  74 + e.preventDefault()
  75 + false
  76 +
  77 +(($) ->
  78 + _chosen = $.fn.chosen
  79 + $.fn.extend chosen: (options) ->
  80 + default_options = search_contains: "true"
  81 + $.extend default_options, options
  82 + _chosen.apply this, [default_options]
  83 +
  84 + # Disable an element and add the 'disabled' Bootstrap class
  85 + $.fn.extend disable: ->
  86 + $(this).attr('disabled', 'disabled').addClass('disabled')
  87 +
  88 + # Enable an element and remove the 'disabled' Bootstrap class
  89 + $.fn.extend enable: ->
  90 + $(this).removeAttr('disabled').removeClass('disabled')
  91 +
  92 +)(jQuery)
... ...
app/assets/javascripts/note.js
... ... @@ -25,14 +25,14 @@ var NoteList = {
25 25 $(this).closest('li').fadeOut(); });
26 26  
27 27 $(".note-form-holder").live("ajax:before", function(){
28   - $(".submit_note").attr("disabled", "disabled");
  28 + $(".submit_note").disable()
29 29 })
30 30  
31 31 $(".note-form-holder").live("ajax:complete", function(){
32   - $(".submit_note").removeAttr("disabled");
  32 + $(".submit_note").enable()
33 33 })
34 34  
35   - disableButtonIfEmtpyField(".note-text", ".submit_note");
  35 + disableButtonIfEmptyField(".note-text", ".submit_note");
36 36  
37 37 $(".note-text").live("focus", function(){
38 38 $(this).css("height", "80px");
... ... @@ -177,6 +177,6 @@ var PerLineNotes = {
177 177 form.show();
178 178 return false;
179 179 });
180   - disableButtonIfEmtpyField(".line-note-text", ".submit_inline_note");
  180 + disableButtonIfEmptyField(".line-note-text", ".submit_inline_note");
181 181 }
182 182 }
... ...
app/assets/javascripts/projects.js.coffee
... ... @@ -8,7 +8,7 @@ window.Projects = ->
8 8 $('.save-project-loader').show()
9 9  
10 10 $('form #project_default_branch').chosen()
11   - disableButtonIfEmtpyField '#project_name', '.project-submit'
  11 + disableButtonIfEmptyField '#project_name', '.project-submit'
12 12  
13 13 # Git clone panel switcher
14 14 $ ->
... ...
app/assets/stylesheets/common.scss
... ... @@ -179,6 +179,15 @@ span.update-author {
179 179 &.merged {
180 180 background-color: #2A2;
181 181 }
  182 +
  183 + &.joined {
  184 + background-color: #1ca9dd;
  185 + }
  186 +
  187 + &.left {
  188 + background-color: #888;
  189 + float:none;
  190 + }
182 191 }
183 192  
184 193 form {
... ...
app/controllers/refs_controller.rb
  1 +require 'github/markup'
  2 +
1 3 class RefsController < ApplicationController
2 4 include Gitlab::Encode
3 5 before_filter :project
... ...
app/decorators/event_decorator.rb
... ... @@ -8,7 +8,9 @@ class EventDecorator &lt; ApplicationDecorator
8 8 "#{self.author_name} #{self.action_name} MR ##{self.target_id}:" + self.merge_request_title
9 9 elsif self.push?
10 10 "#{self.author_name} #{self.push_action_name} #{self.ref_type} " + self.ref_name
11   - else
  11 + elsif self.membership_changed?
  12 + "#{self.author_name} #{self.action_name} #{self.project.name}"
  13 + else
12 14 ""
13 15 end
14 16 end
... ...
app/helpers/projects_helper.rb
... ... @@ -2,5 +2,9 @@ module ProjectsHelper
2 2 def grouper_project_members(project)
3 3 @project.users_projects.sort_by(&:project_access).reverse.group_by(&:project_access)
4 4 end
  5 +
  6 + def remove_from_team_message(project, member)
  7 + "You are going to remove #{member.user_name} from #{project.name}. Are you sure?"
  8 + end
5 9 end
6 10  
... ...
app/helpers/tree_helper.rb
... ... @@ -24,4 +24,14 @@ module TreeHelper
24 24 content.name
25 25 end
26 26 end
  27 +
  28 + # Public: Determines if a given filename is compatible with GitHub::Markup.
  29 + #
  30 + # filename - Filename string to check
  31 + #
  32 + # Returns boolean
  33 + def markup?(filename)
  34 + filename.end_with?(*%w(.mdown .md .markdown .textile .rdoc .org .creole
  35 + .mediawiki .rst .asciidoc .pod))
  36 + end
27 37 end
... ...
app/models/event.rb
... ... @@ -10,6 +10,8 @@ class Event &lt; ActiveRecord::Base
10 10 Pushed = 5
11 11 Commented = 6
12 12 Merged = 7
  13 + Joined = 8 # User joined project
  14 + Left = 9 # User left project
13 15  
14 16 belongs_to :project
15 17 belongs_to :target, polymorphic: true
... ... @@ -37,7 +39,7 @@ class Event &lt; ActiveRecord::Base
37 39 # - new issue
38 40 # - merge request
39 41 def allowed?
40   - push? || issue? || merge_request?
  42 + push? || issue? || merge_request? || membership_changed?
41 43 end
42 44  
43 45 def push?
... ... @@ -84,6 +86,18 @@ class Event &lt; ActiveRecord::Base
84 86 [Closed, Reopened].include?(action)
85 87 end
86 88  
  89 + def joined?
  90 + action == Joined
  91 + end
  92 +
  93 + def left?
  94 + action == Left
  95 + end
  96 +
  97 + def membership_changed?
  98 + joined? || left?
  99 + end
  100 +
87 101 def issue
88 102 target if target_type == "Issue"
89 103 end
... ... @@ -101,6 +115,10 @@ class Event &lt; ActiveRecord::Base
101 115 "closed"
102 116 elsif merged?
103 117 "merged"
  118 + elsif joined?
  119 + 'joined'
  120 + elsif left?
  121 + 'left'
104 122 else
105 123 "opened"
106 124 end
... ...
app/models/merge_request.rb
... ... @@ -162,7 +162,7 @@ class MergeRequest &lt; ActiveRecord::Base
162 162 end
163 163  
164 164 def automerge!(current_user)
165   - if Gitlab::Merge.new(self, current_user).merge
  165 + if Gitlab::Merge.new(self, current_user).merge && self.unmerged_commits.empty?
166 166 self.merge!(current_user.id)
167 167 true
168 168 end
... ...
app/models/users_project.rb
... ... @@ -23,7 +23,7 @@ class UsersProject &lt; ActiveRecord::Base
23 23 def self.bulk_delete(project, user_ids)
24 24 UsersProject.transaction do
25 25 UsersProject.where(:user_id => user_ids, :project_id => project.id).each do |users_project|
26   - users_project.delete
  26 + users_project.destroy
27 27 end
28 28 end
29 29 end
... ...
app/observers/users_project_observer.rb
1 1 class UsersProjectObserver < ActiveRecord::Observer
2 2 def after_create(users_project)
3 3 Notify.project_access_granted_email(users_project.id).deliver
  4 +
  5 + Event.create(
  6 + project_id: users_project.project.id,
  7 + action: Event::Joined,
  8 + author_id: users_project.user.id
  9 + )
4 10 end
5 11  
6 12 def after_update(users_project)
7 13 Notify.project_access_granted_email(users_project.id).deliver
8 14 end
  15 +
  16 + def after_destroy(users_project)
  17 + Event.create(
  18 + project_id: users_project.project.id,
  19 + action: Event::Left,
  20 + author_id: users_project.user.id
  21 + )
  22 + end
  23 +
9 24 end
... ...
app/roles/push_event.rb
... ... @@ -90,6 +90,8 @@ module PushEvent
90 90  
91 91 def push_with_commits?
92 92 md_ref? && commits.any? && parent_commit && last_commit
  93 + rescue Grit::NoSuchPathError
  94 + false
93 95 end
94 96  
95 97 def last_push_to_non_root?
... ...
app/views/admin/projects/_form.html.haml
... ... @@ -32,7 +32,7 @@
32 32 - unless project.new_record?
33 33 .clearfix
34 34 = f.label :owner_id
35   - .input= f.select :owner_id, User.all.map { |user| [user.name, user.id] }
  35 + .input= f.select :owner_id, User.all.map { |user| [user.name, user.id] }, {}, {class: 'chosen'}
36 36  
37 37 - if project.repo_exists?
38 38 .clearfix
... ... @@ -69,7 +69,6 @@
69 69  
70 70 :javascript
71 71 $(function(){
72   - $('#project_owner_id').chosen();
73 72 new Projects();
74 73 })
75 74  
... ...
app/views/admin/projects/show.html.haml
... ... @@ -71,25 +71,11 @@
71 71 %th Project Access:
72 72  
73 73 %tr
74   - %td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true
75   - %td= select_tag :project_access, options_for_select(Project.access_options), class: "project-access-select"
  74 + %td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
  75 + %td= select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3"}
76 76  
77 77 %tr
78 78 %td= submit_tag 'Add', class: "btn primary"
79 79 %td
80 80 Read more about project permissions
81 81 %strong= link_to "here", help_permissions_path, class: "vlink"
82   -
83   -:css
84   - form select {
85   - width:150px;
86   - }
87   -
88   - #user_ids {
89   - width:300px;
90   - }
91   -
92   -:javascript
93   - $('select#user_ids').chosen();
94   - $('select#repo_access').chosen();
95   - $('select#project_access').chosen();
... ...
app/views/admin/team_members/_form.html.haml
... ... @@ -8,20 +8,9 @@
8 8 .clearfix
9 9 %label Project Access:
10 10 .input
11   - = f.select :project_access, options_for_select(Project.access_options, @admin_team_member.project_access), {}, class: "project-access-select"
  11 + = f.select :project_access, options_for_select(Project.access_options, @admin_team_member.project_access), {}, class: "project-access-select chosen span3"
12 12  
13 13 %br
14 14 .actions
15 15 = f.submit 'Save', class: "btn primary"
16 16 = link_to 'Cancel', :back, class: "btn"
17   -
18   -:css
19   - form select {
20   - width:300px;
21   - }
22   -
23   -:javascript
24   - $('select#team_member_user_id').chosen();
25   - $('select#team_member_project_id').chosen();
26   - $('select#team_member_repo_access').chosen();
27   - $('select#team_member_project_access').chosen();
... ...
app/views/admin/users/show.html.haml
... ... @@ -68,8 +68,8 @@
68 68 %th Project Access:
69 69  
70 70 %tr
71   - %td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name), multiple: true
72   - %td= select_tag :project_access, options_for_select(Project.access_options), class: "project-access-select"
  71 + %td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5'
  72 + %td= select_tag :project_access, options_for_select(Project.access_options), class: "project-access-select chosen span3"
73 73  
74 74 %tr
75 75 %td= submit_tag 'Add', class: "btn primary"
... ... @@ -97,17 +97,3 @@
97 97 %td= select_tag :tm_project_access, options_for_select(Project.access_options, tm.project_access), class: "medium project-access-select", disabled: :disabled
98 98 %td= link_to 'Edit Access', edit_admin_team_member_path(tm), class: "btn small"
99 99 %td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn small danger"
100   -
101   -:css
102   - form select {
103   - width:150px;
104   - }
105   -
106   - #project_ids {
107   - width:300px;
108   - }
109   -
110   -:javascript
111   - $('select#project_ids').chosen();
112   - $('select#repo_access').chosen();
113   - $('select#project_access').chosen();
... ...
app/views/commits/_head.html.haml
1 1 %ul.nav.nav-tabs
2 2 %li
3 3 = form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
4   - = select_tag "ref", grouped_options_refs, onchange: "$(this.form).trigger('submit');", class: "project-refs-select"
  4 + = select_tag "ref", grouped_options_refs, onchange: "$(this.form).trigger('submit');", class: "project-refs-select chosen"
5 5 = hidden_field_tag :destination, "commits"
6 6  
7 7 %li{class: "#{'active' if current_page?(project_commits_path(@project)) }"}
... ... @@ -26,8 +26,3 @@
26 26 %span.rss-icon
27 27 = link_to project_commits_path(@project, :atom, { private_token: current_user.private_token, ref: @ref }), title: "Feed" do
28 28 = image_tag "rss_ui.png", title: "feed"
29   -
30   -:javascript
31   - $(function(){
32   - $('.project-refs-select').chosen();
33   - });
... ...
app/views/devise/sessions/_new_ldap.html.haml
... ... @@ -15,7 +15,7 @@
15 15 $(function() {
16 16 $('#new_user').toggle();
17 17 });
18   - = form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => { :class => "login-box" }) do |f|
  18 += form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => { :class => "login-box" }) do |f|
19 19 = f.text_field :email, :class => "text top", :placeholder => "Email"
20 20 = f.password_field :password, :class => "text bottom", :placeholder => "Password"
21 21 - if devise_mapping.rememberable?
... ...
app/views/events/_event.html.haml
... ... @@ -11,3 +11,7 @@
11 11 .event_feed
12 12 = render "events/event_push", event: event
13 13  
  14 + - elsif event.membership_changed?
  15 + .event_feed
  16 + = render "events/event_membership_changed", event: event
  17 +
... ...
app/views/events/_event_membership_changed.html.haml 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 += image_tag gravatar_icon(event.author_email), class: "avatar"
  2 +%strong #{event.author_name}
  3 +%span.event_label{class: event.action_name}= event.action_name
  4 +project
  5 +%strong= link_to event.project.name, event.project
  6 +%span.cgray
  7 + = time_ago_in_words(event.created_at)
  8 + ago.
  9 +
... ...
app/views/issues/_form.html.haml
... ... @@ -18,12 +18,12 @@
18 18 = f.label :assignee_id do
19 19 %i.icon-user
20 20 Assign to
21   - .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select a user" })
  21 + .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select a user" }, {class: 'chosen'})
22 22 .issue_milestone
23 23 = f.label :milestone_id do
24 24 %i.icon-time
25 25 Milestone
26   - .input= f.select(:milestone_id, @project.milestones.active.all.collect {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" })
  26 + .input= f.select(:milestone_id, @project.milestones.active.all.collect {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }, {class: 'chosen'})
27 27  
28 28 .issue_description
29 29 .clearfix
... ...
app/views/issues/edit.html.haml
1 1 = render "form"
2   -
3   -:javascript
4   - $(function(){
5   - $('select#issue_assignee_id').chosen();
6   - $('select#issue_milestone_id').chosen();
7   - });
8   -
... ...
app/views/issues/new.html.haml
1 1 = render "form"
2   -
3   -:javascript
4   - $(function(){
5   - $('select#issue_assignee_id').chosen();
6   - $('select#issue_milestone_id').chosen();
7   - });
8   -
... ...
app/views/layouts/_head_panel.html.haml
... ... @@ -34,12 +34,4 @@
34 34 source: #{raw search_autocomplete_source},
35 35 select: function(event, ui) { location.href = ui.item.url }
36 36 });
37   -
38   - $(document).keypress(function(e) {
39   - if($(e.target).is(":input")) return;
40   - switch(e.which) {
41   - case 115: focusSearch();
42   - e.preventDefault();
43   - }
44   - });
45 37 });
... ...
app/views/merge_requests/_form.html.haml
... ... @@ -16,7 +16,7 @@
16 16 .padded
17 17 = f.label :source_branch, "From", class: "control-label"
18 18 .controls
19   - = f.select(:source_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, style: "width:250px")
  19 + = f.select(:source_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, {class: 'chosen span3'})
20 20 .mr_source_commit
21 21  
22 22 .span2
... ... @@ -28,7 +28,7 @@
28 28 .padded
29 29 = f.label :target_branch, "To", class: "control-label"
30 30 .controls
31   - = f.select(:target_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, style: "width:250px")
  31 + = f.select(:target_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, {class: 'chosen span3'})
32 32 .mr_target_commit
33 33  
34 34 %h4.cdark 2. Fill info
... ... @@ -43,7 +43,7 @@
43 43 = f.label :assignee_id do
44 44 %i.icon-user
45 45 Assign to
46   - .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select user" }, style: "width:250px")
  46 + .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select user" }, {class: 'chosen span3'})
47 47  
48 48 .control-group
49 49  
... ... @@ -56,18 +56,12 @@
56 56 = link_to project_merge_request_path(@project, @merge_request), class: "btn cancel-btn" do
57 57 Cancel
58 58  
59   -
60   -
61 59 :javascript
62 60 $(function(){
63   - disableButtonIfEmtpyField("#merge_request_title", ".save-btn");
64   - $('select#merge_request_assignee_id').chosen();
65   - $('select#merge_request_source_branch').chosen();
66   - $('select#merge_request_target_branch').chosen();
  61 + disableButtonIfEmptyField("#merge_request_title", ".save-btn");
67 62 var source_branch = $("#merge_request_source_branch");
68 63 var target_branch = $("#merge_request_target_branch");
69 64  
70   -
71 65 $.get("#{branch_from_project_merge_requests_path(@project)}", {ref: source_branch.val() });
72 66 $.get("#{branch_to_project_merge_requests_path(@project)}", {ref: target_branch.val() });
73 67  
... ... @@ -79,4 +73,3 @@
79 73 $.get("#{branch_to_project_merge_requests_path(@project)}", {ref: $(this).val() });
80 74 });
81 75 });
82   -
... ...
app/views/milestones/_form.html.haml
... ... @@ -41,7 +41,7 @@
41 41  
42 42 :javascript
43 43 $(function() {
44   - disableButtonIfEmtpyField("#milestone_title", ".save-btn");
  44 + disableButtonIfEmptyField("#milestone_title", ".save-btn");
45 45 $( ".datepicker" ).datepicker({
46 46 dateFormat: "yy-mm-dd",
47 47 onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }
... ...
app/views/milestones/edit.html.haml
1 1 = render "form"
2   -
3   -:javascript
4   - $(function(){
5   - $('select#issue_assignee_id').chosen();
6   - });
7   -
... ...
app/views/projects/_refs.html.haml
1 1 = form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
2   - = select_tag "ref", grouped_options_refs, onchange: "this.form.submit();", class: "project-refs-select"
  2 + = select_tag "ref", grouped_options_refs, onchange: "this.form.submit();", class: "project-refs-select chosen"
3 3 = hidden_field_tag :destination, destination
4   -
5   -:javascript
6   - $(function(){
7   - $('.project-refs-select').chosen();
8   - })
... ...
app/views/protected_branches/index.html.haml
... ... @@ -19,7 +19,7 @@
19 19 .entry.clearfix
20 20 = f.label :name, "Branch"
21 21 .span3
22   - = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , { include_blank: "-- Select branch" }, { class: "span3" })
  22 + = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: "Select branch"}, {class: "chosen span3"})
23 23 &nbsp;
24 24 = f.submit 'Protect', class: "primary btn"
25 25  
... ... @@ -46,6 +46,3 @@
46 46 %td
47 47 - if can? current_user, :admin_project, @project
48 48 = link_to 'Unprotect', [@project, branch], confirm: 'Are you sure?', method: :delete, class: "danger btn small"
49   -
50   -:javascript
51   - $('select#protected_branch_name').chosen();
... ...
app/views/refs/_head.html.haml
1 1 %ul.nav.nav-tabs
2 2 %li
3 3 = form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form", remote: true do
4   - = select_tag "ref", grouped_options_refs, onchange: "$(this.form).trigger('submit');", class: "project-refs-select"
  4 + = select_tag "ref", grouped_options_refs, onchange: "$(this.form).trigger('submit');", class: "project-refs-select chosen"
5 5 = hidden_field_tag :destination, "tree"
6 6 = hidden_field_tag :path, params[:path]
7 7 %li{class: "#{'active' if (controller.controller_name == "refs") }"}
... ...
app/views/refs/_tree.html.haml
... ... @@ -43,18 +43,11 @@
43 43 %i.icon-file
44 44 = content.name
45 45 .file_content.wiki
46   - - if content.name =~ /\.(md|markdown)$/i
47   - = preserve do
48   - = markdown(content.data)
49   - - else
50   - = simple_format(content.data)
  46 + = raw GitHub::Markup.render(content.name, content.data)
51 47  
52 48 :javascript
53 49 $(function(){
54   - $('.project-refs-select').chosen();
55   -
56 50 history.pushState({ path: this.path }, '', "#{@history_path}");
57   -
58 51 });
59 52  
60 53 // Load last commit log for each file in tree
... ...
app/views/refs/_tree_file.html.haml
... ... @@ -9,10 +9,9 @@
9 9 = link_to "history", project_commits_path(@project, path: params[:path], ref: @ref), class: "btn very_small"
10 10 = link_to "blame", blame_file_project_ref_path(@project, @ref, path: params[:path]), class: "btn very_small"
11 11 - if file.text?
12   - - if name =~ /\.(md|markdown)$/i
  12 + - if markup?(name)
13 13 .file_content.wiki
14   - = preserve do
15   - = markdown(file.data)
  14 + = raw GitHub::Markup.render(name, file.data)
16 15 - else
17 16 .file_content.code
18 17 - unless file.empty?
... ...
app/views/refs/blame.html.haml
... ... @@ -38,8 +38,3 @@
38 38 = preserve do
39 39 %pre
40 40 = Gitlab::Encode.utf8 lines.join("\n")
41   -
42   -:javascript
43   - $(function(){
44   - $('.project-refs-select').chosen();
45   - });
... ...
app/views/snippets/_form.html.haml
... ... @@ -16,7 +16,7 @@
16 16 .input= f.text_field :file_name, placeholder: "example.rb"
17 17 .clearfix
18 18 = f.label "Lifetime"
19   - .input= f.select :expires_at, lifetime_select_options, {}, style: "width:200px;"
  19 + .input= f.select :expires_at, lifetime_select_options, {}, {class: 'chosen span2'}
20 20 .clearfix
21 21 = f.label :content, "Code"
22 22 .input= f.text_area :content, class: "span8"
... ... @@ -26,11 +26,3 @@
26 26 = link_to "Cancel", project_snippets_path(@project), class: " btn"
27 27 - unless @snippet.new_record?
28 28 .right= link_to 'Destroy', [@project, @snippet], confirm: 'Are you sure?', method: :delete, class: "btn right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}"
29   -
30   -
31   -
32   -:javascript
33   - $(function(){
34   - $('select#snippet_expires_at').chosen();
35   - });
36   -
... ...
app/views/team_members/_form.html.haml
... ... @@ -10,21 +10,14 @@
10 10  
11 11 %h6 1. Choose people you want in the team
12 12 .clearfix
13   - = f.label :user_ids, "Peolpe"
14   - .input= select_tag(:user_ids, options_from_collection_for_select(User.not_in_project(@project).all, :id, :name), { class: "xxlarge", multiple: true })
15   -
  13 + = f.label :user_ids, "People"
  14 + .input= select_tag(:user_ids, options_from_collection_for_select(User.not_in_project(@project).all, :id, :name), {data: {placeholder: "Select users"}, class: "chosen xxlarge", multiple: true})
16 15  
17 16 %h6 2. Set access level for them
18 17 .clearfix
19 18 = f.label :project_access, "Project Access"
20   - .input= select_tag :project_access, options_for_select(Project.access_options, @team_member.project_access), class: "project-access-select"
21   -
  19 + .input= select_tag :project_access, options_for_select(Project.access_options, @team_member.project_access), class: "project-access-select chosen"
22 20  
23 21 .actions
24 22 = f.submit 'Save', class: "btn save-btn"
25 23 = link_to "Cancel", team_project_path(@project), class: "btn cancel-btn"
26   -
27   -
28   -:javascript
29   - $('select#user_ids').chosen();
30   - $('select#project_access').chosen();
... ...
app/views/team_members/_show.html.haml
1 1 - user = member.user
2 2 - allow_admin = can? current_user, :admin_project, @project
3 3 %tr{id: dom_id(member), class: "team_member_row user_#{user.id}"}
4   - %td
  4 + %td.span6
5 5 = link_to project_team_member_path(@project, member), title: user.name, class: "dark" do
6 6 = image_tag gravatar_icon(user.email, 40), class: "avatar s32"
7 7 = link_to project_team_member_path(@project, member), title: user.name, class: "dark" do
8 8 %strong= truncate(user.name, lenght: 40)
9   - %br
10   - %div.cgray= user.email
  9 + %br
  10 + %small.cgray= user.email
11 11  
12   - %td
  12 + %td.span5
13 13 .right
  14 + - if current_user == user
  15 + %span.btn.disabled This is you!
14 16 - if @project.owner == user
15   - %span.btn.disabled.success Project Owner
16   - - if user.blocked
  17 + %span.btn.disabled.success Owner
  18 + - elsif user.blocked
17 19 %span.btn.disabled.blocked Blocked
  20 + - elsif allow_admin
  21 + = link_to project_team_member_path(project_id: @project, id: member.id), confirm: remove_from_team_message(@project, member), method: :delete, class: "very_small btn danger" do
  22 + %i.icon-minus.icon-white
  23 +
18 24 - if allow_admin
19 25 = form_for(member, as: :team_member, url: project_team_member_path(@project, member)) do |f|
20   - = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select"
  26 + = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2"
... ...
config/gitlab.yml.example
... ... @@ -33,11 +33,12 @@ app:
33 33 git_host:
34 34 admin_uri: git@localhost:gitolite-admin
35 35 base_path: /home/git/repositories/
36   - # hooks_path: /var/lib/gitolite/.gitolite/hooks/ # only needed when gitolite is not installed according the manual
37   - # host: localhost
  36 + hooks_path: /home/git/.gitolite/hooks/
  37 + gitolite_admin_key: gitlab
38 38 git_user: git
39 39 upload_pack: true
40 40 receive_pack: true
  41 + # host: localhost
41 42 # port: 22
42 43  
43 44 # Git settings
... ...
config/initializers/1_settings.rb
... ... @@ -102,6 +102,10 @@ class Settings &lt; Settingslogic
102 102 git_host['admin_uri'] || 'git@localhost:gitolite-admin'
103 103 end
104 104  
  105 + def gitolite_admin_key
  106 + git_host['gitolite_admin_key'] || 'gitlab'
  107 + end
  108 +
105 109 def default_projects_limit
106 110 app['default_projects_limit'] || 10
107 111 end
... ...
doc/installation.md
... ... @@ -113,17 +113,20 @@ Generate key:
113 113 Clone GitLab's fork of the Gitolite source code:
114 114  
115 115 cd /home/git
116   - sudo -H -u git git clone https://github.com/gitlabhq/gitolite.git /home/git/gitolite
  116 + sudo -H -u git git clone -b gl-v304 https://github.com/gitlabhq/gitolite.git /home/git/gitolite
117 117  
118 118 Setup:
119 119  
  120 + cd /home/git
  121 + sudo -u git -H mkdir bin
120 122 sudo -u git sh -c 'echo -e "PATH=\$PATH:/home/git/bin\nexport PATH" >> /home/git/.profile'
121   - sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; /home/git/gitolite/src/gl-system-install"
  123 + sudo -u git sh -c 'gitolite/install -ln /home/git/bin'
  124 +
122 125 sudo cp /home/gitlab/.ssh/id_rsa.pub /home/git/gitlab.pub
123 126 sudo chmod 0444 /home/git/gitlab.pub
124 127  
125   - sudo -u git -H sed -i 's/0077/0007/g' /home/git/share/gitolite/conf/example.gitolite.rc
126   - sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; gl-setup -q /home/git/gitlab.pub"
  128 + sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; gitolite setup -pk /home/git/gitlab.pub"
  129 + sudo -u git -H sed -i 's/0077/0007/g' /home/git/.gitolite.rc
127 130  
128 131 Permissions:
129 132  
... ... @@ -189,8 +192,8 @@ and ensure you have followed all of the above steps carefully.
189 192  
190 193 #### Setup GitLab hooks
191 194  
192   - sudo cp ./lib/hooks/post-receive /home/git/share/gitolite/hooks/common/post-receive
193   - sudo chown git:git /home/git/share/gitolite/hooks/common/post-receive
  195 + sudo cp ./lib/hooks/post-receive /home/git/.gitolite/hooks/common/post-receive
  196 + sudo chown git:git /home/git/.gitolite/hooks/common/post-receive
194 197  
195 198 #### Check application status
196 199  
... ...
features/dashboard/dashboard.feature
... ... @@ -15,4 +15,14 @@ Feature: Dashboard
15 15 And I click "Create Merge Request" link
16 16 Then I see prefilled new Merge Request page
17 17  
  18 + Scenario: I should see User joined Project event
  19 + Given user with name "John Doe" joined project "Shop"
  20 + When I visit dashboard page
  21 + Then I should see "John Doe joined project Shop" event
18 22  
  23 + Scenario: I should see User left Project event
  24 + Given user with name "John Doe" joined project "Shop"
  25 + And user with name "John Doe" left project "Shop"
  26 + When I visit dashboard page
  27 + Then I should see "John Doe left project Shop" event
  28 +
... ...
features/step_definitions/dashboard_steps.rb
... ... @@ -109,3 +109,28 @@ Given /^I have authored merge requests$/ do
109 109 :author => @user,
110 110 :project => project2
111 111 end
  112 +
  113 +Given /^user with name "(.*?)" joined project "(.*?)"$/ do |user_name, project_name|
  114 + user = Factory.create(:user, {name: user_name})
  115 + project = Project.find_by_name project_name
  116 + Event.create(
  117 + project: project,
  118 + author_id: user.id,
  119 + action: Event::Joined
  120 + )
  121 +end
  122 +
  123 +Given /^user with name "(.*?)" left project "(.*?)"$/ do |user_name, project_name|
  124 + user = User.find_by_name user_name
  125 + project = Project.find_by_name project_name
  126 + Event.create(
  127 + project: project,
  128 + author_id: user.id,
  129 + action: Event::Left
  130 + )
  131 +end
  132 +
  133 +Then /^I should see "(.*?)" event$/ do |event_text|
  134 + page.should have_content(event_text)
  135 +end
  136 +
... ...
lib/api/helpers.rb
... ... @@ -8,7 +8,7 @@ module Gitlab
8 8 if @project ||= current_user.projects.find_by_id(params[:id]) ||
9 9 current_user.projects.find_by_code(params[:id])
10 10 else
11   - error!({'message' => '404 Not found'}, 404)
  11 + not_found!
12 12 end
13 13  
14 14 @project
... ... @@ -19,7 +19,48 @@ module Gitlab
19 19 end
20 20  
21 21 def authenticate!
22   - error!({'message' => '401 Unauthorized'}, 401) unless current_user
  22 + unauthorized! unless current_user
  23 + end
  24 +
  25 + def authorize! action, subject
  26 + unless abilities.allowed?(current_user, action, subject)
  27 + forbidden!
  28 + end
  29 + end
  30 +
  31 + # error helpers
  32 +
  33 + def forbidden!
  34 + render_api_error!('403 Forbidden', 403)
  35 + end
  36 +
  37 + def not_found!(resource = nil)
  38 + message = ["404"]
  39 + message << resource if resource
  40 + message << "Not Found"
  41 + render_api_error!(message.join(' '), 404)
  42 + end
  43 +
  44 + def unauthorized!
  45 + render_api_error!('401 Unauthorized', 401)
  46 + end
  47 +
  48 + def not_allowed!
  49 + render_api_error!('Method Not Allowed', 405)
  50 + end
  51 +
  52 + def render_api_error!(message, status)
  53 + error!({'message' => message}, status)
  54 + end
  55 +
  56 + private
  57 +
  58 + def abilities
  59 + @abilities ||= begin
  60 + abilities = Six.new
  61 + abilities << Ability
  62 + abilities
  63 + end
23 64 end
24 65 end
25 66 end
... ...
lib/api/issues.rb
... ... @@ -60,7 +60,7 @@ module Gitlab
60 60 if @issue.save
61 61 present @issue, with: Entities::Issue
62 62 else
63   - error!({'message' => '404 Not found'}, 404)
  63 + not_found!
64 64 end
65 65 end
66 66  
... ... @@ -79,6 +79,8 @@ module Gitlab
79 79 # PUT /projects/:id/issues/:issue_id
80 80 put ":id/issues/:issue_id" do
81 81 @issue = user_project.issues.find(params[:issue_id])
  82 + authorize! :modify_issue, @issue
  83 +
82 84 parameters = {
83 85 title: (params[:title] || @issue.title),
84 86 description: (params[:description] || @issue.description),
... ... @@ -91,7 +93,7 @@ module Gitlab
91 93 if @issue.update_attributes(parameters)
92 94 present @issue, with: Entities::Issue
93 95 else
94   - error!({'message' => '404 Not found'}, 404)
  96 + not_found!
95 97 end
96 98 end
97 99  
... ... @@ -103,7 +105,7 @@ module Gitlab
103 105 # Example Request:
104 106 # DELETE /projects/:id/issues/:issue_id
105 107 delete ":id/issues/:issue_id" do
106   - error!({'message' => 'method not allowed'}, 405)
  108 + not_allowed!
107 109 end
108 110 end
109 111 end
... ...
lib/api/milestones.rb
... ... @@ -45,7 +45,7 @@ module Gitlab
45 45 if @milestone.save
46 46 present @milestone, with: Entities::Milestone
47 47 else
48   - error!({'message' => '404 Not found'}, 404)
  48 + not_found!
49 49 end
50 50 end
51 51  
... ... @@ -61,6 +61,8 @@ module Gitlab
61 61 # Example Request:
62 62 # PUT /projects/:id/milestones/:milestone_id
63 63 put ":id/milestones/:milestone_id" do
  64 + authorize! :admin_milestone, user_project
  65 +
64 66 @milestone = user_project.milestones.find(params[:milestone_id])
65 67 parameters = {
66 68 title: (params[:title] || @milestone.title),
... ... @@ -72,7 +74,7 @@ module Gitlab
72 74 if @milestone.update_attributes(parameters)
73 75 present @milestone, with: Entities::Milestone
74 76 else
75   - error!({'message' => '404 Not found'}, 404)
  77 + not_found!
76 78 end
77 79 end
78 80 end
... ...
lib/api/projects.rb
... ... @@ -50,7 +50,7 @@ module Gitlab
50 50 if @project.saved?
51 51 present @project, with: Entities::Project
52 52 else
53   - error!({'message' => '404 Not found'}, 404)
  53 + not_found!
54 54 end
55 55 end
56 56  
... ... @@ -74,6 +74,7 @@ module Gitlab
74 74 # Example Request:
75 75 # POST /projects/:id/users
76 76 post ":id/users" do
  77 + authorize! :admin_project, user_project
77 78 user_project.add_users_ids_to_team(params[:user_ids].values, params[:project_access])
78 79 nil
79 80 end
... ... @@ -87,6 +88,7 @@ module Gitlab
87 88 # Example Request:
88 89 # PUT /projects/:id/add_users
89 90 put ":id/users" do
  91 + authorize! :admin_project, user_project
90 92 user_project.update_users_ids_to_role(params[:user_ids].values, params[:project_access])
91 93 nil
92 94 end
... ... @@ -99,6 +101,7 @@ module Gitlab
99 101 # Example Request:
100 102 # DELETE /projects/:id/users
101 103 delete ":id/users" do
  104 + authorize! :admin_project, user_project
102 105 user_project.delete_users_ids_from_team(params[:user_ids].values)
103 106 nil
104 107 end
... ... @@ -209,7 +212,7 @@ module Gitlab
209 212 if @snippet.save
210 213 present @snippet, with: Entities::ProjectSnippet
211 214 else
212   - error!({'message' => '404 Not found'}, 404)
  215 + not_found!
213 216 end
214 217 end
215 218  
... ... @@ -226,6 +229,8 @@ module Gitlab
226 229 # PUT /projects/:id/snippets/:snippet_id
227 230 put ":id/snippets/:snippet_id" do
228 231 @snippet = user_project.snippets.find(params[:snippet_id])
  232 + authorize! :modify_snippet, @snippet
  233 +
229 234 parameters = {
230 235 title: (params[:title] || @snippet.title),
231 236 file_name: (params[:file_name] || @snippet.file_name),
... ... @@ -236,7 +241,7 @@ module Gitlab
236 241 if @snippet.update_attributes(parameters)
237 242 present @snippet, with: Entities::ProjectSnippet
238 243 else
239   - error!({'message' => '404 Not found'}, 404)
  244 + not_found!
240 245 end
241 246 end
242 247  
... ... @@ -249,6 +254,8 @@ module Gitlab
249 254 # DELETE /projects/:id/snippets/:snippet_id
250 255 delete ":id/snippets/:snippet_id" do
251 256 @snippet = user_project.snippets.find(params[:snippet_id])
  257 + authorize! :modify_snippet, @snippet
  258 +
252 259 @snippet.destroy
253 260 end
254 261  
... ... @@ -277,10 +284,10 @@ module Gitlab
277 284 ref = params[:sha]
278 285  
279 286 commit = user_project.commit ref
280   - error!('404 Commit Not Found', 404) unless commit
  287 + not_found! "Commit" unless commit
281 288  
282 289 tree = Tree.new commit.tree, user_project, ref, params[:filepath]
283   - error!('404 File Not Found', 404) unless tree.try(:tree)
  290 + not_found! "File" unless tree.try(:tree)
284 291  
285 292 if tree.text?
286 293 encoding = Gitlab::Encode.detect_encoding(tree.data)
... ...
lib/gitlab/backend/gitolite.rb
... ... @@ -35,7 +35,7 @@ module Gitlab
35 35 end
36 36  
37 37 def enable_automerge
38   - config.admin_all_repo!(project)
  38 + config.admin_all_repo!
39 39 end
40 40  
41 41 alias_method :create_repository, :update_repository
... ...
lib/gitlab/backend/gitolite_config.rb
... ... @@ -148,18 +148,7 @@ module Gitlab
148 148 # Enable access to all repos for gitolite admin.
149 149 # We use it for accept merge request feature
150 150 def admin_all_repo
151   - owner_name = ""
152   -
153   - # Read gitolite-admin user
154   - #
155   - begin
156   - repo = conf.get_repo("gitolite-admin")
157   - owner_name = repo.permissions[0]["RW+"][""][0]
158   - raise StandardError if owner_name.blank?
159   - rescue => ex
160   - puts "Can't determine gitolite-admin owner".red
161   - raise StandardError
162   - end
  151 + owner_name = Gitlab.config.gitolite_admin_key
163 152  
164 153 # @ALL repos premission for gitolite owner
165 154 repo_name = "@all"
... ...
lib/gitlab/merge.rb
... ... @@ -21,8 +21,7 @@ module Gitlab
21 21 if output =~ /CONFLICT/
22 22 false
23 23 else
24   - repo.git.push({}, "origin", merge_request.target_branch)
25   - true
  24 + !!repo.git.push({}, "origin", merge_request.target_branch)
26 25 end
27 26 end
28 27 end
... ...
spec/helpers/tree_helper_spec.rb 0 → 100644
... ... @@ -0,0 +1,15 @@
  1 +require 'spec_helper'
  2 +
  3 +describe TreeHelper do
  4 + describe '#markup?' do
  5 + %w(mdown md markdown textile rdoc org creole mediawiki rst asciidoc pod).each do |type|
  6 + it "returns true for #{type} files" do
  7 + markup?("README.#{type}").should be_true
  8 + end
  9 + end
  10 +
  11 + it "returns false when given a non-markup filename" do
  12 + markup?('README.rb').should_not be_true
  13 + end
  14 + end
  15 +end
... ...
spec/models/event_spec.rb
... ... @@ -49,4 +49,26 @@ describe Event do
49 49 it { @event.branch_name.should == "master" }
50 50 it { @event.author.should == @user }
51 51 end
  52 +
  53 + describe "Joined project team" do
  54 + let(:project) {Factory.create :project}
  55 + let(:new_user) {Factory.create :user}
  56 + it "should create event" do
  57 + UsersProject.observers.enable :users_project_observer
  58 + expect{
  59 + UsersProject.bulk_import(project, [new_user.id], UsersProject::DEVELOPER)
  60 + }.to change{Event.count}.by(1)
  61 + end
  62 + end
  63 + describe "Left project team" do
  64 + let(:project) {Factory.create :project}
  65 + let(:new_user) {Factory.create :user}
  66 + it "should create event" do
  67 + UsersProject.bulk_import(project, [new_user.id], UsersProject::DEVELOPER)
  68 + UsersProject.observers.enable :users_project_observer
  69 + expect{
  70 + UsersProject.bulk_delete(project, [new_user.id])
  71 + }.to change{Event.count}.by(1)
  72 + end
  73 + end
52 74 end
... ...
spec/observers/users_project_observer_spec.rb
... ... @@ -23,6 +23,14 @@ describe UsersProjectObserver do
23 23 Notify.should_receive(:project_access_granted_email).with(users_project.id).and_return(double(deliver: true))
24 24 subject.after_create(users_project)
25 25 end
  26 + it "should create new event" do
  27 + Event.should_receive(:create).with(
  28 + project_id: users_project.project.id,
  29 + action: Event::Joined,
  30 + author_id: users_project.user.id
  31 + )
  32 + subject.after_create(users_project)
  33 + end
26 34 end
27 35  
28 36 describe "#after_update" do
... ... @@ -37,4 +45,23 @@ describe UsersProjectObserver do
37 45 subject.after_update(users_project)
38 46 end
39 47 end
  48 + describe "#after_destroy" do
  49 + it "should called when UsersProject destroyed" do
  50 + subject.should_receive(:after_destroy)
  51 + UsersProject.observers.enable :users_project_observer do
  52 + UsersProject.bulk_delete(
  53 + users_project.project,
  54 + [users_project.user.id]
  55 + )
  56 + end
  57 + end
  58 + it "should create new event" do
  59 + Event.should_receive(:create).with(
  60 + project_id: users_project.project.id,
  61 + action: Event::Left,
  62 + author_id: users_project.user.id
  63 + )
  64 + subject.after_destroy(users_project)
  65 + end
  66 + end
40 67 end
... ...
spec/requests/api/projects_spec.rb
... ... @@ -86,7 +86,7 @@ describe Gitlab::API do
86 86 it "should return a 404 error if not found" do
87 87 get api("/projects/42", user)
88 88 response.status.should == 404
89   - json_response['message'].should == '404 Not found'
  89 + json_response['message'].should == '404 Not Found'
90 90 end
91 91 end
92 92  
... ...