Merge commit 'master' into discussions

Conflicts:
	app/assets/stylesheets/sections/notes.scss
	app/contexts/notes/load_context.rb
	app/models/project.rb
	app/observers/note_observer.rb
	app/roles/votes.rb
	app/views/commit/show.html.haml
	app/views/merge_requests/_show.html.haml
	app/views/merge_requests/diffs.js.haml
	app/views/merge_requests/show.js.haml
	app/views/notes/_note.html.haml
	features/steps/project/project_merge_requests.rb
	spec/models/note_spec.rb
This commit is contained in:
Riyad Preukschas 2013-01-15 00:52:25 +01:00
commit 3022786948
930 changed files with 80374 additions and 103682 deletions

2
.gitignore vendored
View File

@ -19,8 +19,10 @@ config/gitlab.yml
config/database.yml
config/initializers/omniauth.rb
config/unicorn.rb
config/resque.yml
db/data.yml
.idea
.DS_Store
.chef
vendor/bundle/*
rails_best_practices_output.html

4
.simplecov Normal file
View File

@ -0,0 +1,4 @@
# .simplecov
SimpleCov.start 'rails' do
merge_timeout 3600
end

View File

@ -3,16 +3,12 @@ env:
- DB=mysql
before_install:
- sudo apt-get install libicu-dev -y
- wget -P /tmp http://phantomjs.googlecode.com/files/phantomjs-1.7.0-linux-i686.tar.bz2
- tar -xf /tmp/phantomjs-1.7.0-linux-i686.tar.bz2 -C /tmp/
- sudo rm -rf /usr/local/phantomjs
- sudo mv /tmp/phantomjs-1.7.0-linux-i686 /usr/local/phantomjs
- gem install charlock_holmes -v="0.6.9"
branches:
only:
- 'master'
rvm:
- 1.9.3
- 1.9.2
services:
- mysql
- postgresql

View File

@ -1,4 +1,7 @@
v 4.0.0
- Remove project code and path from API. Use id instead
- Return valid clonable url to repo for web hook
- Fixed backup issue
- Reorganized settings
- Fixed commits compare
- Refactored scss

26
Gemfile
View File

@ -8,7 +8,7 @@ def linux_only(require_as)
RUBY_PLATFORM.include?('linux') && require_as
end
gem "rails", "3.2.9"
gem "rails", "3.2.11"
# Supported DBs
gem "mysql2", group: :mysql
@ -23,11 +23,15 @@ gem 'omniauth-github'
# GITLAB patched libs
gem "grit", git: "https://github.com/gitlabhq/grit.git", ref: '7f35cb98ff17d534a07e3ce6ec3d580f67402837'
gem "omniauth-ldap", git: "https://github.com/gitlabhq/omniauth-ldap.git", ref: 'f038dd852d7bd473a557e385d5d7c2fd5dc1dc2e'
gem 'yaml_db', git: "https://github.com/gitlabhq/yaml_db.git", ref: '98e9a5dca43e3fedd3268c76a73af40d1bdf1dfd'
gem 'grack', git: "https://github.com/gitlabhq/grack.git", ref: 'ba46f3b0845c6a09d488ae6abdce6ede37e227e8'
gem 'grit_ext', git: "https://github.com/gitlabhq/grit_ext.git", ref: '8e6afc2da821354774aa4d1ee8a1aa2082f84a3e'
# LDAP Auth
gem 'gitlab_omniauth-ldap', '1.0.2', require: "omniauth-ldap"
# Dump db to yml file. Mostly used to migrate from sqlite to mysql
gem 'gitlab_yaml_db', '1.0.0', require: "yaml_db"
# Gitolite client (for work with gitolite-admin repo)
gem "gitolite", '1.1.0'
@ -77,8 +81,9 @@ gem "acts-as-taggable-on", "2.3.3"
gem "draper", "~> 0.18.0"
# Background jobs
gem "resque", "~> 1.23.0"
gem 'resque_mailer'
gem 'slim'
gem 'sinatra', :require => nil
gem 'sidekiq', '2.6.4'
# HTTP requests
gem "httparty"
@ -104,7 +109,7 @@ group :assets do
gem "jquery-rails", "2.1.3"
gem "jquery-ui-rails", "2.0.2"
gem "modernizr", "2.6.2"
gem "raphael-rails", "1.5.2"
gem "raphael-rails", git: "https://github.com/gitlabhq/raphael-rails.git"
gem 'bootstrap-sass', "2.2.1.1"
gem "font-awesome-sass-rails", "~> 2.0.0"
gem "gemoji", "~> 1.2.1", require: 'emoji/railtie'
@ -115,6 +120,14 @@ group :development do
gem "letter_opener"
gem 'quiet_assets', '~> 1.0.1'
gem 'rack-mini-profiler'
# Better errors handler
gem 'better_errors'
gem 'binding_of_caller'
gem 'rails_best_practices'
# Docs generator
gem "sdoc"
end
group :development, :test do
@ -145,7 +158,6 @@ group :test do
gem "simplecov", require: false
gem "shoulda-matchers", "1.3.0"
gem 'email_spec'
gem 'resque_spec'
gem "webmock"
gem 'test_after_commit'
end

View File

@ -39,17 +39,6 @@ GIT
grit_ext (0.6.1)
charlock_holmes (~> 0.6.9)
GIT
remote: https://github.com/gitlabhq/omniauth-ldap.git
revision: f038dd852d7bd473a557e385d5d7c2fd5dc1dc2e
ref: f038dd852d7bd473a557e385d5d7c2fd5dc1dc2e
specs:
omniauth-ldap (1.0.2)
net-ldap (~> 0.2.2)
omniauth (~> 1.0)
pyu-ruby-sasl (~> 0.0.3.1)
rubyntlm (~> 0.1.1)
GIT
remote: https://github.com/gitlabhq/pygments.rb.git
revision: db1da0343adf86b49bdc3add04d02d2e80438d38
@ -60,11 +49,10 @@ GIT
yajl-ruby (~> 1.1.0)
GIT
remote: https://github.com/gitlabhq/yaml_db.git
revision: 98e9a5dca43e3fedd3268c76a73af40d1bdf1dfd
ref: 98e9a5dca43e3fedd3268c76a73af40d1bdf1dfd
remote: https://github.com/gitlabhq/raphael-rails.git
revision: cb2c92a040b9b941a5f1aa1ea866cc26e944fe58
specs:
yaml_db (0.2.2)
raphael-rails (2.1.0)
GIT
remote: https://github.com/jonleighton/poltergeist.git
@ -81,12 +69,12 @@ GIT
GEM
remote: http://rubygems.org/
specs:
actionmailer (3.2.9)
actionpack (= 3.2.9)
actionmailer (3.2.11)
actionpack (= 3.2.11)
mail (~> 2.4.4)
actionpack (3.2.9)
activemodel (= 3.2.9)
activesupport (= 3.2.9)
actionpack (3.2.11)
activemodel (= 3.2.11)
activesupport (= 3.2.11)
builder (~> 3.0.0)
erubis (~> 2.7.0)
journey (~> 1.0.4)
@ -94,18 +82,18 @@ GEM
rack-cache (~> 1.2)
rack-test (~> 0.6.1)
sprockets (~> 2.2.1)
activemodel (3.2.9)
activesupport (= 3.2.9)
activemodel (3.2.11)
activesupport (= 3.2.11)
builder (~> 3.0.0)
activerecord (3.2.9)
activemodel (= 3.2.9)
activesupport (= 3.2.9)
activerecord (3.2.11)
activemodel (= 3.2.11)
activesupport (= 3.2.11)
arel (~> 3.0.2)
tzinfo (~> 0.3.29)
activeresource (3.2.9)
activemodel (= 3.2.9)
activesupport (= 3.2.9)
activesupport (3.2.9)
activeresource (3.2.11)
activemodel (= 3.2.11)
activesupport (= 3.2.11)
activesupport (3.2.11)
i18n (~> 0.6)
multi_json (~> 1.0)
acts-as-taggable-on (2.3.3)
@ -115,6 +103,10 @@ GEM
awesome_print (1.1.0)
backports (2.6.5)
bcrypt-ruby (3.0.1)
better_errors (0.3.2)
coderay (>= 1.0.0)
erubis (>= 2.7.0)
binding_of_caller (0.6.8)
blankslate (3.1.2)
bootstrap-sass (2.2.1.1)
sass (~> 3.2)
@ -129,12 +121,17 @@ GEM
carrierwave (0.7.1)
activemodel (>= 3.2.0)
activesupport (>= 3.2.0)
celluloid (0.12.4)
facter (>= 1.6.12)
timers (>= 1.0.0)
charlock_holmes (0.6.9)
childprocess (0.3.6)
ffi (~> 1.0, >= 1.0.6)
chosen-rails (0.9.8)
railties (~> 3.0)
thor (~> 0.14)
code_analyzer (0.3.1)
sexp_processor
coderay (1.0.8)
coffee-rails (3.2.2)
coffee-script (>= 2.2.0)
@ -145,6 +142,7 @@ GEM
coffee-script-source (1.4.0)
colored (1.2)
colorize (0.5.8)
connection_pool (1.0.0)
crack (0.3.1)
daemons (1.1.9)
devise (2.1.2)
@ -164,6 +162,7 @@ GEM
eventmachine (1.0.0)
execjs (1.4.0)
multi_json (~> 1.0)
facter (1.6.17)
factory_girl (4.1.0)
activesupport (>= 3.0.0)
factory_girl_rails (4.1.0)
@ -190,6 +189,12 @@ GEM
pygments.rb (>= 0.2.13)
github-markup (0.7.4)
gitlab_meta (4.0)
gitlab_omniauth-ldap (1.0.2)
net-ldap (~> 0.2.2)
omniauth (~> 1.0)
pyu-ruby-sasl (~> 0.0.3.1)
rubyntlm (~> 0.1.1)
gitlab_yaml_db (1.0.0)
gitolite (1.1.0)
gratr19 (~> 0.4.4.1)
grit (~> 2.5.0)
@ -240,7 +245,7 @@ GEM
jquery-ui-rails (2.0.2)
jquery-rails
railties (>= 3.1.0)
json (1.7.5)
json (1.7.6)
jwt (0.1.5)
multi_json (>= 1.0)
kaminari (0.14.1)
@ -264,7 +269,7 @@ GEM
mime-types (1.19)
modernizr (2.6.2)
sprockets (~> 2.0)
multi_json (1.3.7)
multi_json (1.5.0)
multi_xml (0.5.1)
multipart-post (1.1.5)
mysql2 (0.3.11)
@ -299,6 +304,7 @@ GEM
pg (0.14.1)
polyglot (0.3.3)
posix-spawn (0.3.6)
progressbar (0.12.0)
pry (0.9.10)
coderay (~> 1.0.5)
method_source (~> 0.8)
@ -306,7 +312,7 @@ GEM
pyu-ruby-sasl (0.0.3.3)
quiet_assets (1.0.1)
railties (~> 3.1)
rack (1.4.1)
rack (1.4.3)
rack-accept (0.4.5)
rack (>= 0.4)
rack-cache (1.2)
@ -315,33 +321,40 @@ GEM
rack (>= 1.1.3)
rack-mount (0.8.3)
rack (>= 1.0.0)
rack-protection (1.2.0)
rack-protection (1.3.2)
rack
rack-ssl (1.3.2)
rack
rack-test (0.6.2)
rack (>= 1.0)
rails (3.2.9)
actionmailer (= 3.2.9)
actionpack (= 3.2.9)
activerecord (= 3.2.9)
activeresource (= 3.2.9)
activesupport (= 3.2.9)
rails (3.2.11)
actionmailer (= 3.2.11)
actionpack (= 3.2.11)
activerecord (= 3.2.11)
activeresource (= 3.2.11)
activesupport (= 3.2.11)
bundler (~> 1.0)
railties (= 3.2.9)
railties (= 3.2.11)
rails-dev-tweaks (0.6.1)
actionpack (~> 3.1)
railties (~> 3.1)
railties (3.2.9)
actionpack (= 3.2.9)
activesupport (= 3.2.9)
rails_best_practices (1.13.2)
activesupport
awesome_print
code_analyzer
colored
erubis
i18n
progressbar
railties (3.2.11)
actionpack (= 3.2.11)
activesupport (= 3.2.11)
rack-ssl (~> 1.3.2)
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0)
raindrops (0.10.0)
rake (10.0.1)
raphael-rails (1.5.2)
rake (10.0.3)
rb-fsevent (0.9.2)
rb-inotify (0.8.8)
ffi (>= 0.5.0)
@ -351,16 +364,6 @@ GEM
redis (3.0.2)
redis-namespace (1.2.1)
redis (~> 3.0.0)
resque (1.23.0)
multi_json (~> 1.0)
redis-namespace (~> 1.0)
sinatra (>= 0.9.2)
vegas (~> 0.1.2)
resque_mailer (2.1.0)
actionmailer (~> 3.0)
resque_spec (0.12.5)
resque (>= 1.19.0)
rspec (>= 2.5.0)
rspec (2.12.0)
rspec-core (~> 2.12.0)
rspec-expectations (~> 2.12.0)
@ -383,6 +386,9 @@ GEM
railties (~> 3.2.0)
sass (>= 3.1.10)
tilt (~> 1.3)
sdoc (0.3.20)
json (>= 1.1.3)
rdoc (~> 3.10)
seed-fu (2.2.0)
activerecord (~> 3.1)
activesupport (~> 3.1)
@ -392,8 +398,15 @@ GEM
multi_json (~> 1.0)
rubyzip
settingslogic (2.0.8)
sexp_processor (4.1.3)
shoulda-matchers (1.3.0)
activesupport (>= 3.0.0)
sidekiq (2.6.4)
celluloid (~> 0.12.0)
connection_pool (~> 1.0)
multi_json (~> 1)
redis (~> 3)
redis-namespace
simplecov (0.7.1)
multi_json (~> 1.0)
simplecov-html (~> 0.7.1)
@ -403,6 +416,9 @@ GEM
rack-protection (~> 1.2)
tilt (~> 1.3, >= 1.3.3)
six (0.2.0)
slim (1.3.6)
temple (~> 0.5.5)
tilt (~> 1.3.3)
slop (3.3.3)
spinach (0.5.2)
colorize
@ -411,12 +427,13 @@ GEM
capybara (~> 1)
railties (>= 3)
spinach (>= 0.4)
sprockets (2.2.1)
sprockets (2.2.2)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
stamp (0.3.0)
temple (0.5.5)
test_after_commit (0.0.1)
therubyracer (0.10.2)
libv8 (~> 3.3.10)
@ -426,6 +443,7 @@ GEM
rack (>= 1.0.0)
thor (0.16.0)
tilt (1.3.3)
timers (1.0.2)
treetop (1.4.12)
polyglot
polyglot (>= 0.3.1)
@ -437,8 +455,6 @@ GEM
kgio (~> 2.6)
rack
raindrops (~> 0.7)
vegas (0.1.11)
rack (>= 1.0.0)
virtus (0.5.2)
backports (~> 2.6.1)
warden (1.2.1)
@ -458,6 +474,8 @@ DEPENDENCIES
acts-as-taggable-on (= 2.3.3)
annotate!
awesome_print
better_errors
binding_of_caller
bootstrap-sass (= 2.2.1.1)
capybara
carrierwave (~> 0.7.1)
@ -477,6 +495,8 @@ DEPENDENCIES
github-linguist (~> 2.3.4)
github-markup (~> 0.7.4)
gitlab_meta (= 4.0)
gitlab_omniauth-ldap (= 1.0.2)
gitlab_yaml_db (= 1.0.0)
gitolite (= 1.1.0)
grack!
grape (~> 0.2.1)
@ -498,7 +518,6 @@ DEPENDENCIES
omniauth (~> 1.1.1)
omniauth-github
omniauth-google-oauth2
omniauth-ldap!
omniauth-twitter
pg
poltergeist!
@ -506,22 +525,24 @@ DEPENDENCIES
pygments.rb!
quiet_assets (~> 1.0.1)
rack-mini-profiler
rails (= 3.2.9)
rails (= 3.2.11)
rails-dev-tweaks
raphael-rails (= 1.5.2)
rails_best_practices
raphael-rails!
rb-fsevent
rb-inotify
redcarpet (~> 2.2.2)
resque (~> 1.23.0)
resque_mailer
resque_spec
rspec-rails
sass-rails (~> 3.2.5)
sdoc
seed-fu
settingslogic
shoulda-matchers (= 1.3.0)
sidekiq (= 2.6.4)
simplecov
sinatra
six
slim
spinach-rails
stamp
test_after_commit
@ -530,4 +551,3 @@ DEPENDENCIES
uglifier (~> 1.3.0)
unicorn (~> 4.4.0)
webmock
yaml_db!

View File

@ -1,2 +1,2 @@
web: bundle exec rails s -p $PORT
worker: bundle exec rake environment resque:work QUEUE=* VVERBOSE=1
worker: bundle exec sidekiq -q post_receive,mailer,system_hook,common,default

View File

@ -2,6 +2,7 @@
GitLab is a free project and repository management application
![CI](http://ci.gitlab.org/projects/1/status?ref=master)
## Application details
@ -39,6 +40,6 @@ Email
## Contribute
[Development Tips](https://github.com/gitlabhq/gitlabhq/blob/master/doc/development.md)
[Developer Guide](https://github.com/gitlabhq/gitlabhq/wiki/Developer-Guide)
Want to help - send a pull request.
We'll accept good pull requests.

View File

@ -1 +1 @@
4.0.0rc1
4.1.0pre

92
app/assets/fonts/OFL.txt Normal file
View File

@ -0,0 +1,92 @@
Copyright (c) 2010, Jan Gerner (post@yanone.de)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 674 B

View File

@ -0,0 +1,30 @@
$ ->
dashboardPage()
dashboardPage = ->
Pager.init 20, true
$(".event_filter_link").bind "click", (event) ->
event.preventDefault()
toggleFilter $(this)
reloadActivities()
reloadActivities = ->
$(".content_list").html ''
Pager.init 20, true
toggleFilter = (sender) ->
sender.parent().toggleClass "inactive"
event_filters = $.cookie("event_filter")
filter = sender.attr("id").split("_")[0]
if event_filters
event_filters = event_filters.split(",")
else
event_filters = new Array()
index = event_filters.indexOf(filter)
if index is -1
event_filters.push filter
else
event_filters.splice index, 1
$.cookie "event_filter", event_filters.join(",")

View File

@ -11,7 +11,7 @@ function initIssuesSearch() {
last_terms = terms;
if (terms.length >= 2 || terms.length == 0) {
$.get(href, { 'f': status, 'terms': terms, 'milestone_id': milestone_id }, function(response) {
$.get(href, { 'status': status, 'terms': terms, 'milestone_id': milestone_id }, function(response) {
$('#issues-table').html(response);
});
}

View File

@ -1,132 +0,0 @@
var MergeRequest = {
diffs_loaded: false,
commits_loaded: false,
opts: false,
init:
function(opts) {
var self = this;
self.opts = opts;
self.initTabs();
self.initMergeWidget();
$(".mr_show_all_commits").bind("click", function() {
self.showAllCommits();
});
},
initMergeWidget:
function() {
var self = this;
self.showState(self.opts.current_state);
if($(".automerge_widget").length && self.opts.check_enable){
$.get(self.opts.url_to_automerge_check, function(data){
self.showState(data.state);
}, "json");
}
if(self.opts.ci_enable){
$.get(self.opts.url_to_ci_check, function(data){
self.showCiState(data.status);
}, "json");
}
},
initTabs:
function() {
$(".mr_nav_tabs a").live("click", function() {
$(".mr_nav_tabs a").parent().removeClass("active");
$(this).parent().addClass("active");
});
var current_tab;
if(this.opts.action == "diffs") {
current_tab = $(".mr_nav_tabs .merge-diffs-tab");
} else {
current_tab = $(".mr_nav_tabs .merge-notes-tab");
}
current_tab.parent().addClass("active");
this.initNotesTab();
this.initDiffTab();
},
initNotesTab:
function() {
$(".mr_nav_tabs a.merge-notes-tab").live("click", function(e) {
$(".merge-request-diffs").hide();
$(".merge_request_notes").show();
var mr_path = $(".merge-notes-tab").attr("data-url");
history.pushState({ path: mr_path }, '', mr_path);
e.preventDefault();
});
},
initDiffTab:
function() {
$(".mr_nav_tabs a.merge-diffs-tab").live("click", function(e) {
if(!MergeRequest.diffs_loaded) {
MergeRequest.loadDiff();
}
$(".merge_request_notes").hide();
$(".merge-request-diffs").show();
var mr_diff_path = $(".merge-diffs-tab").attr("data-url");
history.pushState({ path: mr_diff_path }, '', mr_diff_path);
e.preventDefault();
});
},
showState:
function(state){
$(".automerge_widget").hide();
$(".automerge_widget." + state).show();
},
showCiState:
function(state){
$(".ci_widget").hide();
$(".ci_widget.ci-" + state).show();
},
loadDiff:
function() {
$(".dashboard-loader").show();
$.ajax({
type: "GET",
url: $(".merge-diffs-tab").attr("data-url"),
beforeSend: function(){ $('.status').addClass("loading")},
complete: function(){
MergeRequest.diffs_loaded = true;
$(".merge_request_notes").hide();
$('.status').removeClass("loading");
},
dataType: "script"});
},
showAllCommits:
function() {
$(".first_mr_commits").remove();
$(".all_mr_commits").removeClass("hide");
},
already_cannot_be_merged:
function(){
$(".automerge_widget").hide();
$(".merge_in_progress").hide();
$(".automerge_widget.already_cannot_be_merged").show();
}
};
/*
* Filter merge requests
*/
function merge_requestsPage() {
$("#assignee_id").chosen();
$("#milestone_id").chosen();
$("#milestone_id, #assignee_id").on("change", function(){
$(this).closest("form").submit();
});
}

View File

@ -0,0 +1,97 @@
#
# * Filter merge requests
#
@merge_requestsPage = ->
$('#assignee_id').chosen()
$('#milestone_id').chosen()
$('#milestone_id, #assignee_id').on 'change', ->
$(this).closest('form').submit()
class MergeRequest
constructor: (@opts) ->
this.$el = $('.merge-request')
@diffs_loaded = false
@commits_loaded = false
this.activateTab(@opts.action)
this.bindEvents()
this.initMergeWidget()
this.$('.show-all-commits').on 'click', =>
this.showAllCommits()
# Local jQuery finder
$: (selector) ->
this.$el.find(selector)
initMergeWidget: ->
this.showState( @opts.current_state )
if this.$('.automerge_widget').length and @opts.check_enable
$.get @opts.url_to_automerge_check, (data) =>
this.showState( data.state )
, 'json'
if @opts.ci_enable
$.get @opts.url_to_ci_check, (data) =>
this.showCiState data.status
, 'json'
bindEvents: ->
this.$('.nav-tabs').on 'click', 'a', (event) =>
a = $(event.currentTarget)
href = a.attr('href')
History.replaceState {path: href}, document.title, href
event.preventDefault()
this.$('.nav-tabs').on 'click', 'li', (event) =>
this.activateTab($(event.currentTarget).data('action'))
activateTab: (action) ->
this.$('.nav-tabs li').removeClass 'active'
this.$('.tab-content').hide()
switch action
when 'diffs'
this.$('.nav-tabs .diffs-tab').addClass 'active'
this.loadDiff() unless @diffs_loaded
this.$('.diffs').show()
else
this.$('.nav-tabs .notes-tab').addClass 'active'
this.$('.notes').show()
showState: (state) ->
$('.automerge_widget').hide()
$('.automerge_widget.' + state).show()
showCiState: (state) ->
$('.ci_widget').hide()
$('.ci_widget.ci-' + state).show()
loadDiff: (event) ->
$('.dashboard-loader').show()
$.ajax
type: 'GET'
url: this.$('.nav-tabs .diffs-tab a').attr('href')
beforeSend: =>
this.$('.status').addClass 'loading'
complete: =>
@diffs_loaded = true
this.$('.status').removeClass 'loading'
dataType: 'script'
showAllCommits: ->
this.$('.first-commits').remove()
this.$('.all-commits').removeClass 'hide'
alreadyOrCannotBeMerged: ->
this.$('.automerge_widget').hide()
this.$('.merge-in-progress').hide()
this.$('.automerge_widget.already_cannot_be_merged').show()
this.MergeRequest = MergeRequest

View File

@ -1,14 +1,14 @@
$ ->
$('.milestone-issue-filter tr[data-closed]').addClass('hide')
$('.milestone-issue-filter li[data-closed]').addClass('hide')
$('.milestone-issue-filter ul.nav li a').click ->
$('.milestone-issue-filter li').toggleClass('active')
$('.milestone-issue-filter tr[data-closed]').toggleClass('hide')
$('.milestone-issue-filter li[data-closed]').toggleClass('hide')
false
$('.milestone-merge-requests-filter tr[data-closed]').addClass('hide')
$('.milestone-merge-requests-filter li[data-closed]').addClass('hide')
$('.milestone-merge-requests-filter ul.nav li a').click ->
$('.milestone-merge-requests-filter li').toggleClass('active')
$('.milestone-merge-requests-filter tr[data-closed]').toggleClass('hide')
$('.milestone-merge-requests-filter li[data-closed]').toggleClass('hide')
false

View File

@ -4,9 +4,16 @@ var Pager = {
disable:false,
init:
function(limit) {
function(limit, preload) {
this.limit=limit;
this.offset=limit;
if(preload) {
this.offset = 0;
this.getOld();
} else {
this.offset = limit;
}
this.initLoadMore();
},

View File

@ -117,34 +117,10 @@ span.update-author {
}
.label {
background-color: #474D57;
&.label-tag {
background: none;
border: none;
padding: 4px 6px;
color: #444;
text-shadow: 0 0 1px #fff;
&.grouped {
float: left;
margin-right: 6px;
padding: 6px;
}
}
&.label-issue {
background-color: #eee;
border: 1px solid #ccc;
padding: 4px 6px;
color: #444;
text-shadow: 0 0 1px #fff;
&.grouped {
float: left;
margin-right: 6px;
padding: 6px;
}
}
padding: 0px 4px;
font-size: 10px;
font-style: normal;
background-color: $link_color;
&.label-success {
background-color: #8D8;
@ -425,7 +401,7 @@ li.note {
.supp_diff_link,
.mr_show_all_commits {
.show-all-commits {
cursor: pointer;
}

View File

@ -1,23 +1,24 @@
/**
* ===================================
* Contain 3 main UI block elements:
* .main_box - for show pages
* .ui-box - for simple block & widgets
* Contain UI block elements:
* .ui-box - for any block & widgets
* ===================================
*/
/**
* UI box element
* contains top, middle, bottom blocks
* UI Block
*
*/
.main_box {
@extend .borders;
@extend .prepend-top-20;
@extend .append-bottom-20;
border-width: 1px;
.ui-box {
background: #F9F9F9;
margin-bottom: 25px;
border: 1px solid #CCC;
@include solid-shade;
&.ui-box-show {
margin:20px 0;
background: #FFF;
}
img { max-width: 100%; }
@ -27,9 +28,9 @@
}
}
.top_box_content,
.middle_box_content,
.bottom_box_content {
.ui-box-head,
.ui-box-body,
.ui-box-bottom {
padding: 15px;
word-wrap: break-word;
@ -39,19 +40,25 @@
border: none;
padding: 0;
}
.clearfix {
margin: 0;
}
}
.top_box_content {
.ui-box-head {
.box-title {
color: $style_color;
font-size: 18px;
font-weight: normal;
line-height: 28px;
}
h3 {
margin: 0;
}
}
.middle_box_content {
@include border-radius(0);
.ui-box-body {
border: none;
font-size: 12px;
background-color: #f5f5f5;
@ -59,24 +66,9 @@
border-top: 1px solid #eee;
}
.bottom_box_content {
.ui-box-bottom {
border-top: 1px solid #eee;
}
}
/**
* Big UI Block for show page content
*
*/
.ui-box {
background: #F9F9F9;
margin-bottom: 25px;
border: 1px solid #eaeaea;
@include border-radius(4px);
border-color: #CCC;
@include solid-shade;
&.white {
background: #fff;
@ -86,47 +78,47 @@
margin: 0;
}
h5, .title {
padding: 0 10px;
@include border-radius(4px 4px 0 0);
.title {
@include bg-gray-gradient;
border-top: 1px solid #eaeaea;
border-bottom: 1px solid #bbb;
border-bottom: 1px solid #CCC;
color: #456;
font-size: 16px;
text-shadow: 0 1px 1px #fff;
padding: 0px 10px;
line-height: 36px;
font-size: 14px;
font-weight: normal;
> a {
text-shadow: 0 1px 1px #fff;
}
&.small {
line-height: 28px;
font-size: 14px;
line-height: 28px;
text-shadow: 0 1px 1px white;
}
form {
padding: 9px 0;
margin: 0px;
margin-bottom: 0;
margin-top: 3px;
}
.nav-pills {
li {
padding: 3px 0;
&.active a { background-color: $style_color; }
a {
@include border-radius(7px);
> li {
> a {
padding: 13px;
margin: 0;
font-size: 13px;
}
&.active {
> a {
background: #D5D5D5;
color: $style_color;
@include border-radius(0);
border-radius: 0;
border-left: 1px solid #CCC;
border-right: 1px solid #CCC;
}
}
}
}
}
.bottom {
@include bg-gray-gradient;
@include border-radius(0 0 4px 4px);
border-bottom: none;
border-top: 1px solid #bbb;
}
&.padded {
h5, .title {
margin: -20px;
@ -143,6 +135,7 @@
color: #777;
}
}
.row_title {
font-weight: bold;
color: #444;
@ -151,8 +144,4 @@
text-decoration: underline;
}
}
.ui-box-body {
padding: 10px;
}
}

View File

@ -7,6 +7,10 @@
color: #333;
}
&.btn-white {
background: #FFF;
}
&.primary {
background: #2a79A3;
@include linear-gradient(#47A7b7, #2585b5);

View File

@ -17,20 +17,43 @@
.padded { padding:20px }
.ipadded { padding:20px!important }
.lborder { border-left:1px solid #eee }
.no-padding { padding:0 !important; }
.underlined { border-bottom: 1px solid #CCC; }
.no-borders { border: none; }
.vlink { color: $link_color !important; }
.underlined_link { text-decoration: underline; }
.borders { border: 1px solid #ccc; @include shade; }
.hint { font-style: italic; color: #999; }
.light { color: #888 }
.tiny { font-weight: normal }
/** PILLS & TABS**/
.nav-pills a:hover { background-color: #888; }
.nav-pills .active a { background-color: $style_color; }
.nav-pills {
.active a {
background: $primary_color;
}
> li > a {
@include border-radius(0);
}
&.nav-stacked {
> li > a {
border-left: 4px solid #EEE;
padding: 12px;
}
> .active > a {
border-color: #29B;
border-radius: 0;
background: #F1F1F1;
color: $style_color;
font-weight: bold;
}
}
}
.nav-pills > .active > a > i[class^="icon-"] { background: inherit; }
/**
* nav-tabs
*
*/
.nav-tabs > li > a, .nav-pills > li > a { color: $style_color; }
.nav.nav-tabs {
li {

View File

@ -1,7 +1,7 @@
@font-face{
font-family: Korolev;
src: font-url('korolev-medium-compressed.otf');
@font-face{
font-family: Yanone;
src: font-url('YanoneKaffeesatz-Light.ttf');
}
/** Typo **/
$monospace: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace;
$monospace: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace;

View File

@ -23,14 +23,12 @@
border-bottom: 1px solid #ADF;
}
&:first-child {
@include border-radius(4px 4px 0 0);
border-top: none;
}
&:last-child {
@include border-radius(0 0 4px 4px);
border: none;
border-bottom: none;
&.bottom {
background: #f5f5f5;
}
}
.author { color: #999; }

View File

@ -62,8 +62,8 @@
@mixin header-font {
color: $style_color;
text-shadow: 0 1px 1px #FFF;
font-family: 'Korolev', sans-serif;
font-size: 28px;
line-height: 48px;
font-family: 'Yanone', sans-serif;
font-size: 26px;
line-height: 42px;
font-weight: normal;
}

View File

@ -25,7 +25,7 @@ table {
}
th, td {
padding: 8px;
padding: 10px;
line-height: 18px;
text-align: left;
}

View File

@ -1,74 +1,17 @@
.commit-box {
@extend .main_box;
.commit-head {
@extend .top_box_content;
.commit-title {
line-height: 26px;
margin: 0;
}
.commit-description {
font-size: 14px;
border: none;
background-color: white;
padding-top: 10px;
}
.browse-button {
@extend .btn;
@extend .btn-small;
float: right;
}
}
.commit-info {
@extend .middle_box_content;
@extend .clearfix;
.sha-block {
text-align: right;
&:first-child {
padding-bottom: 6px;
}
a {
border-bottom: 1px solid #aaa;
margin-left: 9px;
}
}
&.merge-commit .sha-block {
clear: right;
}
.committer {
padding-left: 32px;
}
.author a,
.committer a {
font-size: 14px;
line-height: 22px;
text-shadow: 0 1px 1px #fff;
color: #777;
&:hover {
color: #999;
}
}
.avatar {
margin-right: 10px;
}
}
}
/**
*
* COMMIT SHOw
*
*/
.commit-committer-link,
.commit-author-link {
font-size: 13px;
color: #555;
&:hover {
color: #999;
}
}
.diff_file {
border: 1px solid #CCC;
margin-bottom: 1em;
@ -258,13 +201,6 @@
min-width: 65px;
font-family: $monospace;
}
.commit-author-name {
color: #777;
&:hover {
color: #999;
}
}
}
.diff_file_header a,

View File

@ -47,6 +47,12 @@
.event-info {
color: #666;
}
.event-note {
padding-top: 5px;
padding-left: 5px;
display: inline-block;
color: #777;
}
}
.avatar {
position: relative;

View File

@ -33,22 +33,29 @@ header {
*
*/
.app_logo {
width: 170px;
float: left;
margin-right: 15px;
position: relative;
top: -5px;
padding-top: 5px;
a {
float: left;
padding: 0px;
margin: 0 10px;
h1 {
width: 90px;
background: url('logo_dark.png') no-repeat 0px 2px;
float: left;
margin-left: 2px;
padding-left: 45px;
height: 40px;
width: 40px;
@include header-font;
text-indent: -9999px;
}
}
&:hover {
background-color: #EEE;
}
}
/**
@ -60,7 +67,7 @@ header {
position: relative;
float: left;
margin: 0;
margin-right: 30px;
margin-left: 15px;
@include header-font;
}
@ -233,7 +240,7 @@ header {
.app_logo {
a {
h1 {
background: url('logo_white.png') no-repeat 0px 2px;
background: url('logo_white.png') no-repeat center center;
color: #fff;
text-shadow: 0 1px 1px #111;
}
@ -244,5 +251,23 @@ header {
text-shadow: 0 1px 1px #111;
}
}
.app_logo {
.separator {
margin-left: 0;
margin-right: 0;
}
}
.separator {
float: left;
height: 60px;
width: 1px;
background: white;
border-left: 1px solid #DDD;
margin-top: -10px;
margin-left: 10px;
margin-right: 10px;
}
}

View File

@ -1,34 +1,6 @@
.issue_form_box {
@extend .main_box;
.issue_title {
@extend .top_box_content;
.clearfix {
margin-bottom: 0px;
input {
@extend .span8;
}
}
}
.issue_middle_block {
@extend .middle_box_content;
height: 30px;
.issue_assignee {
@extend .span6;
float: left;
}
.issue_milestone {
@extend .span4;
float: left;
}
}
.issue_description {
@extend .bottom_box_content;
}
}
.issues_table {
.issue {
padding: 7px 10px;
padding: 10px;
.issue_check {
float: left;
@ -82,38 +54,34 @@ input.check_all_issues {
}
}
@media (min-width: 800px) { .issues_filters select { width: 160px; } }
@media (min-width: 1000px) { .issues_filters select { width: 200px; } }
@media (min-width: 800px) { .issues_filters select { width: 160px; } }
@media (min-width: 1200px) { .issues_filters select { width: 220px; } }
@media (min-width: 800px) { .issues_bulk_update select { width: 120px; } }
@media (min-width: 1200px) { .issues_bulk_update select { width: 160px; } }
#issues-table-holder {
.issues_filters {
form {
padding: 0;
margin: 0;
margin-top:7px
}
}
.issues_bulk_update {
margin: 0;
form {
padding: 0;
margin: 0;
margin-top:7px
float:left;
}
.update_selected_issues {
position: relative;
top:-2px;
top:5px;
margin-left: 4px;
float: left;
}
.update_issues_text {
padding: 3px;
line-height: 18px;
line-height: 28px;
float: left;
color: #479;
}
}
}

View File

@ -1,17 +1,3 @@
/**
* MR form
*
*/
.mr_branch_box {
@extend .ui-box;
margin-bottom: 20px;
.body {
background: #f1f1f1;
}
}
/**
* MR -> show: Automerge widget
@ -47,6 +33,7 @@
}
label {
color: #444;
text-align: left
}
}
@ -56,7 +43,7 @@
}
}
.mr_nav_tabs {
.merge-request .nav-tabs{
li {
a {
font-weight: bold;
@ -67,7 +54,7 @@
}
li.merge_request {
padding: 7px 10px;
padding: 10px;
img.avatar {
width: 32px;
margin-top: 1px;
@ -78,7 +65,7 @@ li.merge_request {
}
}
.merge_in_progress {
.merge-in-progress {
@extend .padded;
@extend .append-bottom-10;
}
@ -120,19 +107,3 @@ li.merge_request {
.mr_direction_tip {
margin-top:40px
}
.merge_requests_form_box {
@extend .main_box;
.merge_requests_middle_box {
@extend .middle_box_content;
height: 30px;
.merge_requests_assignee {
@extend .span6;
float: left;
}
.merge_requests_milestone {
@extend .span4;
float: left;
}
}
}

View File

@ -294,6 +294,8 @@ ul.notes {
padding: 4px 6px;
}
.note_text {
border: 1px solid #DDD;
box-shadow: none;
font-size: 14px;
height: 80px;
width: 98.6%;

View File

@ -8,16 +8,12 @@
.groups_box,
.projects_box {
> h5 {
color: $style_color;
font-size: 16px;
text-shadow: 0 1px 1px #fff;
padding: 2px 10px;
line-height: 32px;
font-size: 14px;
> .title {
padding: 2px 15px;
}
.nav-projects-tabs li { padding: 0; }
.well-list {
li { padding: 15px; }
.arrow {
float: right;
padding: 10px;
@ -109,7 +105,7 @@ ul.nav.nav-projects-tabs {
li {
a {
padding: 4px 20px;
padding: 6px 25px;
margin-top: 2px;
border-color: #DDD;
background-color: #EEE;

View File

@ -4,21 +4,8 @@
*
*/
.ui_basic {
.app_logo {
.separator {
margin-left: 0;
margin-right: 0;
}
}
.separator {
float: left;
height: 60px;
width: 1px;
background: white;
border-left: 1px solid #DDD;
margin-top: -10px;
margin-left: 10px;
margin-right: 10px;
}
}

View File

@ -17,6 +17,15 @@
&.navbar-gitlab {
.navbar-inner {
background: #657;
.app_logo {
&:hover {
background-color: #6A5A7A;
}
}
.separator {
background: #546;
border-left: 1px solid #706080;
}
}
}
}

View File

@ -17,6 +17,15 @@
&.navbar-gitlab {
.navbar-inner {
background: #708090;
.app_logo {
&:hover {
background-color: #6A7A8A;
}
}
.separator {
background: #607080;
border-left: 1px solid #8090A0;
}
}
}
}

View File

@ -46,21 +46,26 @@
.app_logo {
a {
h1 {
background: url('logo_white.png') no-repeat 0px 2px;
background: url('logo_white.png') no-repeat center center;
color: #eee;
text-shadow: 0 1px 1px #111;
}
}
.separator {
display: none;
&:hover {
background-color: #41464e;
}
}
.project_name {
color: #eee;
text-shadow: 0 1px 1px #111;
}
}
.separator {
background: #31363E;
border-left: 1px solid #666;
}
/*
* End of Application Header
*

View File

@ -17,6 +17,15 @@
&.navbar-gitlab {
.navbar-inner {
background: #567;
.app_logo {
&:hover {
background-color: #516171;
}
}
.separator {
background: #456;
border-left: 1px solid #678;
}
}
}
}

View File

@ -9,16 +9,16 @@ class CommitLoadContext < BaseContext
status: :ok
}
commit = project.commit(params[:id])
commit = project.repository.commit(params[:id])
if commit
commit = CommitDecorator.decorate(commit)
line_notes = project.commit_line_notes(commit)
line_notes = project.notes.for_commit_id(commit.id).inline
result[:commit] = commit
result[:note] = project.build_commit_note(commit)
result[:line_notes] = line_notes
result[:notes_count] = line_notes.count + project.commit_notes(commit).count
result[:notes_count] = project.notes.for_commit_id(commit.id).count
begin
result[:suppress_diff] = true if commit.diffs.size > Commit::DIFF_SAFE_SIZE && !params[:force_show_diff]

View File

@ -0,0 +1,31 @@
class FilterContext
attr_accessor :items, :params
def initialize(items, params)
@items = items
@params = params
end
def execute
apply_filter(items)
end
def apply_filter items
if params[:project_id]
items = items.where(project_id: params[:project_id])
end
if params[:search].present?
items = items.search(params[:search])
end
case params[:status]
when 'closed'
items.closed
when 'all'
items
else
items.opened
end
end
end

View File

@ -4,7 +4,7 @@ class IssuesListContext < BaseContext
attr_accessor :issues
def execute
@issues = case params[:f]
@issues = case params[:status]
when issues_filter[:all] then @project.issues
when issues_filter[:closed] then @project.issues.closed
when issues_filter[:to_me] then @project.issues.opened.assigned(current_user)

View File

@ -9,7 +9,7 @@ module Notes
@notes = case target_type
when "commit"
project.commit_notes(project.commit(target_id)).fresh
project.notes.for_commit_id(target_id).not_inline.fresh
when "issue"
project.issues.find(target_id).notes.inc_author.fresh
when "merge_request"
@ -18,7 +18,7 @@ module Notes
project.snippets.find(target_id).notes.fresh
when "wall"
# this is the only case, where the order is DESC
project.common_notes.order("created_at DESC, id DESC").limit(50)
project.notes.common.inc_author_project.order("created_at DESC, id DESC").limit(50)
end
@notes = if after_id

View File

@ -1,7 +1,7 @@
class TestHookContext < BaseContext
def execute
hook = project.hooks.find(params[:id])
commits = project.commits(project.default_branch, nil, 3)
commits = project.repository.commits(project.default_branch, nil, 3)
data = project.post_receive_data(commits.last.id, commits.first.id, "refs/heads/#{project.default_branch}", current_user)
hook.execute(data)
end

View File

@ -3,10 +3,6 @@ class Admin::DashboardController < AdminController
@projects = Project.order("created_at DESC").limit(10)
@users = User.order("created_at DESC").limit(10)
@resque_accessible = true
@workers = Resque.workers
@pending_jobs = Resque.size(:post_receive)
rescue Redis::InheritedError
@resque_accessible = false
end

View File

@ -1,5 +1,5 @@
class Admin::GroupsController < AdminController
before_filter :group, only: [:edit, :show, :update, :destroy, :project_update]
before_filter :group, only: [:edit, :show, :update, :destroy, :project_update, :project_teams_update]
def index
@groups = Group.order('name ASC')
@ -12,6 +12,8 @@ class Admin::GroupsController < AdminController
@projects = @projects.not_in_group(@group) if @group.projects.present?
@projects = @projects.all
@projects.reject!(&:empty_repo?)
@users = User.active
end
def new
@ -65,7 +67,14 @@ class Admin::GroupsController < AdminController
redirect_to :back, notice: 'Group was successfully updated.'
end
def project_teams_update
@group.add_users_to_project_teams(params[:user_ids], params[:project_access])
redirect_to [:admin, @group], notice: 'Users was successfully added.'
end
def destroy
@group.truncate_teams
@group.destroy
redirect_to admin_groups_path, notice: 'Group was successfully deleted.'

View File

@ -10,6 +10,7 @@ class Admin::ProjectsController < AdminController
end
def show
@repository = @project.repository
@users = User.active
@users = @users.not_in_project(@project) if @project.users.present?
@users = @users.all
@ -19,7 +20,7 @@ class Admin::ProjectsController < AdminController
end
def team_update
@project.add_users_ids_to_team(params[:user_ids], params[:project_access])
@project.team.add_users_ids(params[:user_ids], params[:project_access])
redirect_to [:admin, @project], notice: 'Project was successfully updated.'
end
@ -35,6 +36,9 @@ class Admin::ProjectsController < AdminController
end
def destroy
# Delete team first in order to prevent multiple gitolite calls
@project.team.truncate
@project.destroy
redirect_to admin_projects_path, notice: 'Project was successfully deleted.'

View File

@ -3,13 +3,13 @@ class Admin::UsersController < AdminController
@admin_users = User.scoped
@admin_users = @admin_users.filter(params[:filter])
@admin_users = @admin_users.search(params[:name]) if params[:name].present?
@admin_users = @admin_users.order("name ASC").page(params[:page])
@admin_users = @admin_users.alphabetically.page(params[:page])
end
def show
@admin_user = User.find(params[:id])
@projects = if @admin_user.projects.empty?
@projects = if @admin_user.authorized_projects.empty?
Project
else
Project.without_user(@admin_user)
@ -19,9 +19,9 @@ class Admin::UsersController < AdminController
def team_update
@admin_user = User.find(params[:id])
UsersProject.user_bulk_import(
@admin_user,
UsersProject.add_users_into_projects(
params[:project_ids],
[@admin_user.id],
params[:project_access]
)
@ -98,7 +98,7 @@ class Admin::UsersController < AdminController
def destroy
@admin_user = User.find(params[:id])
if @admin_user.my_own_projects.count > 0
if @admin_user.personal_projects.count > 0
redirect_to admin_users_path, alert: "User is a project owner and can't be removed." and return
end
@admin_user.destroy

View File

@ -76,6 +76,12 @@ class ApplicationController < ActionController::Base
end
end
def repository
@repository ||= project.repository
rescue Grit::NoSuchPathError
nil
end
def add_abilities
abilities << Ability
end

View File

@ -9,10 +9,10 @@ class CommitsController < ProjectResourceController
before_filter :require_non_empty_project
def show
@repo = @project.repo
@repo = @project.repository
@limit, @offset = (params[:limit] || 40), (params[:offset] || 0)
@commits = @project.commits(@ref, @path, @limit, @offset)
@commits = @repo.commits(@ref, @path, @limit, @offset)
@commits = CommitDecorator.decorate(@commits)
respond_to do |format|

View File

@ -20,7 +20,7 @@ class DashboardController < ApplicationController
@projects = @projects.page(params[:page]).per(30)
@events = Event.in_projects(current_user.project_ids)
@events = Event.in_projects(current_user.authorized_projects.pluck(:id))
@events = @event_filter.apply_filter(@events)
@events = @events.limit(20).offset(params[:offset] || 0)
@ -36,14 +36,14 @@ class DashboardController < ApplicationController
# Get authored or assigned open merge requests
def merge_requests
@merge_requests = current_user.cared_merge_requests
@merge_requests = dashboard_filter(@merge_requests)
@merge_requests = FilterContext.new(@merge_requests, params).execute
@merge_requests = @merge_requests.recent.page(params[:page]).per(20)
end
# Get only assigned issues
def issues
@issues = current_user.assigned_issues
@issues = dashboard_filter(@issues)
@issues = FilterContext.new(@issues, params).execute
@issues = @issues.recent.page(params[:page]).per(20)
@issues = @issues.includes(:author, :project)
@ -60,25 +60,7 @@ class DashboardController < ApplicationController
end
def event_filter
@event_filter ||= EventFilter.new(params[:event_filter])
end
def dashboard_filter items
if params[:project_id]
items = items.where(project_id: params[:project_id])
end
if params[:search].present?
items = items.search(params[:search])
end
case params[:status]
when 'closed'
items.closed
when 'all'
items
else
items.opened
end
filters = cookies['event_filter'].split(',') if cookies['event_filter']
@event_filter ||= EventFilter.new(filters)
end
end

View File

@ -21,15 +21,16 @@ class GroupsController < ApplicationController
# Get authored or assigned open merge requests
def merge_requests
@merge_requests = current_user.cared_merge_requests.opened
@merge_requests = @merge_requests.of_group(@group).recent.page(params[:page]).per(20)
@merge_requests = current_user.cared_merge_requests.of_group(@group)
@merge_requests = FilterContext.new(@merge_requests, params).execute
@merge_requests = @merge_requests.recent.page(params[:page]).per(20)
end
# Get only assigned issues
def issues
@user = current_user
@issues = current_user.assigned_issues.opened
@issues = @issues.of_group(@group).recent.page(params[:page]).per(20)
@issues = current_user.assigned_issues.of_group(@group)
@issues = FilterContext.new(@issues, params).execute
@issues = @issues.recent.page(params[:page]).per(20)
@issues = @issues.includes(:author, :project)
respond_to do |format|
@ -44,6 +45,7 @@ class GroupsController < ApplicationController
@projects = result[:projects]
@merge_requests = result[:merge_requests]
@issues = result[:issues]
@wiki_pages = result[:wiki_pages]
end
def people
@ -53,9 +55,16 @@ class GroupsController < ApplicationController
if @project
@team_member = @project.users_projects.new
else
@team_member = UsersProject.new
end
end
def team_members
@group.add_users_to_project_teams(params[:user_ids], params[:project_access])
redirect_to people_group_path(@group), notice: 'Users was successfully added.'
end
protected
def group
@ -63,7 +72,7 @@ class GroupsController < ApplicationController
end
def projects
@projects ||= group.projects.authorized_for(current_user).sorted_by_activity
@projects ||= current_user.authorized_projects.where(namespace_id: group.id).sorted_by_activity
end
def project_ids

View File

@ -74,6 +74,8 @@ class MergeRequestsController < ProjectResourceController
@merge_request.check_if_can_be_merged
end
render json: {state: @merge_request.human_state}
rescue Gitlab::SatelliteNotExistError
render json: {state: :no_satellite}
end
def automerge
@ -88,12 +90,12 @@ class MergeRequestsController < ProjectResourceController
end
def branch_from
@commit = project.commit(params[:ref])
@commit = @repository.commit(params[:ref])
@commit = CommitDecorator.decorate(@commit)
end
def branch_to
@commit = project.commit(params[:ref])
@commit = @repository.commit(params[:ref])
@commit = CommitDecorator.decorate(@commit)
end

View File

@ -1,3 +1,4 @@
class ProjectResourceController < ApplicationController
before_filter :project
before_filter :repository
end

View File

@ -2,6 +2,7 @@ require Rails.root.join('lib', 'gitlab', 'graph', 'json_builder')
class ProjectsController < ProjectResourceController
skip_before_filter :project, only: [:new, :create]
skip_before_filter :repository, only: [:new, :create]
# Authorize
before_filter :authorize_read_project!, except: [:index, :new, :create]
@ -58,7 +59,7 @@ class ProjectsController < ProjectResourceController
respond_to do |format|
format.html do
unless @project.empty_repo?
if @project.repository && !@project.repository.empty?
@last_push = current_user.recent_push(@project.id)
render :show
else
@ -102,11 +103,10 @@ class ProjectsController < ProjectResourceController
def destroy
return access_denied! unless can?(current_user, :remove_project, project)
# Disable the UsersProject update_repository call, otherwise it will be
# called once for every person removed from the project
UsersProject.skip_callback(:destroy, :after, :update_repository)
# Delete team first in order to prevent multiple gitolite calls
project.team.truncate
project.destroy
UsersProject.set_callback(:destroy, :after, :update_repository)
respond_to do |format|
format.html { redirect_to root_path }

View File

@ -12,7 +12,7 @@ class RefsController < ProjectResourceController
respond_to do |format|
format.html do
new_path = if params[:destination] == "tree"
project_tree_path(@project, @ref)
project_tree_path(@project, (@ref + "/" + params[:path]))
else
project_commits_path(@project, @ref)
end
@ -31,7 +31,7 @@ class RefsController < ProjectResourceController
contents = @tree.contents
@logs = contents.map do |content|
file = params[:path] ? File.join(params[:path], content.name) : content.name
last_commit = @project.commits(@commit.id, file, 1).last
last_commit = @repo.commits(@commit.id, file, 1).last
last_commit = CommitDecorator.decorate(last_commit)
{
file_name: content.name,
@ -45,10 +45,10 @@ class RefsController < ProjectResourceController
def define_tree_vars
params[:path] = nil if params[:path].blank?
@repo = project.repo
@commit = project.commit(@ref)
@repo = project.repository
@commit = @repo.commit(@ref)
@commit = CommitDecorator.decorate(@commit)
@tree = Tree.new(@commit.tree, project, @ref, params[:path])
@tree = Tree.new(@commit.tree, @ref, params[:path])
@tree = TreeDecorator.new(@tree)
@hex_path = Digest::SHA1.hexdigest(params[:path] || "")

View File

@ -5,19 +5,19 @@ class RepositoriesController < ProjectResourceController
before_filter :require_non_empty_project
def show
@activities = @project.commits_with_refs(20)
@activities = @repository.commits_with_refs(20)
end
def branches
@branches = @project.branches
@branches = @repository.branches
end
def tags
@tags = @project.tags
@tags = @repository.tags
end
def stats
@stats = Gitlab::GitStats.new(@project.repo, @project.root_ref)
@stats = Gitlab::GitStats.new(@repository.raw, @repository.root_ref)
@graph = @stats.graph
end
@ -27,7 +27,7 @@ class RepositoriesController < ProjectResourceController
end
file_path = @project.archive_repo(params[:ref])
file_path = @repository.archive_repo(params[:ref])
if file_path
# Send file to user

View File

@ -1,6 +1,6 @@
class SearchController < ApplicationController
def show
result = SearchContext.new(current_user.project_ids, params).execute
result = SearchContext.new(current_user.authorized_projects.map(&:id), params).execute
@projects = result[:projects]
@merge_requests = result[:merge_requests]

View File

@ -26,7 +26,7 @@ class ServicesController < ProjectResourceController
end
def test
commits = project.commits(project.default_branch, nil, 3)
commits = project.repository.commits(project.default_branch, nil, 3)
data = project.post_receive_data(commits.last.id, commits.first.id, "refs/heads/#{project.default_branch}", current_user)
@service = project.gitlab_ci_service

View File

@ -16,7 +16,7 @@ class SnippetsController < ProjectResourceController
respond_to :html
def index
@snippets = @project.snippets.fresh
@snippets = @project.snippets.fresh.non_expired
end
def new

View File

@ -16,10 +16,9 @@ class TeamMembersController < ProjectResourceController
end
def create
@project.add_users_ids_to_team(
params[:user_ids],
params[:project_access]
)
users = User.where(id: params[:user_ids])
@project.team << [users, params[:project_access]]
if params[:redirect_to]
redirect_to params[:redirect_to]
@ -50,7 +49,7 @@ class TeamMembersController < ProjectResourceController
def apply_import
giver = Project.find(params[:source_project_id])
status = UsersProject.import_team(giver, project)
status = @project.team.import(giver)
notice = status ? "Succesfully imported" : "Import failed"
redirect_to project_team_members_path(project), notice: notice

View File

@ -22,7 +22,7 @@ class TreeController < ProjectResourceController
end
def edit
@last_commit = @project.last_commit_for(@ref, @path).sha
@last_commit = @project.repository.last_commit_for(@ref, @path).sha
end
def update

View File

@ -6,16 +6,14 @@ class TreeDecorator < ApplicationDecorator
part_path = ""
parts = path.split("\/")
#parts = parts[0...-1] if is_blob?
yield(h.link_to("..", "#")) if parts.count > max_links
yield('..', nil) if parts.count > max_links
parts.each do |part|
part_path = File.join(part_path, part) unless part_path.empty?
part_path = part if part_path.empty?
next unless parts.last(2).include?(part) if parts.count > max_links
yield(h.link_to(h.truncate(part, length: 40), h.project_tree_path(project, h.tree_join(ref, part_path))))
yield(part, h.tree_join(ref, part_path))
end
end
end
@ -26,7 +24,7 @@ class TreeDecorator < ApplicationDecorator
def up_dir_path
file = File.join(path, "..")
h.project_tree_path(project, h.tree_join(ref, file))
h.tree_join(ref, file)
end
def readme

View File

@ -53,7 +53,7 @@ module ApplicationHelper
def last_commit(project)
if project.repo_exists?
time_ago_in_words(project.commit.committed_date) + " ago"
time_ago_in_words(project.repository.commit.committed_date) + " ago"
else
"Never"
end
@ -62,9 +62,11 @@ module ApplicationHelper
end
def grouped_options_refs(destination = :tree)
repository = @project.repository
options = [
["Branch", @project.branch_names ],
[ "Tag", @project.tag_names ]
["Branch", repository.branch_names ],
[ "Tag", repository.tag_names ]
]
# If reference is commit id -
@ -78,7 +80,8 @@ module ApplicationHelper
end
def search_autocomplete_source
projects = current_user.projects.map{ |p| { label: p.name_with_namespace, url: project_path(p) } }
projects = current_user.authorized_projects.map { |p| { label: p.name_with_namespace, url: project_path(p) } }
groups = current_user.authorized_groups.map { |group| { label: "<group> #{group.name}", url: group_path(group) } }
default_nav = [
{ label: "My Profile", url: profile_path },
@ -99,21 +102,21 @@ module ApplicationHelper
]
project_nav = []
if @project && !@project.new_record?
if @project && @project.repository && @project.repository.root_ref
project_nav = [
{ label: "#{@project.name} Issues", url: project_issues_path(@project) },
{ label: "#{@project.name} Commits", url: project_commits_path(@project, @ref || @project.root_ref) },
{ label: "#{@project.name} Commits", url: project_commits_path(@project, @ref || @project.repository.root_ref) },
{ label: "#{@project.name} Merge Requests", url: project_merge_requests_path(@project) },
{ label: "#{@project.name} Milestones", url: project_milestones_path(@project) },
{ label: "#{@project.name} Snippets", url: project_snippets_path(@project) },
{ label: "#{@project.name} Team", url: project_team_index_path(@project) },
{ label: "#{@project.name} Tree", url: project_tree_path(@project, @ref || @project.root_ref) },
{ label: "#{@project.name} Tree", url: project_tree_path(@project, @ref || @project.repository.root_ref) },
{ label: "#{@project.name} Wall", url: wall_project_path(@project) },
{ label: "#{@project.name} Wiki", url: project_wikis_path(@project) },
]
end
[projects, default_nav, project_nav, help_nav].flatten.to_json
[groups, projects, default_nav, project_nav, help_nav].flatten.to_json
end
def emoji_autocomplete_source
@ -139,6 +142,7 @@ module ApplicationHelper
event.last_push_to_non_root? &&
!event.rm_ref? &&
event.project &&
event.project.repository &&
event.project.merge_requests_enabled
end

View File

@ -70,4 +70,12 @@ module CommitsHelper
escape_javascript(render 'commits/commit', commit: commit)
end
end
def diff_line_content(line)
if line.blank?
" &nbsp;"
else
line
end
end
end

View File

@ -20,25 +20,8 @@ module EventsHelper
[event.action_name, target].join(" ")
end
def event_image event
event_image_path = if event.push?
"event_push.png"
elsif event.merged?
"event_mr_merged.png"
end
return nil unless event_image_path
content_tag :div, class: 'event_icon' do
image_tag event_image_path
end
end
def event_filter_link key, tooltip
key = key.to_s
filter = @event_filter.options key
inactive = if @event_filter.active? key
nil
else
@ -46,7 +29,7 @@ module EventsHelper
end
content_tag :div, class: "filter_icon #{inactive}" do
link_to dashboard_path(event_filter: filter), class: 'has_tooltip', 'data-original-title' => tooltip do
link_to dashboard_path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => tooltip do
image_tag "event_filter_#{key}.png"
end
end

View File

@ -0,0 +1,17 @@
module GroupsHelper
def group_filter_path(entity, options={})
exist_opts = {
status: params[:status],
project_id: params[:project_id],
}
options = exist_opts.merge(options)
case entity
when 'issue' then
issues_group_path(@group, options)
when 'merge_request'
merge_requests_group_path(@group, options)
end
end
end

View File

@ -4,7 +4,7 @@ module MergeRequestsHelper
event.project,
merge_request: {
source_branch: event.branch_name,
target_branch: event.project.root_ref,
target_branch: event.project.repository.root_ref,
title: event.branch_name.titleize
}
)

View File

@ -1,6 +1,6 @@
module NamespacesHelper
def namespaces_options(selected = :current_user, scope = :default)
groups = current_user.namespaces.select {|n| n.type == 'Group'}
groups = current_user.owned_groups.select {|n| n.type == 'Group'}
users = if scope == :all
Namespace.root

View File

@ -1,11 +1,12 @@
class Notify < ActionMailer::Base
include Resque::Mailer
add_template_helper ApplicationHelper
add_template_helper GitlabMarkdownHelper
default_url_options[:host] = Gitlab.config.gitlab.host
default_url_options[:protocol] = Gitlab.config.gitlab.protocol
default_url_options[:port] = Gitlab.config.gitlab.port if Gitlab.config.gitlab_on_non_standard_port?
default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root
default from: Gitlab.config.gitlab.email_from
@ -87,7 +88,7 @@ class Notify < ActionMailer::Base
def note_wall_email(recipient_id, note_id)
@note = Note.find(note_id)
@project = @note.project
mail(to: recipient(recipient_id), subject: subject)
mail(to: recipient(recipient_id), subject: subject("note on wall"))
end
@ -147,12 +148,15 @@ class Notify < ActionMailer::Base
# >> @project = Project.last
# => #<Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
# >> subject('Lorem ipsum')
# => "GitLab | Lorem ipsum | Ruby on Rails"
# => "GitLab | Ruby on Rails | Lorem ipsum "
#
# # Accepts multiple arguments
# >> subject('Lorem ipsum', 'Dolor sit amet')
# => "GitLab | Lorem ipsum | Dolor sit amet"
def subject(*extra)
"GitLab | " << extra.join(' | ') << (@project ? " | #{@project.name}" : "")
subject = "GitLab"
subject << (@project ? " | #{@project.name_with_namespace}" : "")
subject << " | " + extra.join(' | ') if extra.present?
subject
end
end

View File

@ -15,35 +15,26 @@ class Ability
def project_abilities(user, project)
rules = []
team = project.team
# Rules based on role in project
if project.master_access_for?(user)
if team.masters.include?(user)
rules << project_master_rules
elsif project.dev_access_for?(user)
elsif team.developers.include?(user)
rules << project_dev_rules
elsif project.report_access_for?(user)
elsif team.reporters.include?(user)
rules << project_report_rules
elsif project.guest_access_for?(user)
elsif team.guests.include?(user)
rules << project_guest_rules
end
if project.namespace
# If user own project namespace
# (Ex. group owner or account owner)
if project.namespace.owner == user
rules << project_admin_rules
end
else
# For compatibility with global projects
# use projects.owner_id
if project.owner == user
rules << project_admin_rules
end
if project.owner == user
rules << project_admin_rules
end
rules.flatten
end
@ -107,9 +98,12 @@ class Ability
def group_abilities user, group
rules = []
rules << [
:manage_group
] if group.owner == user
# Only group owner and administrators can manage group
if group.owner == user || user.admin?
rules << [
:manage_group
]
end
rules.flatten
end

View File

@ -11,7 +11,7 @@ class Commit
attr_accessor :commit, :head, :refs
delegate :message, :authored_date, :committed_date, :parents, :sha,
:date, :committer, :author, :message, :diffs, :tree, :id,
:date, :committer, :author, :diffs, :tree, :id, :stats,
:to_patch, to: :commit
class << self
@ -83,8 +83,8 @@ class Commit
return result unless from && to
first = project.commit(to.try(:strip))
last = project.commit(from.try(:strip))
first = project.repository.commit(to.try(:strip))
last = project.repository.commit(from.try(:strip))
if first && last
result[:same] = (first.id == last.id)
@ -98,6 +98,8 @@ class Commit
end
def initialize(raw_commit, head = nil)
raise "Nil as raw commit passed" unless raw_commit
@commit = raw_commit
@head = head
end
@ -136,17 +138,17 @@ class Commit
end
def prev_commit
parents.try :first
@prev_commit ||= if parents.present?
Commit.new(parents.first)
else
nil
end
end
def prev_commit_id
prev_commit.try :id
end
def parents_count
parents && parents.count || 0
end
# Shows the diff between the commit's parent and the commit.
#
# Cuts out the header and stats from #to_patch and returns only the diff.

View File

@ -1,5 +1,10 @@
# == Issuable concern
#
# Contains common functionality shared between Issues and MergeRequests
module IssueCommonality
#
# Used by Issue, MergeRequest
#
module Issuable
extend ActiveSupport::Concern
included do
@ -64,4 +69,38 @@ module IssueCommonality
closed_changed? && !closed
end
#
# Votes
#
# Return the number of -1 comments (downvotes)
def downvotes
notes.select(&:downvote?).size
end
def downvotes_in_percent
if votes_count.zero?
0
else
100.0 - upvotes_in_percent
end
end
# Return the number of +1 comments (upvotes)
def upvotes
notes.select(&:upvote?).size
end
def upvotes_in_percent
if votes_count.zero?
0
else
100.0 / votes_count * upvotes
end
end
# Return the total number of votes
def votes_count
upvotes + downvotes
end
end

View File

@ -15,9 +15,6 @@
#
class Event < ActiveRecord::Base
include NoteEvent
include PushEvent
attr_accessible :project, :action, :data, :author_id, :project_id,
:target_id, :target_type
@ -113,26 +110,6 @@ class Event < ActiveRecord::Base
target_type == "MergeRequest"
end
def new_issue?
target_type == "Issue" &&
action == Created
end
def new_merge_request?
target_type == "MergeRequest" &&
action == Created
end
def changed_merge_request?
target_type == "MergeRequest" &&
[Closed, Reopened].include?(action)
end
def changed_issue?
target_type == "Issue" &&
[Closed, Reopened].include?(action)
end
def joined?
action == Joined
end
@ -170,4 +147,143 @@ class Event < ActiveRecord::Base
"opened"
end
end
def valid_push?
data[:ref]
rescue => ex
false
end
def tag?
data[:ref]["refs/tags"]
end
def branch?
data[:ref]["refs/heads"]
end
def new_branch?
commit_from =~ /^00000/
end
def new_ref?
commit_from =~ /^00000/
end
def rm_ref?
commit_to =~ /^00000/
end
def md_ref?
!(rm_ref? || new_ref?)
end
def commit_from
data[:before]
end
def commit_to
data[:after]
end
def ref_name
if tag?
tag_name
else
branch_name
end
end
def branch_name
@branch_name ||= data[:ref].gsub("refs/heads/", "")
end
def tag_name
@tag_name ||= data[:ref].gsub("refs/tags/", "")
end
# Max 20 commits from push DESC
def commits
@commits ||= data[:commits].map { |commit| repository.commit(commit[:id]) }.reverse
end
def commits_count
data[:total_commits_count] || commits.count || 0
end
def ref_type
tag? ? "tag" : "branch"
end
def push_action_name
if new_ref?
"pushed new"
elsif rm_ref?
"deleted"
else
"pushed to"
end
end
def repository
project.repository
end
def parent_commit
repository.commit(commit_from)
rescue => ex
nil
end
def last_commit
repository.commit(commit_to)
rescue => ex
nil
end
def push_with_commits?
md_ref? && commits.any? && parent_commit && last_commit
rescue Grit::NoSuchPathError
false
end
def last_push_to_non_root?
branch? && project.default_branch != branch_name
end
def note_commit_id
target.commit_id
end
def note_short_commit_id
note_commit_id[0..8]
end
def note_commit?
target.noteable_type == "Commit"
end
def note_target
target.noteable
end
def note_target_id
if note_commit?
target.commit_id
else
target.noteable_id.to_s
end
end
def wall_note?
target.noteable_type.blank?
end
def note_target_type
if target.noteable_type.present?
target.noteable_type.titleize
else
"Wall"
end.downcase
end
end

View File

@ -23,20 +23,12 @@ class GitlabCiService < Service
after_save :compose_service_hook, if: :activated?
def activated?
active
end
def compose_service_hook
hook = service_hook || build_service_hook
hook.url = [project_url, "/build", "?token=#{token}"].join("")
hook.save
end
def commit_badge_path sha
project_url + "/status?sha=#{sha}"
end
def commit_status_path sha
project_url + "/builds/#{sha}/status.json?token=#{token}"
end

View File

@ -12,6 +12,14 @@
#
class Group < Namespace
def add_users_to_project_teams(user_ids, project_access)
UsersProject.add_users_into_projects(
projects.map(&:id),
user_ids,
project_access
)
end
def users
users = User.joins(:users_projects).where(users_projects: {project_id: project_ids})
users = users << owner
@ -21,4 +29,8 @@ class Group < Namespace
def human_name
name
end
def truncate_teams
UsersProject.truncate_teams(project_ids)
end
end

View File

@ -17,8 +17,7 @@
#
class Issue < ActiveRecord::Base
include IssueCommonality
include Votes
include Issuable
attr_accessible :title, :assignee_id, :closed, :position, :description,
:milestone_id, :label_list, :author_id_of_changes

View File

@ -73,7 +73,7 @@ class Key < ActiveRecord::Base
if is_deploy_key
[project]
else
user.projects
user.authorized_projects
end
end

View File

@ -20,11 +20,10 @@
#
require Rails.root.join("app/models/commit")
require Rails.root.join("app/roles/static_model")
require Rails.root.join("lib/static_model")
class MergeRequest < ActiveRecord::Base
include IssueCommonality
include Votes
include Issuable
attr_accessible :title, :assignee_id, :closed, :target_branch, :source_branch, :milestone_id,
:author_id_of_changes

View File

@ -29,7 +29,7 @@ class Milestone < ActiveRecord::Base
def expired?
if due_date
due_date < Date.today
due_date.past?
else
false
end
@ -58,7 +58,13 @@ class Milestone < ActiveRecord::Base
end
def expires_at
"expires at #{due_date.stamp("Aug 21, 2011")}" if due_date
if due_date
if due_date.past?
"expired at #{due_date.stamp("Aug 21, 2011")}"
else
"expires at #{due_date.stamp("Aug 21, 2011")}"
end
end
end
def can_be_closed?

View File

@ -27,10 +27,13 @@ class Namespace < ActiveRecord::Base
after_create :ensure_dir_exist
after_update :move_dir
after_commit :update_gitolite, on: :update, if: :require_update_gitolite
after_destroy :rm_dir
scope :root, where('type IS NULL')
attr_accessor :require_update_gitolite
def self.search query
where("name LIKE :query OR path LIKE :query", query: "%#{query}%")
end
@ -48,8 +51,17 @@ class Namespace < ActiveRecord::Base
end
def ensure_dir_exist
namespace_dir_path = File.join(Gitlab.config.gitolite.repos_path, path)
system("mkdir -m 770 #{namespace_dir_path}") unless File.exists?(namespace_dir_path)
unless dir_exists?
FileUtils.mkdir( namespace_full_path, mode: 0770 )
end
end
def dir_exists?
File.exists?(namespace_full_path)
end
def namespace_full_path
@namespace_full_path ||= File.join(Gitlab.config.gitolite.repos_path, path)
end
def move_dir
@ -59,16 +71,25 @@ class Namespace < ActiveRecord::Base
if File.exists?(new_path)
raise "Already exists"
end
if system("mv #{old_path} #{new_path}")
begin
FileUtils.mv( old_path, new_path )
send_update_instructions
@require_update_gitolite = true
rescue Exception => e
raise "Namespace move error #{old_path} #{new_path}"
end
end
end
def update_gitolite
@require_update_gitolite = false
projects.each(&:update_repository)
end
def rm_dir
dir_path = File.join(Gitlab.config.gitolite.repos_path, path)
system("rm -rf #{dir_path}")
FileUtils.rm_r( dir_path, force: true )
end
def send_update_instructions

View File

@ -4,7 +4,6 @@
#
# id :integer not null, primary key
# note :text
# noteable_id :string(255)
# noteable_type :string(255)
# author_id :integer
# created_at :datetime not null
@ -12,6 +11,8 @@
# project_id :integer
# attachment :string(255)
# line_code :string(255)
# commit_id :string(255)
# noteable_id :integer
#
require 'carrierwave/orm/activerecord'
@ -41,11 +42,11 @@ class Note < ActiveRecord::Base
mount_uploader :attachment, AttachmentUploader
# Scopes
scope :for_commits, ->{ where(noteable_type: "Commit") }
scope :common, ->{ where(noteable_id: nil, commit_id: nil) }
scope :today, ->{ where("created_at >= :date", date: Date.today) }
scope :last_week, ->{ where("created_at >= :date", date: (Date.today - 7.days)) }
scope :since, ->(day) { where("created_at >= :date", date: (day)) }
scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
scope :inline, where("line_code IS NOT NULL")
scope :not_inline, where("line_code IS NULL")
scope :common, ->{ where(noteable_type: ["", nil]) }
scope :fresh, ->{ order("created_at ASC, id ASC") }
scope :inc_author_project, ->{ includes(:project, :author) }
scope :inc_author, ->{ includes(:author) }
@ -126,7 +127,7 @@ class Note < ActiveRecord::Base
# override to return commits, which are not active record
def noteable
if for_commit?
project.commit(commit_id)
project.repository.commit(commit_id)
else
super
end

View File

@ -9,7 +9,7 @@
# created_at :datetime not null
# updated_at :datetime not null
# private_flag :boolean default(TRUE), not null
# owner_id :integer
# creator_id :integer
# default_branch :string(255)
# issues_enabled :boolean default(TRUE), not null
# wall_enabled :boolean default(TRUE), not null
@ -21,18 +21,14 @@
require "grit"
class Project < ActiveRecord::Base
include Repository
include PushObserver
include Authority
include Team
include NamespacedProject
include Gitolited
class TransferError < StandardError; end
attr_accessible :name, :path, :description, :default_branch, :issues_enabled,
:wall_enabled, :merge_requests_enabled, :wiki_enabled, as: [:default, :admin]
attr_accessible :namespace_id, :owner_id, as: :admin
attr_accessible :namespace_id, :creator_id, as: :admin
attr_accessor :error_code
@ -40,10 +36,10 @@ class Project < ActiveRecord::Base
belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'"
belongs_to :namespace
# TODO: replace owner with creator.
# With namespaces a project owner will be a namespace owner
# so this field makes sense only for global projects
belongs_to :owner, class_name: "User"
belongs_to :creator,
class_name: "User",
foreign_key: "creator_id"
has_many :users, through: :users_projects
has_many :events, dependent: :destroy
has_many :merge_requests, dependent: :destroy
@ -62,9 +58,11 @@ class Project < ActiveRecord::Base
delegate :name, to: :owner, allow_nil: true, prefix: true
# Validations
validates :owner, presence: true
validates :creator, presence: true
validates :description, length: { within: 0..2000 }
validates :name, presence: true, length: { within: 0..255 }
validates :name, presence: true, length: { within: 0..255 },
format: { with: Gitlab::Regex.project_name_regex,
message: "only letters, digits, spaces & '_' '-' '.' allowed. Letter should be first" }
validates :path, presence: true, length: { within: 0..255 },
format: { with: Gitlab::Regex.path_regex,
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
@ -77,19 +75,14 @@ class Project < ActiveRecord::Base
validate :check_limit, :repo_name
# Scopes
scope :public_only, where(private_flag: false)
scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.projects.map(&:id) ) }
scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) }
scope :not_in_group, ->(group) { where("id NOT IN (:ids)", ids: group.project_ids ) }
scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) }
scope :sorted_by_activity, ->() { order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC") }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) }
class << self
def authorized_for user
projects = includes(:users_projects, :namespace)
projects = projects.where("users_projects.user_id = :user_id or projects.owner_id = :user_id or namespaces.owner_id = :user_id", user_id: user.id)
end
def active
joins(:issues, :notes, :merge_requests).order("issues.created_at, notes.created_at, merge_requests.created_at DESC")
end
@ -101,8 +94,10 @@ class Project < ActiveRecord::Base
def find_with_namespace(id)
if id.include?("/")
id = id.split("/")
namespace_id = Namespace.find_by_path(id.first).id
where(namespace_id: namespace_id).find_by_path(id.last)
namespace = Namespace.find_by_path(id.first)
return nil unless namespace
where(namespace_id: namespace.id).find_by_path(id.second)
else
where(path: id, namespace_id: nil).last
end
@ -122,7 +117,7 @@ class Project < ActiveRecord::Base
#
project.path = project.name.dup.parameterize
project.owner = user
project.creator = user
# Apply namespace if user has access to it
# else fallback to user namespace
@ -162,6 +157,20 @@ class Project < ActiveRecord::Base
end
end
def team
@team ||= Team.new(self)
end
def repository
if path
@repository ||= Repository.new(path_with_namespace, default_branch)
else
nil
end
rescue Grit::NoSuchPathError
nil
end
def git_error?
error_code == :gitolite
end
@ -171,8 +180,8 @@ class Project < ActiveRecord::Base
end
def check_limit
unless owner.can_create_project?
errors[:base] << ("Your own projects limit is #{owner.projects_limit}! Please contact administrator to increase it")
unless creator.can_create_project?
errors[:base] << ("Your own projects limit is #{creator.projects_limit}! Please contact administrator to increase it")
end
rescue
errors[:base] << ("Can't check your ability to create project")
@ -198,30 +207,10 @@ class Project < ActiveRecord::Base
[Gitlab.config.gitlab.url, path_with_namespace].join("/")
end
def common_notes
notes.where(noteable_type: ["", nil]).inc_author_project
end
def build_commit_note(commit)
notes.new(commit_id: commit.id, noteable_type: "Commit")
end
def commit_notes(commit)
notes.where(commit_id: commit.id, noteable_type: "Commit").where('line_code IS NULL OR line_code = ""')
end
def commit_line_notes(commit)
notes.where(commit_id: commit.id, noteable_type: "Commit").where("line_code IS NOT NULL")
end
def public?
!private_flag
end
def private?
private_flag
end
def last_activity
last_event
end
@ -262,7 +251,282 @@ class Project < ActiveRecord::Base
def send_move_instructions
self.users_projects.each do |member|
Notify.project_was_moved_email(member.id).deliver
Notify.delay.project_was_moved_email(member.id)
end
end
def owner
if namespace
namespace_owner
else
creator
end
end
def team_member_by_name_or_email(name = nil, email = nil)
user = users.where("name like ? or email like ?", name, email).first
users_projects.where(user: user) if user
end
# Get Team Member record by user id
def team_member_by_id(user_id)
users_projects.find_by_user_id(user_id)
end
def transfer(new_namespace)
Project.transaction do
old_namespace = namespace
self.namespace = new_namespace
old_dir = old_namespace.try(:path) || ''
new_dir = new_namespace.try(:path) || ''
old_repo = if old_dir.present?
File.join(old_dir, self.path)
else
self.path
end
if Project.where(path: self.path, namespace_id: new_namespace.try(:id)).present?
raise TransferError.new("Project with same path in target namespace already exists")
end
Gitlab::ProjectMover.new(self, old_dir, new_dir).execute
gitolite.move_repository(old_repo, self)
save!
end
rescue Gitlab::ProjectMover::ProjectMoveError => ex
raise Project::TransferError.new(ex.message)
end
def name_with_namespace
@name_with_namespace ||= begin
if namespace
namespace.human_name + " / " + name
else
name
end
end
end
def namespace_owner
namespace.try(:owner)
end
def path_with_namespace
if namespace
namespace.path + '/' + path
else
path
end
end
# This method will be called after each post receive and only if the provided
# user is present in GitLab.
#
# All callbacks for post receive should be placed here.
def trigger_post_receive(oldrev, newrev, ref, user)
data = post_receive_data(oldrev, newrev, ref, user)
# Create push event
self.observe_push(data)
if push_to_branch? ref, oldrev
# Close merged MR
self.update_merge_requests(oldrev, newrev, ref, user)
# Execute web hooks
self.execute_hooks(data.dup)
# Execute project services
self.execute_services(data.dup)
end
# Create satellite
self.satellite.create unless self.satellite.exists?
# Discover the default branch, but only if it hasn't already been set to
# something else
if repository && default_branch.nil?
update_attributes(default_branch: self.repository.discover_default_branch)
end
end
def push_to_branch? ref, oldrev
ref_parts = ref.split('/')
# Return if this is not a push to a branch (e.g. new commits)
!(ref_parts[1] !~ /heads/ || oldrev == "00000000000000000000000000000000")
end
def observe_push(data)
Event.create(
project: self,
action: Event::Pushed,
data: data,
author_id: data[:user_id]
)
end
def execute_hooks(data)
hooks.each { |hook| hook.execute(data) }
end
def execute_services(data)
services.each do |service|
# Call service hook only if it is active
service.execute(data) if service.active
end
end
# Produce a hash of post-receive data
#
# data = {
# before: String,
# after: String,
# ref: String,
# user_id: String,
# user_name: String,
# repository: {
# name: String,
# url: String,
# description: String,
# homepage: String,
# },
# commits: Array,
# total_commits_count: Fixnum
# }
#
def post_receive_data(oldrev, newrev, ref, user)
push_commits = repository.commits_between(oldrev, newrev)
# Total commits count
push_commits_count = push_commits.size
# Get latest 20 commits ASC
push_commits_limited = push_commits.last(20)
# Hash to be passed as post_receive_data
data = {
before: oldrev,
after: newrev,
ref: ref,
user_id: user.id,
user_name: user.name,
repository: {
name: name,
url: url_to_repo,
description: description,
homepage: web_url,
},
commits: [],
total_commits_count: push_commits_count
}
# For perfomance purposes maximum 20 latest commits
# will be passed as post receive hook data.
#
push_commits_limited.each do |commit|
data[:commits] << {
id: commit.id,
message: commit.safe_message,
timestamp: commit.date.xmlschema,
url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{commit.id}",
author: {
name: commit.author_name,
email: commit.author_email
}
}
end
data
end
def update_merge_requests(oldrev, newrev, ref, user)
return true unless ref =~ /heads/
branch_name = ref.gsub("refs/heads/", "")
c_ids = self.repository.commits_between(oldrev, newrev).map(&:id)
# Update code for merge requests
mrs = self.merge_requests.opened.find_all_by_branch(branch_name).all
mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked }
# Close merge requests
mrs = self.merge_requests.opened.where(target_branch: branch_name).all
mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) }
mrs.each { |merge_request| merge_request.merge!(user.id) }
true
end
def valid_repo?
repo
rescue
errors.add(:path, "Invalid repository path")
false
end
def empty_repo?
!repository || repository.empty?
end
def satellite
@satellite ||= Gitlab::Satellite::Satellite.new(self)
end
def repo
repository.raw
end
def url_to_repo
gitolite.url_to_repo(path_with_namespace)
end
def namespace_dir
namespace.try(:path) || ''
end
def update_repository
gitolite.update_repository(self)
end
def destroy_repository
gitolite.remove_repository(self)
end
def repo_exists?
@repo_exists ||= (repository && repository.branches.present?)
rescue
@repo_exists = false
end
def open_branches
if protected_branches.empty?
self.repo.heads
else
pnames = protected_branches.map(&:name)
self.repo.heads.reject { |h| pnames.include?(h.name) }
end.sort_by(&:name)
end
def root_ref?(branch)
repository.root_ref == branch
end
def ssh_url_to_repo
url_to_repo
end
def http_url_to_repo
http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
end
# Check if current branch name is marked as protected in the system
def protected_branch? branch_name
protected_branches.map(&:name).include?(branch_name)
end
end

View File

@ -10,7 +10,7 @@
#
class ProtectedBranch < ActiveRecord::Base
include GitHost
include Gitolited
attr_accessible :name
@ -22,10 +22,10 @@ class ProtectedBranch < ActiveRecord::Base
after_destroy :update_repository
def update_repository
git_host.update_repository(project)
gitolite.update_repository(project)
end
def commit
project.commit(self.name)
project.repository.commit(self.name)
end
end

View File

@ -1,15 +1,35 @@
module Repository
include GitHost
class Repository
# Repository directory name with namespace direcotry
# Examples:
# gitlab/gitolite
# diaspora
#
attr_accessor :path_with_namespace
def valid_repo?
# Grit repo object
attr_accessor :repo
# Default branch in the repository
attr_accessor :root_ref
def initialize(path_with_namespace, root_ref = 'master')
@root_ref = root_ref || "master"
@path_with_namespace = path_with_namespace
# Init grit repo object
repo
rescue
errors.add(:path, "Invalid repository path")
false
end
def empty_repo?
!repo_exists? || !has_commits?
def raw
repo
end
def path_to_repo
@path_to_repo ||= File.join(Gitlab.config.gitolite.repos_path, "#{path_with_namespace}.git")
end
def repo
@repo ||= Grit::Repo.new(path_to_repo)
end
def commit(commit_id = nil)
@ -40,10 +60,6 @@ module Repository
Commit.commits_between(repo, from, to)
end
def satellite
@satellite ||= Gitlab::Satellite::Satellite.new(self)
end
def has_post_receive_file?
!!hook_file
end
@ -88,36 +104,6 @@ module Repository
[branch_names + tag_names].flatten
end
def repo
@repo ||= Grit::Repo.new(path_to_repo)
end
def url_to_repo
git_host.url_to_repo(path_with_namespace)
end
def path_to_repo
File.join(Gitlab.config.gitolite.repos_path, "#{path_with_namespace}.git")
end
def namespace_dir
namespace.try(:path) || ''
end
def update_repository
git_host.update_repository(self)
end
def destroy_repository
git_host.remove_repository(self)
end
def repo_exists?
@repo_exists ||= (repo && !repo.branches.empty?)
rescue
@repo_exists = false
end
def heads
@heads ||= repo.heads
end
@ -128,13 +114,14 @@ module Repository
path ? (tree / path) : tree
end
def open_branches
if protected_branches.empty?
self.repo.heads
else
pnames = protected_branches.map(&:name)
self.repo.heads.reject { |h| pnames.include?(h.name) }
end.sort_by(&:name)
def has_commits?
!!commit
rescue Grit::NoSuchPathError
false
end
def empty?
!has_commits?
end
# Discovers the default branch based on the repository's available branches
@ -153,20 +140,6 @@ module Repository
end
end
def has_commits?
!!commit
rescue Grit::NoSuchPathError
false
end
def root_ref
default_branch || "master"
end
def root_ref?(branch)
root_ref == branch
end
# Archive Project to .tar.gz
#
# Already packed repo archives stored at
@ -193,17 +166,4 @@ module Repository
file_path
end
def ssh_url_to_repo
url_to_repo
end
def http_url_to_repo
http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
end
# Check if current branch name is marked as protected in the system
def protected_branch? branch_name
protected_branches.map(&:name).include?(branch_name)
end
end

View File

@ -20,4 +20,8 @@ class Service < ActiveRecord::Base
has_one :service_hook
validates :project_id, presence: true
def activated?
active
end
end

View File

@ -19,6 +19,6 @@ class SystemHook < WebHook
end
def async_execute(data)
Resque.enqueue(SystemHookWorker, id, data)
Sidekiq::Client.enqueue(SystemHookWorker, id, data)
end
end

118
app/models/team.rb Normal file
View File

@ -0,0 +1,118 @@
class Team
attr_accessor :project
def initialize(project)
@project = project
end
# Shortcut to add users
#
# Use:
# @team << [@user, :master]
# @team << [@users, :master]
#
def << args
users = args.first
if users.respond_to?(:each)
add_users(users, args.second)
else
add_user(users, args.second)
end
end
def add_user(user, access)
add_users_ids([user.id], access)
end
def add_users(users, access)
add_users_ids(users.map(&:id), access)
end
def add_users_ids(user_ids, access)
UsersProject.add_users_into_projects(
[project.id],
user_ids,
access
)
end
# Remove all users from project team
def truncate
UsersProject.truncate_team(project)
end
def members
project.users_projects
end
def guests
members.guests.map(&:user)
end
def reporters
members.reporters.map(&:user)
end
def developers
members.developers.map(&:user)
end
def masters
members.masters.map(&:user)
end
def repository_readers
repository_members[UsersProject::REPORTER]
end
def repository_writers
repository_members[UsersProject::DEVELOPER]
end
def repository_masters
repository_members[UsersProject::MASTER]
end
def repository_members
keys = Hash.new {|h,k| h[k] = [] }
UsersProject.select("keys.identifier, project_access").
joins(user: :keys).where(project_id: project.id).
each {|row| keys[row.project_access] << [row.identifier] }
keys[UsersProject::REPORTER] += project.deploy_keys.pluck(:identifier)
keys
end
def import(source_project)
target_project = project
source_team = source_project.users_projects.all
target_team = target_project.users_projects.all
target_user_ids = target_team.map(&:user_id)
source_team.reject! do |tm|
# Skip if user already present in team
target_user_ids.include?(tm.user_id)
end
source_team.map! do |tm|
new_tm = tm.dup
new_tm.id = nil
new_tm.project_id = target_project.id
new_tm.skip_git = true
new_tm
end
UsersProject.transaction do
source_team.each do |tm|
tm.save
end
target_project.update_repository
end
true
rescue
false
end
end

View File

@ -1,12 +1,13 @@
class Tree
include Linguist::BlobHelper
attr_accessor :path, :tree, :project, :ref
attr_accessor :path, :tree, :ref
delegate :contents, :basename, :name, :data, :mime_type,
:mode, :size, :text?, :colorize, to: :tree
def initialize(raw_tree, project, ref = nil, path = nil)
@project, @ref, @path = project, ref, path
def initialize(raw_tree, ref = nil, path = nil)
@ref, @path = ref, path
@tree = if path.present?
raw_tree / path
else

View File

@ -34,8 +34,6 @@
#
class User < ActiveRecord::Base
include Account
devise :database_authenticatable, :token_authenticatable, :lockable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable
@ -51,7 +49,6 @@ class User < ActiveRecord::Base
has_many :groups, class_name: "Group", foreign_key: :owner_id
has_many :keys, dependent: :destroy
has_many :projects, through: :users_projects
has_many :users_projects, dependent: :destroy
has_many :issues, foreign_key: :author_id, dependent: :destroy
has_many :notes, foreign_key: :author_id, dependent: :destroy
@ -70,6 +67,8 @@ class User < ActiveRecord::Base
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
validate :namespace_uniq, if: ->(user) { user.username_changed? }
before_validation :generate_password, on: :create
before_save :ensure_authentication_token
alias_attribute :private_token, :authentication_token
@ -77,11 +76,14 @@ class User < ActiveRecord::Base
delegate :path, to: :namespace, allow_nil: true, prefix: true
# Scopes
scope :not_in_project, ->(project) { where("id not in (:ids)", ids: project.users.map(&:id) ) }
scope :admins, where(admin: true)
scope :blocked, where(blocked: true)
scope :active, where(blocked: false)
scope :alphabetically, order('name ASC')
#
# Class methods
#
class << self
def filter filter_name
case filter_name
@ -93,6 +95,14 @@ class User < ActiveRecord::Base
end
end
def not_in_project(project)
if project.users.present?
where("id not in (:ids)", ids: project.users.map(&:id) )
else
scoped
end
end
def without_projects
where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)')
end
@ -118,9 +128,158 @@ class User < ActiveRecord::Base
end
end
#
# Instance methods
#
def generate_password
if self.force_random_password
self.password = self.password_confirmation = Devise.friendly_token.first(8)
end
end
def namespace_uniq
namespace_name = self.username
if Namespace.find_by_path(namespace_name)
self.errors.add :username, "already exist"
end
end
# Namespaces user has access to
def namespaces
namespaces = []
# Add user account namespace
namespaces << self.namespace if self.namespace
# Add groups you can manage
namespaces += if admin
Group.all
else
groups.all
end
namespaces
end
# Groups where user is an owner
def owned_groups
groups
end
# Groups user has access to
def authorized_groups
@authorized_groups ||= begin
groups = Group.where(id: self.authorized_projects.pluck(:namespace_id)).all
groups = groups + self.groups
groups.uniq
end
end
# Projects user has access to
def authorized_projects
project_ids = users_projects.pluck(:project_id)
project_ids = project_ids | owned_projects.pluck(:id)
Project.where(id: project_ids)
end
# Projects in user namespace
def personal_projects
Project.personal(self)
end
# Projects where user is an owner
def owned_projects
Project.where("(projects.namespace_id IN (:namespaces)) OR
(projects.namespace_id IS NULL AND projects.creator_id = :user_id)",
namespaces: namespaces.map(&:id), user_id: self.id)
end
# Team membership in personal projects
def tm_in_personal_projects
UsersProject.where(project_id: personal_projects.map(&:id), user_id: self.id)
end
# Returns a string for use as a Gitolite user identifier
#
# Note that Gitolite 2.x requires the following pattern for users:
#
# ^@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$
def identifier
# Replace non-word chars with underscores, then make sure it starts with
# valid chars
email.gsub(/\W/, '_').gsub(/\A([\W\_])+/, '')
end
def is_admin?
admin
end
def require_ssh_key?
keys.count == 0
end
def can_create_project?
projects_limit > personal_projects.count
end
def can_create_group?
is_admin?
end
def abilities
@abilities ||= begin
abilities = Six.new
abilities << Ability
abilities
end
end
def can? action, subject
abilities.allowed?(self, action, subject)
end
def first_name
name.split.first unless name.blank?
end
def cared_merge_requests
MergeRequest.where("author_id = :id or assignee_id = :id", id: self.id)
end
# Remove user from all projects and
# set blocked attribute to true
def block
users_projects.find_each do |membership|
return false unless membership.destroy
end
self.blocked = true
save
end
def projects_limit_percent
return 100 if projects_limit.zero?
(personal_projects.count.to_f / projects_limit) * 100
end
def recent_push project_id = nil
# Get push events not earlier than 2 hours ago
events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours)
events = events.where(project_id: project_id) if project_id
# Take only latest one
events = events.recent.limit(1).first
end
def projects_sorted_by_activity
authorized_projects.sorted_by_activity
end
def several_namespaces?
namespaces.size > 1
end
def namespace_id
namespace.try :id
end
end

View File

@ -11,7 +11,7 @@
#
class UsersProject < ActiveRecord::Base
include GitHost
include Gitolited
GUEST = 10
REPORTER = 20
@ -23,87 +23,96 @@ class UsersProject < ActiveRecord::Base
belongs_to :user
belongs_to :project
after_save :update_repository
after_destroy :update_repository
attr_accessor :skip_git
after_save :update_repository, unless: :skip_git?
after_destroy :update_repository, unless: :skip_git?
validates :user, presence: true
validates :user_id, uniqueness: { :scope => [:project_id], message: "already exists in project" }
validates :user_id, uniqueness: { scope: [:project_id], message: "already exists in project" }
validates :project_access, inclusion: { in: [GUEST, REPORTER, DEVELOPER, MASTER] }, presence: true
validates :project, presence: true
delegate :name, :email, to: :user, prefix: true
scope :guests, where(project_access: GUEST)
scope :reporters, where(project_access: REPORTER)
scope :developers, where(project_access: DEVELOPER)
scope :masters, where(project_access: MASTER)
scope :in_project, ->(project) { where(project_id: project.id) }
class << self
def import_team(source_project, target_project)
UsersProject.without_repository_callback do
UsersProject.transaction do
team = source_project.users_projects.all
team.each do |tm|
# Skip if user already present in team
next if target_project.users.include?(tm.user)
# Add users to project teams with passed access option
#
# access can be an integer representing a access code
# or symbol like :master representing role
#
# Ex.
# add_users_into_projects(
# project_ids,
# user_ids,
# UsersProject::MASTER
# )
#
# add_users_into_projects(
# project_ids,
# user_ids,
# :master
# )
#
def add_users_into_projects(project_ids, user_ids, access)
project_access = if roles_hash.has_key?(access)
roles_hash[access]
elsif roles_hash.values.include?(access.to_i)
access
else
raise "Non valid access"
end
new_tm = tm.dup
new_tm.id = nil
new_tm.project_id = target_project.id
new_tm.save
UsersProject.transaction do
project_ids.each do |project_id|
user_ids.each do |user_id|
users_project = UsersProject.new(project_access: project_access, user_id: user_id)
users_project.project_id = project_id
users_project.skip_git = true
users_project.save
end
end
Gitlab::Gitolite.new.update_repositories(Project.where(id: project_ids))
end
target_project.update_repository
true
rescue
false
end
def without_repository_callback
UsersProject.skip_callback(:destroy, :after, :update_repository)
yield
UsersProject.set_callback(:destroy, :after, :update_repository)
end
def bulk_delete(project, user_ids)
def truncate_teams(project_ids)
UsersProject.transaction do
UsersProject.where(:user_id => user_ids, :project_id => project.id).each do |users_project|
users_projects = UsersProject.where(project_id: project_ids)
users_projects.each do |users_project|
users_project.skip_git = true
users_project.destroy
end
Gitlab::Gitolite.new.update_repositories(Project.where(id: project_ids))
end
true
rescue
false
end
def bulk_update(project, user_ids, project_access)
UsersProject.transaction do
UsersProject.where(:user_id => user_ids, :project_id => project.id).each do |users_project|
users_project.project_access = project_access
users_project.save
end
end
def truncate_team project
truncate_teams [project.id]
end
def bulk_import(project, user_ids, project_access)
UsersProject.transaction do
user_ids.each do |user_id|
users_project = UsersProject.new(
project_access: project_access,
user_id: user_id
)
users_project.project = project
users_project.save
end
end
end
def user_bulk_import(user, project_ids, project_access)
UsersProject.transaction do
project_ids.each do |project_id|
users_project = UsersProject.new(
project_access: project_access,
)
users_project.project_id = project_id
users_project.user_id = user.id
users_project.save
end
end
def roles_hash
{
guest: GUEST,
reporter: REPORTER,
developer: DEVELOPER,
master: MASTER
}
end
def access_roles
@ -116,12 +125,8 @@ class UsersProject < ActiveRecord::Base
end
end
def role_access
project_access
end
def update_repository
git_host.update_repository(project)
gitolite.update_repository(project)
end
def project_access_human
@ -131,4 +136,8 @@ class UsersProject < ActiveRecord::Base
def repo_access_human
self.class.access_roles.invert[self.project_access]
end
def skip_git?
!!@skip_git
end
end

View File

@ -50,5 +50,4 @@ class Wiki < ActiveRecord::Base
def set_slug
self.slug = self.title.parameterize
end
end

View File

@ -3,7 +3,7 @@ class IssueObserver < ActiveRecord::Observer
def after_create(issue)
if issue.assignee && issue.assignee != current_user
Notify.new_issue_email(issue.id).deliver
Notify.delay.new_issue_email(issue.id)
end
end
@ -16,7 +16,7 @@ class IssueObserver < ActiveRecord::Observer
if status
Note.create_status_change_note(issue, current_user, status)
[issue.author, issue.assignee].compact.each do |recipient|
Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user.id).deliver
Notify.delay.issue_status_changed_email(recipient.id, issue.id, status, current_user.id)
end
end
end
@ -27,7 +27,7 @@ class IssueObserver < ActiveRecord::Observer
recipient_ids = [issue.assignee_id, issue.assignee_id_was].keep_if {|id| id && id != current_user.id }
recipient_ids.each do |recipient_id|
Notify.reassigned_issue_email(recipient_id, issue.id, issue.assignee_id_was).deliver
Notify.delay.reassigned_issue_email(recipient_id, issue.id, issue.assignee_id_was)
end
end
end

View File

@ -1,12 +1,12 @@
class KeyObserver < ActiveRecord::Observer
include GitHost
include Gitolited
def after_save(key)
git_host.set_key(key.identifier, key.key, key.projects)
gitolite.set_key(key.identifier, key.key, key.projects)
end
def after_destroy(key)
return if key.is_deploy_key && !key.last_deploy?
git_host.remove_key(key.identifier, key.projects)
gitolite.remove_key(key.identifier, key.projects)
end
end

View File

@ -3,7 +3,7 @@ class MergeRequestObserver < ActiveRecord::Observer
def after_create(merge_request)
if merge_request.assignee && merge_request.assignee != current_user
Notify.new_merge_request_email(merge_request.id).deliver
Notify.delay.new_merge_request_email(merge_request.id)
end
end
@ -25,7 +25,7 @@ class MergeRequestObserver < ActiveRecord::Observer
recipients_ids.delete current_user.id
recipients_ids.each do |recipient_id|
Notify.reassigned_merge_request_email(recipient_id, merge_request.id, merge_request.assignee_id_was).deliver
Notify.delay.reassigned_merge_request_email(recipient_id, merge_request.id, merge_request.assignee_id_was)
end
end
end

View File

@ -11,7 +11,7 @@ class NoteObserver < ActiveRecord::Observer
notify_team(note)
elsif note.notify_author
# Notify only author of resource
Notify.note_commit_email(note.noteable.author_email, note.id).deliver
Notify.delay.note_commit_email(note.noteable.author_email, note.id)
else
# Otherwise ignore it
nil
@ -26,7 +26,7 @@ class NoteObserver < ActiveRecord::Observer
if Notify.respond_to? notify_method
team_without_note_author(note).map do |u|
Notify.send(notify_method, u.id, note.id).deliver
Notify.delay.send(notify_method, u.id, note.id)
end
end
end

View File

@ -2,7 +2,7 @@ class UserObserver < ActiveRecord::Observer
def after_create(user)
log_info("User \"#{user.name}\" (#{user.email}) was created")
Notify.new_user_email(user.id, user.password).deliver
Notify.delay.new_user_email(user.id, user.password)
end
def after_destroy user
@ -14,7 +14,7 @@ class UserObserver < ActiveRecord::Observer
if user.namespace
user.namespace.update_attributes(path: user.username)
else
user.create_namespace!(path: user.username, name: user.name)
user.create_namespace!(path: user.username, name: user.username)
end
end
end

View File

@ -1,7 +1,7 @@
class UsersProjectObserver < ActiveRecord::Observer
def after_commit(users_project)
return if users_project.destroyed?
Notify.project_access_granted_email(users_project.id).deliver
Notify.delay.project_access_granted_email(users_project.id)
end
def after_create(users_project)

View File

@ -1,124 +0,0 @@
module Account
# Returns a string for use as a Gitolite user identifier
#
# Note that Gitolite 2.x requires the following pattern for users:
#
# ^@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$
def identifier
# Replace non-word chars with underscores, then make sure it starts with
# valid chars
email.gsub(/\W/, '_').gsub(/\A([\W\_])+/, '')
end
def is_admin?
admin
end
def require_ssh_key?
keys.count == 0
end
def can_create_project?
projects_limit > my_own_projects.count
end
def can_create_group?
is_admin?
end
def abilities
@abilities ||= begin
abilities = Six.new
abilities << Ability
abilities
end
end
def can? action, subject
abilities.allowed?(self, action, subject)
end
def last_activity_project
projects.first
end
def first_name
name.split.first unless name.blank?
end
def cared_merge_requests
MergeRequest.where("author_id = :id or assignee_id = :id", id: self.id)
end
def project_ids
projects.map(&:id)
end
# Remove user from all projects and
# set blocked attribute to true
def block
users_projects.find_each do |membership|
return false unless membership.destroy
end
self.blocked = true
save
end
def projects_limit_percent
return 100 if projects_limit.zero?
(my_own_projects.count.to_f / projects_limit) * 100
end
def recent_push project_id = nil
# Get push events not earlier than 2 hours ago
events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours)
events = events.where(project_id: project_id) if project_id
# Take only latest one
events = events.recent.limit(1).first
end
def projects_sorted_by_activity
projects.sorted_by_activity
end
def namespaces
namespaces = []
# Add user account namespace
namespaces << self.namespace if self.namespace
# Add groups you can manage
namespaces += if admin
Group.all
else
groups.all
end
namespaces
end
def several_namespaces?
namespaces.size > 1
end
def namespace_id
namespace.try :id
end
def authorized_groups
@authorized_groups ||= begin
groups = Group.where(id: self.projects.pluck(:namespace_id)).all
groups = groups + self.groups
groups.uniq
end
end
def authorized_projects
Project.authorized_for(self)
end
def my_own_projects
Project.personal(self)
end
end

View File

@ -1,58 +0,0 @@
module Authority
# Compatible with all access rights
# Should be rewrited for new access rights
def add_access(user, *access)
access = if access.include?(:admin)
{ project_access: UsersProject::MASTER }
elsif access.include?(:write)
{ project_access: UsersProject::DEVELOPER }
else
{ project_access: UsersProject::REPORTER }
end
opts = { user: user }
opts.merge!(access)
users_projects.create(opts)
end
def reset_access(user)
users_projects.where(project_id: self.id, user_id: user.id).destroy if self.id
end
def repository_readers
keys = Key.joins({user: :users_projects}).
where("users_projects.project_id = ? AND users_projects.project_access = ?", id, UsersProject::REPORTER)
keys.map(&:identifier) + deploy_keys.map(&:identifier)
end
def repository_writers
keys = Key.joins({user: :users_projects}).
where("users_projects.project_id = ? AND users_projects.project_access = ?", id, UsersProject::DEVELOPER)
keys.map(&:identifier)
end
def repository_masters
keys = Key.joins({user: :users_projects}).
where("users_projects.project_id = ? AND users_projects.project_access = ?", id, UsersProject::MASTER)
keys.map(&:identifier)
end
def allow_read_for?(user)
!users_projects.where(user_id: user.id).empty?
end
def guest_access_for?(user)
!users_projects.where(user_id: user.id).empty?
end
def report_access_for?(user)
!users_projects.where(user_id: user.id, project_access: [UsersProject::REPORTER, UsersProject::DEVELOPER, UsersProject::MASTER]).empty?
end
def dev_access_for?(user)
!users_projects.where(user_id: user.id, project_access: [UsersProject::DEVELOPER, UsersProject::MASTER]).empty?
end
def master_access_for?(user)
!users_projects.where(user_id: user.id, project_access: [UsersProject::MASTER]).empty?
end
end

Some files were not shown because too many files have changed in this diff Show More