Commit 2e34a6d3c40a60ed689de5d7870fe663b1959e88
Exists in
master
and in
4 other branches
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 | ... | ... |
... | ... | @@ -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 | -} |
... | ... | @@ -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
app/controllers/refs_controller.rb
app/decorators/event_decorator.rb
... | ... | @@ -8,7 +8,9 @@ class EventDecorator < 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 < 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 < 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 < 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 < 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 < 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 < 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
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
... | ... | @@ -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
app/views/issues/new.html.haml
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
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 | |
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
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 < 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
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
... | ... | @@ -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 | ... | ... |