Merge branch 'master' into project_hooks_api

This commit is contained in:
miks 2012-09-10 16:47:31 +03:00
commit 2e34a6d3c4
58 changed files with 468 additions and 335 deletions

20
Gemfile
View file

@ -1,5 +1,13 @@
source "http://rubygems.org"
def darwin_only(require_as)
RUBY_PLATFORM.include?('darwin') && require_as
end
def linux_only(require_as)
RUBY_PLATFORM.include?('linux') && require_as
end
gem "rails", "3.2.8"
# Supported DBs
@ -45,6 +53,7 @@ gem "seed-fu"
# Markdown to HTML
gem "redcarpet", "~> 2.1.1"
gem "github-markup", "~> 0.7.4"
# Servers
gem "thin"
@ -101,13 +110,20 @@ group :development, :test do
gem "capybara"
gem "capybara-webkit"
gem "headless"
gem "autotest"
gem "autotest-rails"
gem "pry"
gem "awesome_print"
gem "database_cleaner"
gem "launchy"
gem 'factory_girl_rails'
# Guard
gem 'guard-rspec'
gem 'guard-cucumber'
# Notification
gem 'rb-fsevent', :require => darwin_only('rb-fsevent')
gem 'growl', :require => darwin_only('growl')
gem 'rb-inotify', :require => linux_only('rb-inotify')
end
group :test do

View file

@ -68,7 +68,6 @@ GIT
GEM
remote: http://rubygems.org/
specs:
ZenTest (4.8.1)
actionmailer (3.2.8)
actionpack (= 3.2.8)
mail (~> 2.4.4)
@ -100,10 +99,6 @@ GEM
rails (~> 3.0)
addressable (2.2.8)
arel (3.0.2)
autotest (4.4.6)
ZenTest (>= 4.4.1)
autotest-rails (4.1.2)
ZenTest (~> 4.5)
awesome_print (1.0.2)
bcrypt-ruby (3.0.1)
blankslate (2.1.2.4)
@ -178,6 +173,7 @@ GEM
gherkin (2.11.0)
json (>= 1.4.6)
git (1.2.5)
github-markup (0.7.4)
gitlab_meta (2.9)
grape (0.2.1)
hashie (~> 1.2)
@ -185,6 +181,15 @@ GEM
multi_xml
rack
rack-mount
growl (1.0.3)
guard (1.3.2)
listen (>= 0.4.2)
thor (>= 0.14.6)
guard-cucumber (1.2.0)
cucumber (>= 1.2.0)
guard (>= 1.1.0)
guard-rspec (1.2.1)
guard (>= 1.1)
haml (3.1.6)
haml-rails (0.3.4)
actionpack (~> 3.0)
@ -218,6 +223,7 @@ GEM
libv8 (3.3.10.4)
libwebsocket (0.1.3)
addressable
listen (0.5.0)
mail (2.4.4)
i18n (>= 0.4.0)
mime-types (~> 1.16)
@ -273,6 +279,9 @@ GEM
raindrops (0.9.0)
rake (0.9.2.2)
raphael-rails (1.5.2)
rb-fsevent (0.9.1)
rb-inotify (0.8.8)
ffi (>= 0.5.0)
rdoc (3.12)
json (~> 1.4)
redcarpet (2.1.1)
@ -376,8 +385,6 @@ PLATFORMS
DEPENDENCIES
acts-as-taggable-on (= 2.3.1)
annotate!
autotest
autotest-rails
awesome_print
bootstrap-sass (= 2.0.4)
capybara
@ -396,11 +403,15 @@ DEPENDENCIES
ffaker
foreman
git
github-markup (~> 0.7.4)
gitlab_meta (= 2.9)
gitolite!
grack!
grape (~> 0.2.1)
grit!
growl
guard-cucumber
guard-rspec
haml-rails
headless
httparty
@ -418,6 +429,8 @@ DEPENDENCIES
rack-mini-profiler
rails (= 3.2.8)
raphael-rails (= 1.5.2)
rb-fsevent
rb-inotify
redcarpet (~> 2.1.1)
resque (~> 1.20.0)
resque_mailer

30
Guardfile Normal file
View file

@ -0,0 +1,30 @@
# A sample Guardfile
# More info at https://github.com/guard/guard#readme
guard 'rspec', :version => 2 do
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
watch('spec/spec_helper.rb') { "spec" }
# Rails example
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
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"] }
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
watch('config/routes.rb') { "spec/routing" }
watch('app/controllers/application_controller.rb') { "spec/controllers" }
# Capybara request specs
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
# Turnip features and steps
watch(%r{^spec/acceptance/(.+)\.feature$})
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
end
guard 'cucumber' do
watch(%r{^features/.+\.feature$})
watch(%r{^features/support/.+$}) { 'features' }
watch(%r{^features/step_definitions/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'features' }
end

View file

@ -5,7 +5,7 @@ function switchToNewIssue(form){
$('select#issue_milestone_id').chosen();
$("#new_issue_dialog").show("fade", { direction: "right" }, 150);
$('.top-tabs .add_new').hide();
disableButtonIfEmtpyField("#issue_title", ".save-btn");
disableButtonIfEmptyField("#issue_title", ".save-btn");
});
}
@ -16,7 +16,7 @@ function switchToEditIssue(form){
$('select#issue_milestone_id').chosen();
$("#edit_issue_dialog").show("fade", { direction: "right" }, 150);
$('.add_new').hide();
disableButtonIfEmtpyField("#issue_title", ".save-btn");
disableButtonIfEmptyField("#issue_title", ".save-btn");
});
}

View file

@ -1,130 +0,0 @@
$(document).ready(function(){
$(".one_click_select").live("click", function(){
$(this).select();
});
$('body').on('ajax:complete, ajax:beforeSend, submit', 'form', function(e){
var buttons = $('[type="submit"]', this);
switch( e.type ){
case 'ajax:beforeSend':
case 'submit':
buttons.attr('disabled', 'disabled');
break;
case ' ajax:complete':
default:
buttons.removeAttr('disabled');
break;
}
})
$(".account-box").mouseenter(showMenu);
$(".account-box").mouseleave(resetMenu);
$("#projects-list .project").live('click', function(e){
if(e.target.nodeName != "A" && e.target.nodeName != "INPUT") {
location.href = $(this).attr("url");
e.stopPropagation();
return false;
}
});
/**
* Focus search field by pressing 's' key
*/
$(document).keypress(function(e) {
if( $(e.target).is(":input") ) return;
switch(e.which) {
case 115: focusSearch();
e.preventDefault();
}
});
/**
* Commit show suppressed diff
*
*/
$(".supp_diff_link").bind("click", function() {
showDiff(this);
});
/**
* Note markdown preview
*
*/
$(document).on('click', '#preview-link', function(e) {
$('#preview-note').text('Loading...');
var previewLinkText = ($(this).text() == 'Preview' ? 'Edit' : 'Preview');
$(this).text(previewLinkText);
var note = $('#note_note').val();
if (note.trim().length === 0) { note = 'Nothing to preview'; }
$.post($(this).attr('href'), {note: note}, function(data) {
$('#preview-note').html(data);
});
$('#preview-note, #note_note').toggle();
e.preventDefault();
});
});
function focusSearch() {
$("#search").focus();
}
function updatePage(data){
$.ajax({type: "GET", url: location.href, data: data, dataType: "script"});
}
function showMenu() {
$(this).toggleClass('hover');
}
function resetMenu() {
$(this).removeClass("hover");
}
function slugify(text) {
return text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase();
}
function showDiff(link) {
$(link).next('table').show();
$(link).remove();
}
(function($){
var _chosen = $.fn.chosen;
$.fn.extend({
chosen: function(options) {
var default_options = {'search_contains' : 'true'};
$.extend(default_options, options);
return _chosen.apply(this, [default_options]);
}})
})(jQuery);
function ajaxGet(url) {
$.ajax({type: "GET", url: url, dataType: "script"});
}
/**
* Disable button if text field is empty
*/
function disableButtonIfEmtpyField(field_selector, button_selector) {
field = $(field_selector);
if(field.val() == "") {
field.closest("form").find(button_selector).attr("disabled", "disabled").addClass("disabled");
}
field.on('keyup', function(){
var field = $(this);
var closest_submit = field.closest("form").find(button_selector);
if(field.val() == "") {
closest_submit.attr("disabled", "disabled").addClass("disabled");
} else {
closest_submit.removeAttr("disabled").removeClass("disabled");
}
})
}

View file

@ -0,0 +1,92 @@
window.updatePage = (data) ->
$.ajax({type: "GET", url: location.href, data: data, dataType: "script"})
window.slugify = (text) ->
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
window.ajaxGet = (url) ->
$.ajax({type: "GET", url: url, dataType: "script"})
# Disable button if text field is empty
window.disableButtonIfEmptyField = (field_selector, button_selector) ->
field = $(field_selector)
closest_submit = field.closest("form").find(button_selector)
closest_submit.disable() if field.val() is ""
field.on "keyup", ->
if $(this).val() is ""
closest_submit.disable()
else
closest_submit.enable()
$ ->
# Click a .one_click_select field, select the contents
$(".one_click_select").live 'click', -> $(this).select()
# Initialize chosen selects
$('select.chosen').chosen()
# Disable form buttons while a form is submitting
$('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
buttons = $('[type="submit"]', this)
switch e.type
when 'ajax:beforeSend', 'submit'
buttons.disable()
else
buttons.enable()
# Show/Hide the profile menu when hovering the account box
$('.account-box').hover -> $(this).toggleClass('hover')
# Focus search field by pressing 's' key
$(document).keypress (e) ->
# Don't do anything if typing in an input
return if $(e.target).is(":input")
switch e.which
when 115
$("#search").focus()
e.preventDefault()
# Commit show suppressed diff
$(".supp_diff_link").bind "click", ->
$(this).next('table').show()
$(this).remove()
# Note markdown preview
$(document).on 'click', '#preview-link', (e) ->
$('#preview-note').text('Loading...')
previewLinkText = if $(this).text() == 'Preview' then 'Edit' else 'Preview'
$(this).text(previewLinkText)
note = $('#note_note').val()
if note.trim().length == 0
$('#preview-note').text("Nothing to preview.")
else
$.post $(this).attr('href'), {note: note}, (data) ->
$('#preview-note').html(data)
$('#preview-note, #note_note').toggle()
e.preventDefault()
false
(($) ->
_chosen = $.fn.chosen
$.fn.extend chosen: (options) ->
default_options = search_contains: "true"
$.extend default_options, options
_chosen.apply this, [default_options]
# Disable an element and add the 'disabled' Bootstrap class
$.fn.extend disable: ->
$(this).attr('disabled', 'disabled').addClass('disabled')
# Enable an element and remove the 'disabled' Bootstrap class
$.fn.extend enable: ->
$(this).removeAttr('disabled').removeClass('disabled')
)(jQuery)

View file

@ -25,14 +25,14 @@ var NoteList = {
$(this).closest('li').fadeOut(); });
$(".note-form-holder").live("ajax:before", function(){
$(".submit_note").attr("disabled", "disabled");
$(".submit_note").disable()
})
$(".note-form-holder").live("ajax:complete", function(){
$(".submit_note").removeAttr("disabled");
$(".submit_note").enable()
})
disableButtonIfEmtpyField(".note-text", ".submit_note");
disableButtonIfEmptyField(".note-text", ".submit_note");
$(".note-text").live("focus", function(){
$(this).css("height", "80px");
@ -177,6 +177,6 @@ var PerLineNotes = {
form.show();
return false;
});
disableButtonIfEmtpyField(".line-note-text", ".submit_inline_note");
disableButtonIfEmptyField(".line-note-text", ".submit_inline_note");
}
}

View file

@ -8,7 +8,7 @@ window.Projects = ->
$('.save-project-loader').show()
$('form #project_default_branch').chosen()
disableButtonIfEmtpyField '#project_name', '.project-submit'
disableButtonIfEmptyField '#project_name', '.project-submit'
# Git clone panel switcher
$ ->

View file

@ -179,6 +179,15 @@ span.update-author {
&.merged {
background-color: #2A2;
}
&.joined {
background-color: #1ca9dd;
}
&.left {
background-color: #888;
float:none;
}
}
form {

View file

@ -1,3 +1,5 @@
require 'github/markup'
class RefsController < ApplicationController
include Gitlab::Encode
before_filter :project

View file

@ -8,6 +8,8 @@ class EventDecorator < ApplicationDecorator
"#{self.author_name} #{self.action_name} MR ##{self.target_id}:" + self.merge_request_title
elsif self.push?
"#{self.author_name} #{self.push_action_name} #{self.ref_type} " + self.ref_name
elsif self.membership_changed?
"#{self.author_name} #{self.action_name} #{self.project.name}"
else
""
end

View file

@ -2,5 +2,9 @@ module ProjectsHelper
def grouper_project_members(project)
@project.users_projects.sort_by(&:project_access).reverse.group_by(&:project_access)
end
def remove_from_team_message(project, member)
"You are going to remove #{member.user_name} from #{project.name}. Are you sure?"
end
end

View file

@ -24,4 +24,14 @@ module TreeHelper
content.name
end
end
# Public: Determines if a given filename is compatible with GitHub::Markup.
#
# filename - Filename string to check
#
# Returns boolean
def markup?(filename)
filename.end_with?(*%w(.mdown .md .markdown .textile .rdoc .org .creole
.mediawiki .rst .asciidoc .pod))
end
end

View file

@ -10,6 +10,8 @@ class Event < ActiveRecord::Base
Pushed = 5
Commented = 6
Merged = 7
Joined = 8 # User joined project
Left = 9 # User left project
belongs_to :project
belongs_to :target, polymorphic: true
@ -37,7 +39,7 @@ class Event < ActiveRecord::Base
# - new issue
# - merge request
def allowed?
push? || issue? || merge_request?
push? || issue? || merge_request? || membership_changed?
end
def push?
@ -84,6 +86,18 @@ class Event < ActiveRecord::Base
[Closed, Reopened].include?(action)
end
def joined?
action == Joined
end
def left?
action == Left
end
def membership_changed?
joined? || left?
end
def issue
target if target_type == "Issue"
end
@ -101,6 +115,10 @@ class Event < ActiveRecord::Base
"closed"
elsif merged?
"merged"
elsif joined?
'joined'
elsif left?
'left'
else
"opened"
end

View file

@ -162,7 +162,7 @@ class MergeRequest < ActiveRecord::Base
end
def automerge!(current_user)
if Gitlab::Merge.new(self, current_user).merge
if Gitlab::Merge.new(self, current_user).merge && self.unmerged_commits.empty?
self.merge!(current_user.id)
true
end

View file

@ -23,7 +23,7 @@ class UsersProject < ActiveRecord::Base
def self.bulk_delete(project, user_ids)
UsersProject.transaction do
UsersProject.where(:user_id => user_ids, :project_id => project.id).each do |users_project|
users_project.delete
users_project.destroy
end
end
end

View file

@ -1,9 +1,24 @@
class UsersProjectObserver < ActiveRecord::Observer
def after_create(users_project)
Notify.project_access_granted_email(users_project.id).deliver
Event.create(
project_id: users_project.project.id,
action: Event::Joined,
author_id: users_project.user.id
)
end
def after_update(users_project)
Notify.project_access_granted_email(users_project.id).deliver
end
def after_destroy(users_project)
Event.create(
project_id: users_project.project.id,
action: Event::Left,
author_id: users_project.user.id
)
end
end

View file

@ -90,6 +90,8 @@ module PushEvent
def push_with_commits?
md_ref? && commits.any? && parent_commit && last_commit
rescue Grit::NoSuchPathError
false
end
def last_push_to_non_root?

View file

@ -32,7 +32,7 @@
- unless project.new_record?
.clearfix
= f.label :owner_id
.input= f.select :owner_id, User.all.map { |user| [user.name, user.id] }
.input= f.select :owner_id, User.all.map { |user| [user.name, user.id] }, {}, {class: 'chosen'}
- if project.repo_exists?
.clearfix
@ -69,7 +69,6 @@
:javascript
$(function(){
$('#project_owner_id').chosen();
new Projects();
})

View file

@ -71,25 +71,11 @@
%th Project Access:
%tr
%td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true
%td= select_tag :project_access, options_for_select(Project.access_options), class: "project-access-select"
%td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
%td= select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3"}
%tr
%td= submit_tag 'Add', class: "btn primary"
%td
Read more about project permissions
%strong= link_to "here", help_permissions_path, class: "vlink"
:css
form select {
width:150px;
}
#user_ids {
width:300px;
}
:javascript
$('select#user_ids').chosen();
$('select#repo_access').chosen();
$('select#project_access').chosen();

View file

@ -8,20 +8,9 @@
.clearfix
%label Project Access:
.input
= f.select :project_access, options_for_select(Project.access_options, @admin_team_member.project_access), {}, class: "project-access-select"
= f.select :project_access, options_for_select(Project.access_options, @admin_team_member.project_access), {}, class: "project-access-select chosen span3"
%br
.actions
= f.submit 'Save', class: "btn primary"
= link_to 'Cancel', :back, class: "btn"
:css
form select {
width:300px;
}
:javascript
$('select#team_member_user_id').chosen();
$('select#team_member_project_id').chosen();
$('select#team_member_repo_access').chosen();
$('select#team_member_project_access').chosen();

View file

@ -68,8 +68,8 @@
%th Project Access:
%tr
%td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name), multiple: true
%td= select_tag :project_access, options_for_select(Project.access_options), class: "project-access-select"
%td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5'
%td= select_tag :project_access, options_for_select(Project.access_options), class: "project-access-select chosen span3"
%tr
%td= submit_tag 'Add', class: "btn primary"
@ -97,17 +97,3 @@
%td= select_tag :tm_project_access, options_for_select(Project.access_options, tm.project_access), class: "medium project-access-select", disabled: :disabled
%td= link_to 'Edit Access', edit_admin_team_member_path(tm), class: "btn small"
%td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn small danger"
:css
form select {
width:150px;
}
#project_ids {
width:300px;
}
:javascript
$('select#project_ids').chosen();
$('select#repo_access').chosen();
$('select#project_access').chosen();

View file

@ -1,7 +1,7 @@
%ul.nav.nav-tabs
%li
= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
= select_tag "ref", grouped_options_refs, onchange: "$(this.form).trigger('submit');", class: "project-refs-select"
= select_tag "ref", grouped_options_refs, onchange: "$(this.form).trigger('submit');", class: "project-refs-select chosen"
= hidden_field_tag :destination, "commits"
%li{class: "#{'active' if current_page?(project_commits_path(@project)) }"}
@ -26,8 +26,3 @@
%span.rss-icon
= link_to project_commits_path(@project, :atom, { private_token: current_user.private_token, ref: @ref }), title: "Feed" do
= image_tag "rss_ui.png", title: "feed"
:javascript
$(function(){
$('.project-refs-select').chosen();
});

View file

@ -15,7 +15,7 @@
$(function() {
$('#new_user').toggle();
});
= form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => { :class => "login-box" }) do |f|
= form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => { :class => "login-box" }) do |f|
= f.text_field :email, :class => "text top", :placeholder => "Email"
= f.password_field :password, :class => "text bottom", :placeholder => "Password"
- if devise_mapping.rememberable?

View file

@ -11,3 +11,7 @@
.event_feed
= render "events/event_push", event: event
- elsif event.membership_changed?
.event_feed
= render "events/event_membership_changed", event: event

View file

@ -0,0 +1,9 @@
= image_tag gravatar_icon(event.author_email), class: "avatar"
%strong #{event.author_name}
%span.event_label{class: event.action_name}= event.action_name
project
%strong= link_to event.project.name, event.project
%span.cgray
= time_ago_in_words(event.created_at)
ago.

View file

@ -18,12 +18,12 @@
= f.label :assignee_id do
%i.icon-user
Assign to
.input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select a user" })
.input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select a user" }, {class: 'chosen'})
.issue_milestone
= f.label :milestone_id do
%i.icon-time
Milestone
.input= f.select(:milestone_id, @project.milestones.active.all.collect {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" })
.input= f.select(:milestone_id, @project.milestones.active.all.collect {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }, {class: 'chosen'})
.issue_description
.clearfix

View file

@ -1,8 +1 @@
= render "form"
:javascript
$(function(){
$('select#issue_assignee_id').chosen();
$('select#issue_milestone_id').chosen();
});

View file

@ -1,8 +1 @@
= render "form"
:javascript
$(function(){
$('select#issue_assignee_id').chosen();
$('select#issue_milestone_id').chosen();
});

View file

@ -34,12 +34,4 @@
source: #{raw search_autocomplete_source},
select: function(event, ui) { location.href = ui.item.url }
});
$(document).keypress(function(e) {
if($(e.target).is(":input")) return;
switch(e.which) {
case 115: focusSearch();
e.preventDefault();
}
});
});

View file

@ -16,7 +16,7 @@
.padded
= f.label :source_branch, "From", class: "control-label"
.controls
= f.select(:source_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, style: "width:250px")
= f.select(:source_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, {class: 'chosen span3'})
.mr_source_commit
.span2
@ -28,7 +28,7 @@
.padded
= f.label :target_branch, "To", class: "control-label"
.controls
= f.select(:target_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, style: "width:250px")
= f.select(:target_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, {class: 'chosen span3'})
.mr_target_commit
%h4.cdark 2. Fill info
@ -43,7 +43,7 @@
= f.label :assignee_id do
%i.icon-user
Assign to
.input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select user" }, style: "width:250px")
.input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select user" }, {class: 'chosen span3'})
.control-group
@ -56,18 +56,12 @@
= link_to project_merge_request_path(@project, @merge_request), class: "btn cancel-btn" do
Cancel
:javascript
$(function(){
disableButtonIfEmtpyField("#merge_request_title", ".save-btn");
$('select#merge_request_assignee_id').chosen();
$('select#merge_request_source_branch').chosen();
$('select#merge_request_target_branch').chosen();
disableButtonIfEmptyField("#merge_request_title", ".save-btn");
var source_branch = $("#merge_request_source_branch");
var target_branch = $("#merge_request_target_branch");
$.get("#{branch_from_project_merge_requests_path(@project)}", {ref: source_branch.val() });
$.get("#{branch_to_project_merge_requests_path(@project)}", {ref: target_branch.val() });
@ -79,4 +73,3 @@
$.get("#{branch_to_project_merge_requests_path(@project)}", {ref: $(this).val() });
});
});

View file

@ -41,7 +41,7 @@
:javascript
$(function() {
disableButtonIfEmtpyField("#milestone_title", ".save-btn");
disableButtonIfEmptyField("#milestone_title", ".save-btn");
$( ".datepicker" ).datepicker({
dateFormat: "yy-mm-dd",
onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }

View file

@ -1,7 +1 @@
= render "form"
:javascript
$(function(){
$('select#issue_assignee_id').chosen();
});

View file

@ -1,8 +1,3 @@
= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
= select_tag "ref", grouped_options_refs, onchange: "this.form.submit();", class: "project-refs-select"
= select_tag "ref", grouped_options_refs, onchange: "this.form.submit();", class: "project-refs-select chosen"
= hidden_field_tag :destination, destination
:javascript
$(function(){
$('.project-refs-select').chosen();
})

View file

@ -19,7 +19,7 @@
.entry.clearfix
= f.label :name, "Branch"
.span3
= f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , { include_blank: "-- Select branch" }, { class: "span3" })
= f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: "Select branch"}, {class: "chosen span3"})
&nbsp;
= f.submit 'Protect', class: "primary btn"
@ -46,6 +46,3 @@
%td
- if can? current_user, :admin_project, @project
= link_to 'Unprotect', [@project, branch], confirm: 'Are you sure?', method: :delete, class: "danger btn small"
:javascript
$('select#protected_branch_name').chosen();

View file

@ -1,7 +1,7 @@
%ul.nav.nav-tabs
%li
= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form", remote: true do
= select_tag "ref", grouped_options_refs, onchange: "$(this.form).trigger('submit');", class: "project-refs-select"
= select_tag "ref", grouped_options_refs, onchange: "$(this.form).trigger('submit');", class: "project-refs-select chosen"
= hidden_field_tag :destination, "tree"
= hidden_field_tag :path, params[:path]
%li{class: "#{'active' if (controller.controller_name == "refs") }"}

View file

@ -43,18 +43,11 @@
%i.icon-file
= content.name
.file_content.wiki
- if content.name =~ /\.(md|markdown)$/i
= preserve do
= markdown(content.data)
- else
= simple_format(content.data)
= raw GitHub::Markup.render(content.name, content.data)
:javascript
$(function(){
$('.project-refs-select').chosen();
history.pushState({ path: this.path }, '', "#{@history_path}");
});
// Load last commit log for each file in tree

View file

@ -9,10 +9,9 @@
= link_to "history", project_commits_path(@project, path: params[:path], ref: @ref), class: "btn very_small"
= link_to "blame", blame_file_project_ref_path(@project, @ref, path: params[:path]), class: "btn very_small"
- if file.text?
- if name =~ /\.(md|markdown)$/i
- if markup?(name)
.file_content.wiki
= preserve do
= markdown(file.data)
= raw GitHub::Markup.render(name, file.data)
- else
.file_content.code
- unless file.empty?

View file

@ -38,8 +38,3 @@
= preserve do
%pre
= Gitlab::Encode.utf8 lines.join("\n")
:javascript
$(function(){
$('.project-refs-select').chosen();
});

View file

@ -16,7 +16,7 @@
.input= f.text_field :file_name, placeholder: "example.rb"
.clearfix
= f.label "Lifetime"
.input= f.select :expires_at, lifetime_select_options, {}, style: "width:200px;"
.input= f.select :expires_at, lifetime_select_options, {}, {class: 'chosen span2'}
.clearfix
= f.label :content, "Code"
.input= f.text_area :content, class: "span8"
@ -26,11 +26,3 @@
= link_to "Cancel", project_snippets_path(@project), class: " btn"
- unless @snippet.new_record?
.right= link_to 'Destroy', [@project, @snippet], confirm: 'Are you sure?', method: :delete, class: "btn right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}"
:javascript
$(function(){
$('select#snippet_expires_at').chosen();
});

View file

@ -10,21 +10,14 @@
%h6 1. Choose people you want in the team
.clearfix
= f.label :user_ids, "Peolpe"
.input= select_tag(:user_ids, options_from_collection_for_select(User.not_in_project(@project).all, :id, :name), { class: "xxlarge", multiple: true })
= f.label :user_ids, "People"
.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})
%h6 2. Set access level for them
.clearfix
= f.label :project_access, "Project Access"
.input= select_tag :project_access, options_for_select(Project.access_options, @team_member.project_access), class: "project-access-select"
.input= select_tag :project_access, options_for_select(Project.access_options, @team_member.project_access), class: "project-access-select chosen"
.actions
= f.submit 'Save', class: "btn save-btn"
= link_to "Cancel", team_project_path(@project), class: "btn cancel-btn"
:javascript
$('select#user_ids').chosen();
$('select#project_access').chosen();

View file

@ -1,20 +1,26 @@
- user = member.user
- allow_admin = can? current_user, :admin_project, @project
%tr{id: dom_id(member), class: "team_member_row user_#{user.id}"}
%td
%td.span6
= link_to project_team_member_path(@project, member), title: user.name, class: "dark" do
= image_tag gravatar_icon(user.email, 40), class: "avatar s32"
= link_to project_team_member_path(@project, member), title: user.name, class: "dark" do
%strong= truncate(user.name, lenght: 40)
%br
%div.cgray= user.email
%small.cgray= user.email
%td
%td.span5
.right
- if current_user == user
%span.btn.disabled This is you!
- if @project.owner == user
%span.btn.disabled.success Project Owner
- if user.blocked
%span.btn.disabled.success Owner
- elsif user.blocked
%span.btn.disabled.blocked Blocked
- elsif allow_admin
= 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
%i.icon-minus.icon-white
- if allow_admin
= form_for(member, as: :team_member, url: project_team_member_path(@project, member)) do |f|
= f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select"
= f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2"

View file

@ -33,11 +33,12 @@ app:
git_host:
admin_uri: git@localhost:gitolite-admin
base_path: /home/git/repositories/
# hooks_path: /var/lib/gitolite/.gitolite/hooks/ # only needed when gitolite is not installed according the manual
# host: localhost
hooks_path: /home/git/.gitolite/hooks/
gitolite_admin_key: gitlab
git_user: git
upload_pack: true
receive_pack: true
# host: localhost
# port: 22
# Git settings

View file

@ -102,6 +102,10 @@ class Settings < Settingslogic
git_host['admin_uri'] || 'git@localhost:gitolite-admin'
end
def gitolite_admin_key
git_host['gitolite_admin_key'] || 'gitlab'
end
def default_projects_limit
app['default_projects_limit'] || 10
end

View file

@ -113,17 +113,20 @@ Generate key:
Clone GitLab's fork of the Gitolite source code:
cd /home/git
sudo -H -u git git clone https://github.com/gitlabhq/gitolite.git /home/git/gitolite
sudo -H -u git git clone -b gl-v304 https://github.com/gitlabhq/gitolite.git /home/git/gitolite
Setup:
cd /home/git
sudo -u git -H mkdir bin
sudo -u git sh -c 'echo -e "PATH=\$PATH:/home/git/bin\nexport PATH" >> /home/git/.profile'
sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; /home/git/gitolite/src/gl-system-install"
sudo -u git sh -c 'gitolite/install -ln /home/git/bin'
sudo cp /home/gitlab/.ssh/id_rsa.pub /home/git/gitlab.pub
sudo chmod 0444 /home/git/gitlab.pub
sudo -u git -H sed -i 's/0077/0007/g' /home/git/share/gitolite/conf/example.gitolite.rc
sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; gl-setup -q /home/git/gitlab.pub"
sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; gitolite setup -pk /home/git/gitlab.pub"
sudo -u git -H sed -i 's/0077/0007/g' /home/git/.gitolite.rc
Permissions:
@ -189,8 +192,8 @@ and ensure you have followed all of the above steps carefully.
#### Setup GitLab hooks
sudo cp ./lib/hooks/post-receive /home/git/share/gitolite/hooks/common/post-receive
sudo chown git:git /home/git/share/gitolite/hooks/common/post-receive
sudo cp ./lib/hooks/post-receive /home/git/.gitolite/hooks/common/post-receive
sudo chown git:git /home/git/.gitolite/hooks/common/post-receive
#### Check application status

View file

@ -15,4 +15,14 @@ Feature: Dashboard
And I click "Create Merge Request" link
Then I see prefilled new Merge Request page
Scenario: I should see User joined Project event
Given user with name "John Doe" joined project "Shop"
When I visit dashboard page
Then I should see "John Doe joined project Shop" event
Scenario: I should see User left Project event
Given user with name "John Doe" joined project "Shop"
And user with name "John Doe" left project "Shop"
When I visit dashboard page
Then I should see "John Doe left project Shop" event

View file

@ -109,3 +109,28 @@ Given /^I have authored merge requests$/ do
:author => @user,
:project => project2
end
Given /^user with name "(.*?)" joined project "(.*?)"$/ do |user_name, project_name|
user = Factory.create(:user, {name: user_name})
project = Project.find_by_name project_name
Event.create(
project: project,
author_id: user.id,
action: Event::Joined
)
end
Given /^user with name "(.*?)" left project "(.*?)"$/ do |user_name, project_name|
user = User.find_by_name user_name
project = Project.find_by_name project_name
Event.create(
project: project,
author_id: user.id,
action: Event::Left
)
end
Then /^I should see "(.*?)" event$/ do |event_text|
page.should have_content(event_text)
end

View file

@ -8,7 +8,7 @@ module Gitlab
if @project ||= current_user.projects.find_by_id(params[:id]) ||
current_user.projects.find_by_code(params[:id])
else
error!({'message' => '404 Not found'}, 404)
not_found!
end
@project
@ -19,7 +19,48 @@ module Gitlab
end
def authenticate!
error!({'message' => '401 Unauthorized'}, 401) unless current_user
unauthorized! unless current_user
end
def authorize! action, subject
unless abilities.allowed?(current_user, action, subject)
forbidden!
end
end
# error helpers
def forbidden!
render_api_error!('403 Forbidden', 403)
end
def not_found!(resource = nil)
message = ["404"]
message << resource if resource
message << "Not Found"
render_api_error!(message.join(' '), 404)
end
def unauthorized!
render_api_error!('401 Unauthorized', 401)
end
def not_allowed!
render_api_error!('Method Not Allowed', 405)
end
def render_api_error!(message, status)
error!({'message' => message}, status)
end
private
def abilities
@abilities ||= begin
abilities = Six.new
abilities << Ability
abilities
end
end
end
end

View file

@ -60,7 +60,7 @@ module Gitlab
if @issue.save
present @issue, with: Entities::Issue
else
error!({'message' => '404 Not found'}, 404)
not_found!
end
end
@ -79,6 +79,8 @@ module Gitlab
# PUT /projects/:id/issues/:issue_id
put ":id/issues/:issue_id" do
@issue = user_project.issues.find(params[:issue_id])
authorize! :modify_issue, @issue
parameters = {
title: (params[:title] || @issue.title),
description: (params[:description] || @issue.description),
@ -91,7 +93,7 @@ module Gitlab
if @issue.update_attributes(parameters)
present @issue, with: Entities::Issue
else
error!({'message' => '404 Not found'}, 404)
not_found!
end
end
@ -103,7 +105,7 @@ module Gitlab
# Example Request:
# DELETE /projects/:id/issues/:issue_id
delete ":id/issues/:issue_id" do
error!({'message' => 'method not allowed'}, 405)
not_allowed!
end
end
end

View file

@ -45,7 +45,7 @@ module Gitlab
if @milestone.save
present @milestone, with: Entities::Milestone
else
error!({'message' => '404 Not found'}, 404)
not_found!
end
end
@ -61,6 +61,8 @@ module Gitlab
# Example Request:
# PUT /projects/:id/milestones/:milestone_id
put ":id/milestones/:milestone_id" do
authorize! :admin_milestone, user_project
@milestone = user_project.milestones.find(params[:milestone_id])
parameters = {
title: (params[:title] || @milestone.title),
@ -72,7 +74,7 @@ module Gitlab
if @milestone.update_attributes(parameters)
present @milestone, with: Entities::Milestone
else
error!({'message' => '404 Not found'}, 404)
not_found!
end
end
end

View file

@ -50,7 +50,7 @@ module Gitlab
if @project.saved?
present @project, with: Entities::Project
else
error!({'message' => '404 Not found'}, 404)
not_found!
end
end
@ -74,6 +74,7 @@ module Gitlab
# Example Request:
# POST /projects/:id/users
post ":id/users" do
authorize! :admin_project, user_project
user_project.add_users_ids_to_team(params[:user_ids].values, params[:project_access])
nil
end
@ -87,6 +88,7 @@ module Gitlab
# Example Request:
# PUT /projects/:id/add_users
put ":id/users" do
authorize! :admin_project, user_project
user_project.update_users_ids_to_role(params[:user_ids].values, params[:project_access])
nil
end
@ -99,6 +101,7 @@ module Gitlab
# Example Request:
# DELETE /projects/:id/users
delete ":id/users" do
authorize! :admin_project, user_project
user_project.delete_users_ids_from_team(params[:user_ids].values)
nil
end
@ -209,7 +212,7 @@ module Gitlab
if @snippet.save
present @snippet, with: Entities::ProjectSnippet
else
error!({'message' => '404 Not found'}, 404)
not_found!
end
end
@ -226,6 +229,8 @@ module Gitlab
# PUT /projects/:id/snippets/:snippet_id
put ":id/snippets/:snippet_id" do
@snippet = user_project.snippets.find(params[:snippet_id])
authorize! :modify_snippet, @snippet
parameters = {
title: (params[:title] || @snippet.title),
file_name: (params[:file_name] || @snippet.file_name),
@ -236,7 +241,7 @@ module Gitlab
if @snippet.update_attributes(parameters)
present @snippet, with: Entities::ProjectSnippet
else
error!({'message' => '404 Not found'}, 404)
not_found!
end
end
@ -249,6 +254,8 @@ module Gitlab
# DELETE /projects/:id/snippets/:snippet_id
delete ":id/snippets/:snippet_id" do
@snippet = user_project.snippets.find(params[:snippet_id])
authorize! :modify_snippet, @snippet
@snippet.destroy
end
@ -277,10 +284,10 @@ module Gitlab
ref = params[:sha]
commit = user_project.commit ref
error!('404 Commit Not Found', 404) unless commit
not_found! "Commit" unless commit
tree = Tree.new commit.tree, user_project, ref, params[:filepath]
error!('404 File Not Found', 404) unless tree.try(:tree)
not_found! "File" unless tree.try(:tree)
if tree.text?
encoding = Gitlab::Encode.detect_encoding(tree.data)

View file

@ -35,7 +35,7 @@ module Gitlab
end
def enable_automerge
config.admin_all_repo!(project)
config.admin_all_repo!
end
alias_method :create_repository, :update_repository

View file

@ -148,18 +148,7 @@ module Gitlab
# Enable access to all repos for gitolite admin.
# We use it for accept merge request feature
def admin_all_repo
owner_name = ""
# Read gitolite-admin user
#
begin
repo = conf.get_repo("gitolite-admin")
owner_name = repo.permissions[0]["RW+"][""][0]
raise StandardError if owner_name.blank?
rescue => ex
puts "Can't determine gitolite-admin owner".red
raise StandardError
end
owner_name = Gitlab.config.gitolite_admin_key
# @ALL repos premission for gitolite owner
repo_name = "@all"

View file

@ -21,8 +21,7 @@ module Gitlab
if output =~ /CONFLICT/
false
else
repo.git.push({}, "origin", merge_request.target_branch)
true
!!repo.git.push({}, "origin", merge_request.target_branch)
end
end
end

View file

@ -0,0 +1,15 @@
require 'spec_helper'
describe TreeHelper do
describe '#markup?' do
%w(mdown md markdown textile rdoc org creole mediawiki rst asciidoc pod).each do |type|
it "returns true for #{type} files" do
markup?("README.#{type}").should be_true
end
end
it "returns false when given a non-markup filename" do
markup?('README.rb').should_not be_true
end
end
end

View file

@ -49,4 +49,26 @@ describe Event do
it { @event.branch_name.should == "master" }
it { @event.author.should == @user }
end
describe "Joined project team" do
let(:project) {Factory.create :project}
let(:new_user) {Factory.create :user}
it "should create event" do
UsersProject.observers.enable :users_project_observer
expect{
UsersProject.bulk_import(project, [new_user.id], UsersProject::DEVELOPER)
}.to change{Event.count}.by(1)
end
end
describe "Left project team" do
let(:project) {Factory.create :project}
let(:new_user) {Factory.create :user}
it "should create event" do
UsersProject.bulk_import(project, [new_user.id], UsersProject::DEVELOPER)
UsersProject.observers.enable :users_project_observer
expect{
UsersProject.bulk_delete(project, [new_user.id])
}.to change{Event.count}.by(1)
end
end
end

View file

@ -23,6 +23,14 @@ describe UsersProjectObserver do
Notify.should_receive(:project_access_granted_email).with(users_project.id).and_return(double(deliver: true))
subject.after_create(users_project)
end
it "should create new event" do
Event.should_receive(:create).with(
project_id: users_project.project.id,
action: Event::Joined,
author_id: users_project.user.id
)
subject.after_create(users_project)
end
end
describe "#after_update" do
@ -37,4 +45,23 @@ describe UsersProjectObserver do
subject.after_update(users_project)
end
end
describe "#after_destroy" do
it "should called when UsersProject destroyed" do
subject.should_receive(:after_destroy)
UsersProject.observers.enable :users_project_observer do
UsersProject.bulk_delete(
users_project.project,
[users_project.user.id]
)
end
end
it "should create new event" do
Event.should_receive(:create).with(
project_id: users_project.project.id,
action: Event::Left,
author_id: users_project.user.id
)
subject.after_destroy(users_project)
end
end
end

View file

@ -86,7 +86,7 @@ describe Gitlab::API do
it "should return a 404 error if not found" do
get api("/projects/42", user)
response.status.should == 404
json_response['message'].should == '404 Not found'
json_response['message'].should == '404 Not Found'
end
end