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
1 source "http://rubygems.org" 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 gem "rails", "3.2.8" 11 gem "rails", "3.2.8"
4 12
5 # Supported DBs 13 # Supported DBs
@@ -44,7 +52,8 @@ gem "ffaker" @@ -44,7 +52,8 @@ gem "ffaker"
44 gem "seed-fu" 52 gem "seed-fu"
45 53
46 # Markdown to HTML 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 # Servers 58 # Servers
50 gem "thin" 59 gem "thin"
@@ -101,13 +110,20 @@ group :development, :test do @@ -101,13 +110,20 @@ group :development, :test do
101 gem "capybara" 110 gem "capybara"
102 gem "capybara-webkit" 111 gem "capybara-webkit"
103 gem "headless" 112 gem "headless"
104 - gem "autotest"  
105 - gem "autotest-rails"  
106 gem "pry" 113 gem "pry"
107 gem "awesome_print" 114 gem "awesome_print"
108 gem "database_cleaner" 115 gem "database_cleaner"
109 gem "launchy" 116 gem "launchy"
110 gem 'factory_girl_rails' 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 end 127 end
112 128
113 group :test do 129 group :test do
@@ -68,7 +68,6 @@ GIT @@ -68,7 +68,6 @@ GIT
68 GEM 68 GEM
69 remote: http://rubygems.org/ 69 remote: http://rubygems.org/
70 specs: 70 specs:
71 - ZenTest (4.8.1)  
72 actionmailer (3.2.8) 71 actionmailer (3.2.8)
73 actionpack (= 3.2.8) 72 actionpack (= 3.2.8)
74 mail (~> 2.4.4) 73 mail (~> 2.4.4)
@@ -100,10 +99,6 @@ GEM @@ -100,10 +99,6 @@ GEM
100 rails (~> 3.0) 99 rails (~> 3.0)
101 addressable (2.2.8) 100 addressable (2.2.8)
102 arel (3.0.2) 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 awesome_print (1.0.2) 102 awesome_print (1.0.2)
108 bcrypt-ruby (3.0.1) 103 bcrypt-ruby (3.0.1)
109 blankslate (2.1.2.4) 104 blankslate (2.1.2.4)
@@ -178,6 +173,7 @@ GEM @@ -178,6 +173,7 @@ GEM
178 gherkin (2.11.0) 173 gherkin (2.11.0)
179 json (>= 1.4.6) 174 json (>= 1.4.6)
180 git (1.2.5) 175 git (1.2.5)
  176 + github-markup (0.7.4)
181 gitlab_meta (2.9) 177 gitlab_meta (2.9)
182 grape (0.2.1) 178 grape (0.2.1)
183 hashie (~> 1.2) 179 hashie (~> 1.2)
@@ -185,6 +181,15 @@ GEM @@ -185,6 +181,15 @@ GEM
185 multi_xml 181 multi_xml
186 rack 182 rack
187 rack-mount 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 haml (3.1.6) 193 haml (3.1.6)
189 haml-rails (0.3.4) 194 haml-rails (0.3.4)
190 actionpack (~> 3.0) 195 actionpack (~> 3.0)
@@ -218,6 +223,7 @@ GEM @@ -218,6 +223,7 @@ GEM
218 libv8 (3.3.10.4) 223 libv8 (3.3.10.4)
219 libwebsocket (0.1.3) 224 libwebsocket (0.1.3)
220 addressable 225 addressable
  226 + listen (0.5.0)
221 mail (2.4.4) 227 mail (2.4.4)
222 i18n (>= 0.4.0) 228 i18n (>= 0.4.0)
223 mime-types (~> 1.16) 229 mime-types (~> 1.16)
@@ -273,6 +279,9 @@ GEM @@ -273,6 +279,9 @@ GEM
273 raindrops (0.9.0) 279 raindrops (0.9.0)
274 rake (0.9.2.2) 280 rake (0.9.2.2)
275 raphael-rails (1.5.2) 281 raphael-rails (1.5.2)
  282 + rb-fsevent (0.9.1)
  283 + rb-inotify (0.8.8)
  284 + ffi (>= 0.5.0)
276 rdoc (3.12) 285 rdoc (3.12)
277 json (~> 1.4) 286 json (~> 1.4)
278 redcarpet (2.1.1) 287 redcarpet (2.1.1)
@@ -376,8 +385,6 @@ PLATFORMS @@ -376,8 +385,6 @@ PLATFORMS
376 DEPENDENCIES 385 DEPENDENCIES
377 acts-as-taggable-on (= 2.3.1) 386 acts-as-taggable-on (= 2.3.1)
378 annotate! 387 annotate!
379 - autotest  
380 - autotest-rails  
381 awesome_print 388 awesome_print
382 bootstrap-sass (= 2.0.4) 389 bootstrap-sass (= 2.0.4)
383 capybara 390 capybara
@@ -396,11 +403,15 @@ DEPENDENCIES @@ -396,11 +403,15 @@ DEPENDENCIES
396 ffaker 403 ffaker
397 foreman 404 foreman
398 git 405 git
  406 + github-markup (~> 0.7.4)
399 gitlab_meta (= 2.9) 407 gitlab_meta (= 2.9)
400 gitolite! 408 gitolite!
401 grack! 409 grack!
402 grape (~> 0.2.1) 410 grape (~> 0.2.1)
403 grit! 411 grit!
  412 + growl
  413 + guard-cucumber
  414 + guard-rspec
404 haml-rails 415 haml-rails
405 headless 416 headless
406 httparty 417 httparty
@@ -418,6 +429,8 @@ DEPENDENCIES @@ -418,6 +429,8 @@ DEPENDENCIES
418 rack-mini-profiler 429 rack-mini-profiler
419 rails (= 3.2.8) 430 rails (= 3.2.8)
420 raphael-rails (= 1.5.2) 431 raphael-rails (= 1.5.2)
  432 + rb-fsevent
  433 + rb-inotify
421 redcarpet (~> 2.1.1) 434 redcarpet (~> 2.1.1)
422 resque (~> 1.20.0) 435 resque (~> 1.20.0)
423 resque_mailer 436 resque_mailer
Guardfile 0 → 100644
@@ -0,0 +1,30 @@ @@ -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,7 +5,7 @@ function switchToNewIssue(form){
5 $('select#issue_milestone_id').chosen(); 5 $('select#issue_milestone_id').chosen();
6 $("#new_issue_dialog").show("fade", { direction: "right" }, 150); 6 $("#new_issue_dialog").show("fade", { direction: "right" }, 150);
7 $('.top-tabs .add_new').hide(); 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,7 +16,7 @@ function switchToEditIssue(form){
16 $('select#issue_milestone_id').chosen(); 16 $('select#issue_milestone_id').chosen();
17 $("#edit_issue_dialog").show("fade", { direction: "right" }, 150); 17 $("#edit_issue_dialog").show("fade", { direction: "right" }, 150);
18 $('.add_new').hide(); 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,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 @@ @@ -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,14 +25,14 @@ var NoteList = {
25 $(this).closest('li').fadeOut(); }); 25 $(this).closest('li').fadeOut(); });
26 26
27 $(".note-form-holder").live("ajax:before", function(){ 27 $(".note-form-holder").live("ajax:before", function(){
28 - $(".submit_note").attr("disabled", "disabled"); 28 + $(".submit_note").disable()
29 }) 29 })
30 30
31 $(".note-form-holder").live("ajax:complete", function(){ 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 $(".note-text").live("focus", function(){ 37 $(".note-text").live("focus", function(){
38 $(this).css("height", "80px"); 38 $(this).css("height", "80px");
@@ -177,6 +177,6 @@ var PerLineNotes = { @@ -177,6 +177,6 @@ var PerLineNotes = {
177 form.show(); 177 form.show();
178 return false; 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,7 +8,7 @@ window.Projects = ->
8 $('.save-project-loader').show() 8 $('.save-project-loader').show()
9 9
10 $('form #project_default_branch').chosen() 10 $('form #project_default_branch').chosen()
11 - disableButtonIfEmtpyField '#project_name', '.project-submit' 11 + disableButtonIfEmptyField '#project_name', '.project-submit'
12 12
13 # Git clone panel switcher 13 # Git clone panel switcher
14 $ -> 14 $ ->
app/assets/stylesheets/common.scss
@@ -179,6 +179,15 @@ span.update-author { @@ -179,6 +179,15 @@ span.update-author {
179 &.merged { 179 &.merged {
180 background-color: #2A2; 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 form { 193 form {
app/controllers/refs_controller.rb
  1 +require 'github/markup'
  2 +
1 class RefsController < ApplicationController 3 class RefsController < ApplicationController
2 include Gitlab::Encode 4 include Gitlab::Encode
3 before_filter :project 5 before_filter :project
app/decorators/event_decorator.rb
@@ -8,7 +8,9 @@ class EventDecorator &lt; ApplicationDecorator @@ -8,7 +8,9 @@ class EventDecorator &lt; ApplicationDecorator
8 "#{self.author_name} #{self.action_name} MR ##{self.target_id}:" + self.merge_request_title 8 "#{self.author_name} #{self.action_name} MR ##{self.target_id}:" + self.merge_request_title
9 elsif self.push? 9 elsif self.push?
10 "#{self.author_name} #{self.push_action_name} #{self.ref_type} " + self.ref_name 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 end 15 end
14 end 16 end
app/helpers/projects_helper.rb
@@ -2,5 +2,9 @@ module ProjectsHelper @@ -2,5 +2,9 @@ module ProjectsHelper
2 def grouper_project_members(project) 2 def grouper_project_members(project)
3 @project.users_projects.sort_by(&:project_access).reverse.group_by(&:project_access) 3 @project.users_projects.sort_by(&:project_access).reverse.group_by(&:project_access)
4 end 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 end 9 end
6 10
app/helpers/tree_helper.rb
@@ -24,4 +24,14 @@ module TreeHelper @@ -24,4 +24,14 @@ module TreeHelper
24 content.name 24 content.name
25 end 25 end
26 end 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 end 37 end
app/models/event.rb
@@ -10,6 +10,8 @@ class Event &lt; ActiveRecord::Base @@ -10,6 +10,8 @@ class Event &lt; ActiveRecord::Base
10 Pushed = 5 10 Pushed = 5
11 Commented = 6 11 Commented = 6
12 Merged = 7 12 Merged = 7
  13 + Joined = 8 # User joined project
  14 + Left = 9 # User left project
13 15
14 belongs_to :project 16 belongs_to :project
15 belongs_to :target, polymorphic: true 17 belongs_to :target, polymorphic: true
@@ -37,7 +39,7 @@ class Event &lt; ActiveRecord::Base @@ -37,7 +39,7 @@ class Event &lt; ActiveRecord::Base
37 # - new issue 39 # - new issue
38 # - merge request 40 # - merge request
39 def allowed? 41 def allowed?
40 - push? || issue? || merge_request? 42 + push? || issue? || merge_request? || membership_changed?
41 end 43 end
42 44
43 def push? 45 def push?
@@ -84,6 +86,18 @@ class Event &lt; ActiveRecord::Base @@ -84,6 +86,18 @@ class Event &lt; ActiveRecord::Base
84 [Closed, Reopened].include?(action) 86 [Closed, Reopened].include?(action)
85 end 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 def issue 101 def issue
88 target if target_type == "Issue" 102 target if target_type == "Issue"
89 end 103 end
@@ -101,6 +115,10 @@ class Event &lt; ActiveRecord::Base @@ -101,6 +115,10 @@ class Event &lt; ActiveRecord::Base
101 "closed" 115 "closed"
102 elsif merged? 116 elsif merged?
103 "merged" 117 "merged"
  118 + elsif joined?
  119 + 'joined'
  120 + elsif left?
  121 + 'left'
104 else 122 else
105 "opened" 123 "opened"
106 end 124 end
app/models/merge_request.rb
@@ -162,7 +162,7 @@ class MergeRequest &lt; ActiveRecord::Base @@ -162,7 +162,7 @@ class MergeRequest &lt; ActiveRecord::Base
162 end 162 end
163 163
164 def automerge!(current_user) 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 self.merge!(current_user.id) 166 self.merge!(current_user.id)
167 true 167 true
168 end 168 end
app/models/users_project.rb
@@ -23,7 +23,7 @@ class UsersProject &lt; ActiveRecord::Base @@ -23,7 +23,7 @@ class UsersProject &lt; ActiveRecord::Base
23 def self.bulk_delete(project, user_ids) 23 def self.bulk_delete(project, user_ids)
24 UsersProject.transaction do 24 UsersProject.transaction do
25 UsersProject.where(:user_id => user_ids, :project_id => project.id).each do |users_project| 25 UsersProject.where(:user_id => user_ids, :project_id => project.id).each do |users_project|
26 - users_project.delete 26 + users_project.destroy
27 end 27 end
28 end 28 end
29 end 29 end
app/observers/users_project_observer.rb
1 class UsersProjectObserver < ActiveRecord::Observer 1 class UsersProjectObserver < ActiveRecord::Observer
2 def after_create(users_project) 2 def after_create(users_project)
3 Notify.project_access_granted_email(users_project.id).deliver 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 end 10 end
5 11
6 def after_update(users_project) 12 def after_update(users_project)
7 Notify.project_access_granted_email(users_project.id).deliver 13 Notify.project_access_granted_email(users_project.id).deliver
8 end 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 end 24 end
app/roles/push_event.rb
@@ -90,6 +90,8 @@ module PushEvent @@ -90,6 +90,8 @@ module PushEvent
90 90
91 def push_with_commits? 91 def push_with_commits?
92 md_ref? && commits.any? && parent_commit && last_commit 92 md_ref? && commits.any? && parent_commit && last_commit
  93 + rescue Grit::NoSuchPathError
  94 + false
93 end 95 end
94 96
95 def last_push_to_non_root? 97 def last_push_to_non_root?
app/views/admin/projects/_form.html.haml
@@ -32,7 +32,7 @@ @@ -32,7 +32,7 @@
32 - unless project.new_record? 32 - unless project.new_record?
33 .clearfix 33 .clearfix
34 = f.label :owner_id 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 - if project.repo_exists? 37 - if project.repo_exists?
38 .clearfix 38 .clearfix
@@ -69,7 +69,6 @@ @@ -69,7 +69,6 @@
69 69
70 :javascript 70 :javascript
71 $(function(){ 71 $(function(){
72 - $('#project_owner_id').chosen();  
73 new Projects(); 72 new Projects();
74 }) 73 })
75 74
app/views/admin/projects/show.html.haml
@@ -71,25 +71,11 @@ @@ -71,25 +71,11 @@
71 %th Project Access: 71 %th Project Access:
72 72
73 %tr 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 %tr 77 %tr
78 %td= submit_tag 'Add', class: "btn primary" 78 %td= submit_tag 'Add', class: "btn primary"
79 %td 79 %td
80 Read more about project permissions 80 Read more about project permissions
81 %strong= link_to "here", help_permissions_path, class: "vlink" 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,20 +8,9 @@
8 .clearfix 8 .clearfix
9 %label Project Access: 9 %label Project Access:
10 .input 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 %br 13 %br
14 .actions 14 .actions
15 = f.submit 'Save', class: "btn primary" 15 = f.submit 'Save', class: "btn primary"
16 = link_to 'Cancel', :back, class: "btn" 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,8 +68,8 @@
68 %th Project Access: 68 %th Project Access:
69 69
70 %tr 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 %tr 74 %tr
75 %td= submit_tag 'Add', class: "btn primary" 75 %td= submit_tag 'Add', class: "btn primary"
@@ -97,17 +97,3 @@ @@ -97,17 +97,3 @@
97 %td= select_tag :tm_project_access, options_for_select(Project.access_options, tm.project_access), class: "medium project-access-select", disabled: :disabled 97 %td= select_tag :tm_project_access, options_for_select(Project.access_options, tm.project_access), class: "medium project-access-select", disabled: :disabled
98 %td= link_to 'Edit Access', edit_admin_team_member_path(tm), class: "btn small" 98 %td= link_to 'Edit Access', edit_admin_team_member_path(tm), class: "btn small"
99 %td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn small danger" 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 %ul.nav.nav-tabs 1 %ul.nav.nav-tabs
2 %li 2 %li
3 = form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do 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 = hidden_field_tag :destination, "commits" 5 = hidden_field_tag :destination, "commits"
6 6
7 %li{class: "#{'active' if current_page?(project_commits_path(@project)) }"} 7 %li{class: "#{'active' if current_page?(project_commits_path(@project)) }"}
@@ -26,8 +26,3 @@ @@ -26,8 +26,3 @@
26 %span.rss-icon 26 %span.rss-icon
27 = link_to project_commits_path(@project, :atom, { private_token: current_user.private_token, ref: @ref }), title: "Feed" do 27 = link_to project_commits_path(@project, :atom, { private_token: current_user.private_token, ref: @ref }), title: "Feed" do
28 = image_tag "rss_ui.png", title: "feed" 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,7 +15,7 @@
15 $(function() { 15 $(function() {
16 $('#new_user').toggle(); 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 = f.text_field :email, :class => "text top", :placeholder => "Email" 19 = f.text_field :email, :class => "text top", :placeholder => "Email"
20 = f.password_field :password, :class => "text bottom", :placeholder => "Password" 20 = f.password_field :password, :class => "text bottom", :placeholder => "Password"
21 - if devise_mapping.rememberable? 21 - if devise_mapping.rememberable?
app/views/events/_event.html.haml
@@ -11,3 +11,7 @@ @@ -11,3 +11,7 @@
11 .event_feed 11 .event_feed
12 = render "events/event_push", event: event 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 @@ @@ -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,12 +18,12 @@
18 = f.label :assignee_id do 18 = f.label :assignee_id do
19 %i.icon-user 19 %i.icon-user
20 Assign to 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 .issue_milestone 22 .issue_milestone
23 = f.label :milestone_id do 23 = f.label :milestone_id do
24 %i.icon-time 24 %i.icon-time
25 Milestone 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 .issue_description 28 .issue_description
29 .clearfix 29 .clearfix
app/views/issues/edit.html.haml
1 = render "form" 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 = render "form" 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,12 +34,4 @@
34 source: #{raw search_autocomplete_source}, 34 source: #{raw search_autocomplete_source},
35 select: function(event, ui) { location.href = ui.item.url } 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,7 +16,7 @@
16 .padded 16 .padded
17 = f.label :source_branch, "From", class: "control-label" 17 = f.label :source_branch, "From", class: "control-label"
18 .controls 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 .mr_source_commit 20 .mr_source_commit
21 21
22 .span2 22 .span2
@@ -28,7 +28,7 @@ @@ -28,7 +28,7 @@
28 .padded 28 .padded
29 = f.label :target_branch, "To", class: "control-label" 29 = f.label :target_branch, "To", class: "control-label"
30 .controls 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 .mr_target_commit 32 .mr_target_commit
33 33
34 %h4.cdark 2. Fill info 34 %h4.cdark 2. Fill info
@@ -43,7 +43,7 @@ @@ -43,7 +43,7 @@
43 = f.label :assignee_id do 43 = f.label :assignee_id do
44 %i.icon-user 44 %i.icon-user
45 Assign to 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 .control-group 48 .control-group
49 49
@@ -56,18 +56,12 @@ @@ -56,18 +56,12 @@
56 = link_to project_merge_request_path(@project, @merge_request), class: "btn cancel-btn" do 56 = link_to project_merge_request_path(@project, @merge_request), class: "btn cancel-btn" do
57 Cancel 57 Cancel
58 58
59 -  
60 -  
61 :javascript 59 :javascript
62 $(function(){ 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 var source_branch = $("#merge_request_source_branch"); 62 var source_branch = $("#merge_request_source_branch");
68 var target_branch = $("#merge_request_target_branch"); 63 var target_branch = $("#merge_request_target_branch");
69 64
70 -  
71 $.get("#{branch_from_project_merge_requests_path(@project)}", {ref: source_branch.val() }); 65 $.get("#{branch_from_project_merge_requests_path(@project)}", {ref: source_branch.val() });
72 $.get("#{branch_to_project_merge_requests_path(@project)}", {ref: target_branch.val() }); 66 $.get("#{branch_to_project_merge_requests_path(@project)}", {ref: target_branch.val() });
73 67
@@ -79,4 +73,3 @@ @@ -79,4 +73,3 @@
79 $.get("#{branch_to_project_merge_requests_path(@project)}", {ref: $(this).val() }); 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,7 +41,7 @@
41 41
42 :javascript 42 :javascript
43 $(function() { 43 $(function() {
44 - disableButtonIfEmtpyField("#milestone_title", ".save-btn"); 44 + disableButtonIfEmptyField("#milestone_title", ".save-btn");
45 $( ".datepicker" ).datepicker({ 45 $( ".datepicker" ).datepicker({
46 dateFormat: "yy-mm-dd", 46 dateFormat: "yy-mm-dd",
47 onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } 47 onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }
app/views/milestones/edit.html.haml
1 = render "form" 1 = render "form"
2 -  
3 -:javascript  
4 - $(function(){  
5 - $('select#issue_assignee_id').chosen();  
6 - });  
7 -  
app/views/projects/_refs.html.haml
1 = form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do 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 = hidden_field_tag :destination, destination 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,7 +19,7 @@
19 .entry.clearfix 19 .entry.clearfix
20 = f.label :name, "Branch" 20 = f.label :name, "Branch"
21 .span3 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 &nbsp; 23 &nbsp;
24 = f.submit 'Protect', class: "primary btn" 24 = f.submit 'Protect', class: "primary btn"
25 25
@@ -46,6 +46,3 @@ @@ -46,6 +46,3 @@
46 %td 46 %td
47 - if can? current_user, :admin_project, @project 47 - if can? current_user, :admin_project, @project
48 = link_to 'Unprotect', [@project, branch], confirm: 'Are you sure?', method: :delete, class: "danger btn small" 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 %ul.nav.nav-tabs 1 %ul.nav.nav-tabs
2 %li 2 %li
3 = form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form", remote: true do 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 = hidden_field_tag :destination, "tree" 5 = hidden_field_tag :destination, "tree"
6 = hidden_field_tag :path, params[:path] 6 = hidden_field_tag :path, params[:path]
7 %li{class: "#{'active' if (controller.controller_name == "refs") }"} 7 %li{class: "#{'active' if (controller.controller_name == "refs") }"}
app/views/refs/_tree.html.haml
@@ -43,18 +43,11 @@ @@ -43,18 +43,11 @@
43 %i.icon-file 43 %i.icon-file
44 = content.name 44 = content.name
45 .file_content.wiki 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 :javascript 48 :javascript
53 $(function(){ 49 $(function(){
54 - $('.project-refs-select').chosen();  
55 -  
56 history.pushState({ path: this.path }, '', "#{@history_path}"); 50 history.pushState({ path: this.path }, '', "#{@history_path}");
57 -  
58 }); 51 });
59 52
60 // Load last commit log for each file in tree 53 // Load last commit log for each file in tree
app/views/refs/_tree_file.html.haml
@@ -9,10 +9,9 @@ @@ -9,10 +9,9 @@
9 = link_to "history", project_commits_path(@project, path: params[:path], ref: @ref), class: "btn very_small" 9 = link_to "history", project_commits_path(@project, path: params[:path], ref: @ref), class: "btn very_small"
10 = link_to "blame", blame_file_project_ref_path(@project, @ref, path: params[:path]), class: "btn very_small" 10 = link_to "blame", blame_file_project_ref_path(@project, @ref, path: params[:path]), class: "btn very_small"
11 - if file.text? 11 - if file.text?
12 - - if name =~ /\.(md|markdown)$/i 12 + - if markup?(name)
13 .file_content.wiki 13 .file_content.wiki
14 - = preserve do  
15 - = markdown(file.data) 14 + = raw GitHub::Markup.render(name, file.data)
16 - else 15 - else
17 .file_content.code 16 .file_content.code
18 - unless file.empty? 17 - unless file.empty?
app/views/refs/blame.html.haml
@@ -38,8 +38,3 @@ @@ -38,8 +38,3 @@
38 = preserve do 38 = preserve do
39 %pre 39 %pre
40 = Gitlab::Encode.utf8 lines.join("\n") 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,7 +16,7 @@
16 .input= f.text_field :file_name, placeholder: "example.rb" 16 .input= f.text_field :file_name, placeholder: "example.rb"
17 .clearfix 17 .clearfix
18 = f.label "Lifetime" 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 .clearfix 20 .clearfix
21 = f.label :content, "Code" 21 = f.label :content, "Code"
22 .input= f.text_area :content, class: "span8" 22 .input= f.text_area :content, class: "span8"
@@ -26,11 +26,3 @@ @@ -26,11 +26,3 @@
26 = link_to "Cancel", project_snippets_path(@project), class: " btn" 26 = link_to "Cancel", project_snippets_path(@project), class: " btn"
27 - unless @snippet.new_record? 27 - unless @snippet.new_record?
28 .right= link_to 'Destroy', [@project, @snippet], confirm: 'Are you sure?', method: :delete, class: "btn right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}" 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,21 +10,14 @@
10 10
11 %h6 1. Choose people you want in the team 11 %h6 1. Choose people you want in the team
12 .clearfix 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 %h6 2. Set access level for them 16 %h6 2. Set access level for them
18 .clearfix 17 .clearfix
19 = f.label :project_access, "Project Access" 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 .actions 21 .actions
24 = f.submit 'Save', class: "btn save-btn" 22 = f.submit 'Save', class: "btn save-btn"
25 = link_to "Cancel", team_project_path(@project), class: "btn cancel-btn" 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 - user = member.user 1 - user = member.user
2 - allow_admin = can? current_user, :admin_project, @project 2 - allow_admin = can? current_user, :admin_project, @project
3 %tr{id: dom_id(member), class: "team_member_row user_#{user.id}"} 3 %tr{id: dom_id(member), class: "team_member_row user_#{user.id}"}
4 - %td 4 + %td.span6
5 = link_to project_team_member_path(@project, member), title: user.name, class: "dark" do 5 = link_to project_team_member_path(@project, member), title: user.name, class: "dark" do
6 = image_tag gravatar_icon(user.email, 40), class: "avatar s32" 6 = image_tag gravatar_icon(user.email, 40), class: "avatar s32"
7 = link_to project_team_member_path(@project, member), title: user.name, class: "dark" do 7 = link_to project_team_member_path(@project, member), title: user.name, class: "dark" do
8 %strong= truncate(user.name, lenght: 40) 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 .right 13 .right
  14 + - if current_user == user
  15 + %span.btn.disabled This is you!
14 - if @project.owner == user 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 %span.btn.disabled.blocked Blocked 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 - if allow_admin 24 - if allow_admin
19 = form_for(member, as: :team_member, url: project_team_member_path(@project, member)) do |f| 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,11 +33,12 @@ app:
33 git_host: 33 git_host:
34 admin_uri: git@localhost:gitolite-admin 34 admin_uri: git@localhost:gitolite-admin
35 base_path: /home/git/repositories/ 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 git_user: git 38 git_user: git
39 upload_pack: true 39 upload_pack: true
40 receive_pack: true 40 receive_pack: true
  41 + # host: localhost
41 # port: 22 42 # port: 22
42 43
43 # Git settings 44 # Git settings
config/initializers/1_settings.rb
@@ -102,6 +102,10 @@ class Settings &lt; Settingslogic @@ -102,6 +102,10 @@ class Settings &lt; Settingslogic
102 git_host['admin_uri'] || 'git@localhost:gitolite-admin' 102 git_host['admin_uri'] || 'git@localhost:gitolite-admin'
103 end 103 end
104 104
  105 + def gitolite_admin_key
  106 + git_host['gitolite_admin_key'] || 'gitlab'
  107 + end
  108 +
105 def default_projects_limit 109 def default_projects_limit
106 app['default_projects_limit'] || 10 110 app['default_projects_limit'] || 10
107 end 111 end
doc/installation.md
@@ -113,17 +113,20 @@ Generate key: @@ -113,17 +113,20 @@ Generate key:
113 Clone GitLab's fork of the Gitolite source code: 113 Clone GitLab's fork of the Gitolite source code:
114 114
115 cd /home/git 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 Setup: 118 Setup:
119 119
  120 + cd /home/git
  121 + sudo -u git -H mkdir bin
120 sudo -u git sh -c 'echo -e "PATH=\$PATH:/home/git/bin\nexport PATH" >> /home/git/.profile' 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 sudo cp /home/gitlab/.ssh/id_rsa.pub /home/git/gitlab.pub 125 sudo cp /home/gitlab/.ssh/id_rsa.pub /home/git/gitlab.pub
123 sudo chmod 0444 /home/git/gitlab.pub 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 Permissions: 131 Permissions:
129 132
@@ -189,8 +192,8 @@ and ensure you have followed all of the above steps carefully. @@ -189,8 +192,8 @@ and ensure you have followed all of the above steps carefully.
189 192
190 #### Setup GitLab hooks 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 #### Check application status 198 #### Check application status
196 199
features/dashboard/dashboard.feature
@@ -15,4 +15,14 @@ Feature: Dashboard @@ -15,4 +15,14 @@ Feature: Dashboard
15 And I click "Create Merge Request" link 15 And I click "Create Merge Request" link
16 Then I see prefilled new Merge Request page 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,3 +109,28 @@ Given /^I have authored merge requests$/ do
109 :author => @user, 109 :author => @user,
110 :project => project2 110 :project => project2
111 end 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,7 +8,7 @@ module Gitlab
8 if @project ||= current_user.projects.find_by_id(params[:id]) || 8 if @project ||= current_user.projects.find_by_id(params[:id]) ||
9 current_user.projects.find_by_code(params[:id]) 9 current_user.projects.find_by_code(params[:id])
10 else 10 else
11 - error!({'message' => '404 Not found'}, 404) 11 + not_found!
12 end 12 end
13 13
14 @project 14 @project
@@ -19,7 +19,48 @@ module Gitlab @@ -19,7 +19,48 @@ module Gitlab
19 end 19 end
20 20
21 def authenticate! 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 end 64 end
24 end 65 end
25 end 66 end
lib/api/issues.rb
@@ -60,7 +60,7 @@ module Gitlab @@ -60,7 +60,7 @@ module Gitlab
60 if @issue.save 60 if @issue.save
61 present @issue, with: Entities::Issue 61 present @issue, with: Entities::Issue
62 else 62 else
63 - error!({'message' => '404 Not found'}, 404) 63 + not_found!
64 end 64 end
65 end 65 end
66 66
@@ -79,6 +79,8 @@ module Gitlab @@ -79,6 +79,8 @@ module Gitlab
79 # PUT /projects/:id/issues/:issue_id 79 # PUT /projects/:id/issues/:issue_id
80 put ":id/issues/:issue_id" do 80 put ":id/issues/:issue_id" do
81 @issue = user_project.issues.find(params[:issue_id]) 81 @issue = user_project.issues.find(params[:issue_id])
  82 + authorize! :modify_issue, @issue
  83 +
82 parameters = { 84 parameters = {
83 title: (params[:title] || @issue.title), 85 title: (params[:title] || @issue.title),
84 description: (params[:description] || @issue.description), 86 description: (params[:description] || @issue.description),
@@ -91,7 +93,7 @@ module Gitlab @@ -91,7 +93,7 @@ module Gitlab
91 if @issue.update_attributes(parameters) 93 if @issue.update_attributes(parameters)
92 present @issue, with: Entities::Issue 94 present @issue, with: Entities::Issue
93 else 95 else
94 - error!({'message' => '404 Not found'}, 404) 96 + not_found!
95 end 97 end
96 end 98 end
97 99
@@ -103,7 +105,7 @@ module Gitlab @@ -103,7 +105,7 @@ module Gitlab
103 # Example Request: 105 # Example Request:
104 # DELETE /projects/:id/issues/:issue_id 106 # DELETE /projects/:id/issues/:issue_id
105 delete ":id/issues/:issue_id" do 107 delete ":id/issues/:issue_id" do
106 - error!({'message' => 'method not allowed'}, 405) 108 + not_allowed!
107 end 109 end
108 end 110 end
109 end 111 end
lib/api/milestones.rb
@@ -45,7 +45,7 @@ module Gitlab @@ -45,7 +45,7 @@ module Gitlab
45 if @milestone.save 45 if @milestone.save
46 present @milestone, with: Entities::Milestone 46 present @milestone, with: Entities::Milestone
47 else 47 else
48 - error!({'message' => '404 Not found'}, 404) 48 + not_found!
49 end 49 end
50 end 50 end
51 51
@@ -61,6 +61,8 @@ module Gitlab @@ -61,6 +61,8 @@ module Gitlab
61 # Example Request: 61 # Example Request:
62 # PUT /projects/:id/milestones/:milestone_id 62 # PUT /projects/:id/milestones/:milestone_id
63 put ":id/milestones/:milestone_id" do 63 put ":id/milestones/:milestone_id" do
  64 + authorize! :admin_milestone, user_project
  65 +
64 @milestone = user_project.milestones.find(params[:milestone_id]) 66 @milestone = user_project.milestones.find(params[:milestone_id])
65 parameters = { 67 parameters = {
66 title: (params[:title] || @milestone.title), 68 title: (params[:title] || @milestone.title),
@@ -72,7 +74,7 @@ module Gitlab @@ -72,7 +74,7 @@ module Gitlab
72 if @milestone.update_attributes(parameters) 74 if @milestone.update_attributes(parameters)
73 present @milestone, with: Entities::Milestone 75 present @milestone, with: Entities::Milestone
74 else 76 else
75 - error!({'message' => '404 Not found'}, 404) 77 + not_found!
76 end 78 end
77 end 79 end
78 end 80 end
lib/api/projects.rb
@@ -50,7 +50,7 @@ module Gitlab @@ -50,7 +50,7 @@ module Gitlab
50 if @project.saved? 50 if @project.saved?
51 present @project, with: Entities::Project 51 present @project, with: Entities::Project
52 else 52 else
53 - error!({'message' => '404 Not found'}, 404) 53 + not_found!
54 end 54 end
55 end 55 end
56 56
@@ -74,6 +74,7 @@ module Gitlab @@ -74,6 +74,7 @@ module Gitlab
74 # Example Request: 74 # Example Request:
75 # POST /projects/:id/users 75 # POST /projects/:id/users
76 post ":id/users" do 76 post ":id/users" do
  77 + authorize! :admin_project, user_project
77 user_project.add_users_ids_to_team(params[:user_ids].values, params[:project_access]) 78 user_project.add_users_ids_to_team(params[:user_ids].values, params[:project_access])
78 nil 79 nil
79 end 80 end
@@ -87,6 +88,7 @@ module Gitlab @@ -87,6 +88,7 @@ module Gitlab
87 # Example Request: 88 # Example Request:
88 # PUT /projects/:id/add_users 89 # PUT /projects/:id/add_users
89 put ":id/users" do 90 put ":id/users" do
  91 + authorize! :admin_project, user_project
90 user_project.update_users_ids_to_role(params[:user_ids].values, params[:project_access]) 92 user_project.update_users_ids_to_role(params[:user_ids].values, params[:project_access])
91 nil 93 nil
92 end 94 end
@@ -99,6 +101,7 @@ module Gitlab @@ -99,6 +101,7 @@ module Gitlab
99 # Example Request: 101 # Example Request:
100 # DELETE /projects/:id/users 102 # DELETE /projects/:id/users
101 delete ":id/users" do 103 delete ":id/users" do
  104 + authorize! :admin_project, user_project
102 user_project.delete_users_ids_from_team(params[:user_ids].values) 105 user_project.delete_users_ids_from_team(params[:user_ids].values)
103 nil 106 nil
104 end 107 end
@@ -209,7 +212,7 @@ module Gitlab @@ -209,7 +212,7 @@ module Gitlab
209 if @snippet.save 212 if @snippet.save
210 present @snippet, with: Entities::ProjectSnippet 213 present @snippet, with: Entities::ProjectSnippet
211 else 214 else
212 - error!({'message' => '404 Not found'}, 404) 215 + not_found!
213 end 216 end
214 end 217 end
215 218
@@ -226,6 +229,8 @@ module Gitlab @@ -226,6 +229,8 @@ module Gitlab
226 # PUT /projects/:id/snippets/:snippet_id 229 # PUT /projects/:id/snippets/:snippet_id
227 put ":id/snippets/:snippet_id" do 230 put ":id/snippets/:snippet_id" do
228 @snippet = user_project.snippets.find(params[:snippet_id]) 231 @snippet = user_project.snippets.find(params[:snippet_id])
  232 + authorize! :modify_snippet, @snippet
  233 +
229 parameters = { 234 parameters = {
230 title: (params[:title] || @snippet.title), 235 title: (params[:title] || @snippet.title),
231 file_name: (params[:file_name] || @snippet.file_name), 236 file_name: (params[:file_name] || @snippet.file_name),
@@ -236,7 +241,7 @@ module Gitlab @@ -236,7 +241,7 @@ module Gitlab
236 if @snippet.update_attributes(parameters) 241 if @snippet.update_attributes(parameters)
237 present @snippet, with: Entities::ProjectSnippet 242 present @snippet, with: Entities::ProjectSnippet
238 else 243 else
239 - error!({'message' => '404 Not found'}, 404) 244 + not_found!
240 end 245 end
241 end 246 end
242 247
@@ -249,6 +254,8 @@ module Gitlab @@ -249,6 +254,8 @@ module Gitlab
249 # DELETE /projects/:id/snippets/:snippet_id 254 # DELETE /projects/:id/snippets/:snippet_id
250 delete ":id/snippets/:snippet_id" do 255 delete ":id/snippets/:snippet_id" do
251 @snippet = user_project.snippets.find(params[:snippet_id]) 256 @snippet = user_project.snippets.find(params[:snippet_id])
  257 + authorize! :modify_snippet, @snippet
  258 +
252 @snippet.destroy 259 @snippet.destroy
253 end 260 end
254 261
@@ -277,10 +284,10 @@ module Gitlab @@ -277,10 +284,10 @@ module Gitlab
277 ref = params[:sha] 284 ref = params[:sha]
278 285
279 commit = user_project.commit ref 286 commit = user_project.commit ref
280 - error!('404 Commit Not Found', 404) unless commit 287 + not_found! "Commit" unless commit
281 288
282 tree = Tree.new commit.tree, user_project, ref, params[:filepath] 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 if tree.text? 292 if tree.text?
286 encoding = Gitlab::Encode.detect_encoding(tree.data) 293 encoding = Gitlab::Encode.detect_encoding(tree.data)
lib/gitlab/backend/gitolite.rb
@@ -35,7 +35,7 @@ module Gitlab @@ -35,7 +35,7 @@ module Gitlab
35 end 35 end
36 36
37 def enable_automerge 37 def enable_automerge
38 - config.admin_all_repo!(project) 38 + config.admin_all_repo!
39 end 39 end
40 40
41 alias_method :create_repository, :update_repository 41 alias_method :create_repository, :update_repository
lib/gitlab/backend/gitolite_config.rb
@@ -148,18 +148,7 @@ module Gitlab @@ -148,18 +148,7 @@ module Gitlab
148 # Enable access to all repos for gitolite admin. 148 # Enable access to all repos for gitolite admin.
149 # We use it for accept merge request feature 149 # We use it for accept merge request feature
150 def admin_all_repo 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 # @ALL repos premission for gitolite owner 153 # @ALL repos premission for gitolite owner
165 repo_name = "@all" 154 repo_name = "@all"
lib/gitlab/merge.rb
@@ -21,8 +21,7 @@ module Gitlab @@ -21,8 +21,7 @@ module Gitlab
21 if output =~ /CONFLICT/ 21 if output =~ /CONFLICT/
22 false 22 false
23 else 23 else
24 - repo.git.push({}, "origin", merge_request.target_branch)  
25 - true 24 + !!repo.git.push({}, "origin", merge_request.target_branch)
26 end 25 end
27 end 26 end
28 end 27 end
spec/helpers/tree_helper_spec.rb 0 → 100644
@@ -0,0 +1,15 @@ @@ -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,4 +49,26 @@ describe Event do
49 it { @event.branch_name.should == "master" } 49 it { @event.branch_name.should == "master" }
50 it { @event.author.should == @user } 50 it { @event.author.should == @user }
51 end 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 end 74 end
spec/observers/users_project_observer_spec.rb
@@ -23,6 +23,14 @@ describe UsersProjectObserver do @@ -23,6 +23,14 @@ describe UsersProjectObserver do
23 Notify.should_receive(:project_access_granted_email).with(users_project.id).and_return(double(deliver: true)) 23 Notify.should_receive(:project_access_granted_email).with(users_project.id).and_return(double(deliver: true))
24 subject.after_create(users_project) 24 subject.after_create(users_project)
25 end 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 end 34 end
27 35
28 describe "#after_update" do 36 describe "#after_update" do
@@ -37,4 +45,23 @@ describe UsersProjectObserver do @@ -37,4 +45,23 @@ describe UsersProjectObserver do
37 subject.after_update(users_project) 45 subject.after_update(users_project)
38 end 46 end
39 end 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 end 67 end
spec/requests/api/projects_spec.rb
@@ -86,7 +86,7 @@ describe Gitlab::API do @@ -86,7 +86,7 @@ describe Gitlab::API do
86 it "should return a 404 error if not found" do 86 it "should return a 404 error if not found" do
87 get api("/projects/42", user) 87 get api("/projects/42", user)
88 response.status.should == 404 88 response.status.should == 404
89 - json_response['message'].should == '404 Not found' 89 + json_response['message'].should == '404 Not Found'
90 end 90 end
91 end 91 end
92 92