Commit 30227869 authored by Riyad Preukschas's avatar Riyad Preukschas

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
parents 8ee5fce9 aca0caa8
...@@ -19,8 +19,10 @@ config/gitlab.yml ...@@ -19,8 +19,10 @@ config/gitlab.yml
config/database.yml config/database.yml
config/initializers/omniauth.rb config/initializers/omniauth.rb
config/unicorn.rb config/unicorn.rb
config/resque.yml
db/data.yml db/data.yml
.idea .idea
.DS_Store .DS_Store
.chef .chef
vendor/bundle/* vendor/bundle/*
rails_best_practices_output.html
# .simplecov
SimpleCov.start 'rails' do
merge_timeout 3600
end
...@@ -3,16 +3,12 @@ env: ...@@ -3,16 +3,12 @@ env:
- DB=mysql - DB=mysql
before_install: before_install:
- sudo apt-get install libicu-dev -y - 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" - gem install charlock_holmes -v="0.6.9"
branches: branches:
only: only:
- 'master' - 'master'
rvm: rvm:
- 1.9.3 - 1.9.2
services: services:
- mysql - mysql
- postgresql - postgresql
......
v 4.0.0 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 - Reorganized settings
- Fixed commits compare - Fixed commits compare
- Refactored scss - Refactored scss
......
...@@ -8,7 +8,7 @@ def linux_only(require_as) ...@@ -8,7 +8,7 @@ def linux_only(require_as)
RUBY_PLATFORM.include?('linux') && require_as RUBY_PLATFORM.include?('linux') && require_as
end end
gem "rails", "3.2.9" gem "rails", "3.2.11"
# Supported DBs # Supported DBs
gem "mysql2", group: :mysql gem "mysql2", group: :mysql
...@@ -23,11 +23,15 @@ gem 'omniauth-github' ...@@ -23,11 +23,15 @@ gem 'omniauth-github'
# GITLAB patched libs # GITLAB patched libs
gem "grit", git: "https://github.com/gitlabhq/grit.git", ref: '7f35cb98ff17d534a07e3ce6ec3d580f67402837' 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 'grack', git: "https://github.com/gitlabhq/grack.git", ref: 'ba46f3b0845c6a09d488ae6abdce6ede37e227e8'
gem 'grit_ext', git: "https://github.com/gitlabhq/grit_ext.git", ref: '8e6afc2da821354774aa4d1ee8a1aa2082f84a3e' 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) # Gitolite client (for work with gitolite-admin repo)
gem "gitolite", '1.1.0' gem "gitolite", '1.1.0'
...@@ -77,8 +81,9 @@ gem "acts-as-taggable-on", "2.3.3" ...@@ -77,8 +81,9 @@ gem "acts-as-taggable-on", "2.3.3"
gem "draper", "~> 0.18.0" gem "draper", "~> 0.18.0"
# Background jobs # Background jobs
gem "resque", "~> 1.23.0" gem 'slim'
gem 'resque_mailer' gem 'sinatra', :require => nil
gem 'sidekiq', '2.6.4'
# HTTP requests # HTTP requests
gem "httparty" gem "httparty"
...@@ -104,7 +109,7 @@ group :assets do ...@@ -104,7 +109,7 @@ group :assets do
gem "jquery-rails", "2.1.3" gem "jquery-rails", "2.1.3"
gem "jquery-ui-rails", "2.0.2" gem "jquery-ui-rails", "2.0.2"
gem "modernizr", "2.6.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 'bootstrap-sass', "2.2.1.1"
gem "font-awesome-sass-rails", "~> 2.0.0" gem "font-awesome-sass-rails", "~> 2.0.0"
gem "gemoji", "~> 1.2.1", require: 'emoji/railtie' gem "gemoji", "~> 1.2.1", require: 'emoji/railtie'
...@@ -115,6 +120,14 @@ group :development do ...@@ -115,6 +120,14 @@ group :development do
gem "letter_opener" gem "letter_opener"
gem 'quiet_assets', '~> 1.0.1' gem 'quiet_assets', '~> 1.0.1'
gem 'rack-mini-profiler' gem 'rack-mini-profiler'
# Better errors handler
gem 'better_errors'
gem 'binding_of_caller'
gem 'rails_best_practices'
# Docs generator
gem "sdoc"
end end
group :development, :test do group :development, :test do
...@@ -145,7 +158,6 @@ group :test do ...@@ -145,7 +158,6 @@ group :test do
gem "simplecov", require: false gem "simplecov", require: false
gem "shoulda-matchers", "1.3.0" gem "shoulda-matchers", "1.3.0"
gem 'email_spec' gem 'email_spec'
gem 'resque_spec'
gem "webmock" gem "webmock"
gem 'test_after_commit' gem 'test_after_commit'
end end
......
...@@ -39,17 +39,6 @@ GIT ...@@ -39,17 +39,6 @@ GIT
grit_ext (0.6.1) grit_ext (0.6.1)
charlock_holmes (~> 0.6.9) 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 GIT
remote: https://github.com/gitlabhq/pygments.rb.git remote: https://github.com/gitlabhq/pygments.rb.git
revision: db1da0343adf86b49bdc3add04d02d2e80438d38 revision: db1da0343adf86b49bdc3add04d02d2e80438d38
...@@ -60,11 +49,10 @@ GIT ...@@ -60,11 +49,10 @@ GIT
yajl-ruby (~> 1.1.0) yajl-ruby (~> 1.1.0)
GIT GIT
remote: https://github.com/gitlabhq/yaml_db.git remote: https://github.com/gitlabhq/raphael-rails.git
revision: 98e9a5dca43e3fedd3268c76a73af40d1bdf1dfd revision: cb2c92a040b9b941a5f1aa1ea866cc26e944fe58
ref: 98e9a5dca43e3fedd3268c76a73af40d1bdf1dfd
specs: specs:
yaml_db (0.2.2) raphael-rails (2.1.0)
GIT GIT
remote: https://github.com/jonleighton/poltergeist.git remote: https://github.com/jonleighton/poltergeist.git
...@@ -81,12 +69,12 @@ GIT ...@@ -81,12 +69,12 @@ GIT
GEM GEM
remote: http://rubygems.org/ remote: http://rubygems.org/
specs: specs:
actionmailer (3.2.9) actionmailer (3.2.11)
actionpack (= 3.2.9) actionpack (= 3.2.11)
mail (~> 2.4.4) mail (~> 2.4.4)
actionpack (3.2.9) actionpack (3.2.11)
activemodel (= 3.2.9) activemodel (= 3.2.11)
activesupport (= 3.2.9) activesupport (= 3.2.11)
builder (~> 3.0.0) builder (~> 3.0.0)
erubis (~> 2.7.0) erubis (~> 2.7.0)
journey (~> 1.0.4) journey (~> 1.0.4)
...@@ -94,18 +82,18 @@ GEM ...@@ -94,18 +82,18 @@ GEM
rack-cache (~> 1.2) rack-cache (~> 1.2)
rack-test (~> 0.6.1) rack-test (~> 0.6.1)
sprockets (~> 2.2.1) sprockets (~> 2.2.1)
activemodel (3.2.9) activemodel (3.2.11)
activesupport (= 3.2.9) activesupport (= 3.2.11)
builder (~> 3.0.0) builder (~> 3.0.0)
activerecord (3.2.9) activerecord (3.2.11)
activemodel (= 3.2.9) activemodel (= 3.2.11)
activesupport (= 3.2.9) activesupport (= 3.2.11)
arel (~> 3.0.2) arel (~> 3.0.2)
tzinfo (~> 0.3.29) tzinfo (~> 0.3.29)
activeresource (3.2.9) activeresource (3.2.11)
activemodel (= 3.2.9) activemodel (= 3.2.11)
activesupport (= 3.2.9) activesupport (= 3.2.11)
activesupport (3.2.9) activesupport (3.2.11)
i18n (~> 0.6) i18n (~> 0.6)
multi_json (~> 1.0) multi_json (~> 1.0)
acts-as-taggable-on (2.3.3) acts-as-taggable-on (2.3.3)
...@@ -115,6 +103,10 @@ GEM ...@@ -115,6 +103,10 @@ GEM
awesome_print (1.1.0) awesome_print (1.1.0)
backports (2.6.5) backports (2.6.5)
bcrypt-ruby (3.0.1) 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) blankslate (3.1.2)
bootstrap-sass (2.2.1.1) bootstrap-sass (2.2.1.1)
sass (~> 3.2) sass (~> 3.2)
...@@ -129,12 +121,17 @@ GEM ...@@ -129,12 +121,17 @@ GEM
carrierwave (0.7.1) carrierwave (0.7.1)
activemodel (>= 3.2.0) activemodel (>= 3.2.0)
activesupport (>= 3.2.0) activesupport (>= 3.2.0)
celluloid (0.12.4)
facter (>= 1.6.12)
timers (>= 1.0.0)
charlock_holmes (0.6.9) charlock_holmes (0.6.9)
childprocess (0.3.6) childprocess (0.3.6)
ffi (~> 1.0, >= 1.0.6) ffi (~> 1.0, >= 1.0.6)
chosen-rails (0.9.8) chosen-rails (0.9.8)
railties (~> 3.0) railties (~> 3.0)
thor (~> 0.14) thor (~> 0.14)
code_analyzer (0.3.1)
sexp_processor
coderay (1.0.8) coderay (1.0.8)
coffee-rails (3.2.2) coffee-rails (3.2.2)
coffee-script (>= 2.2.0) coffee-script (>= 2.2.0)
...@@ -145,6 +142,7 @@ GEM ...@@ -145,6 +142,7 @@ GEM
coffee-script-source (1.4.0) coffee-script-source (1.4.0)
colored (1.2) colored (1.2)
colorize (0.5.8) colorize (0.5.8)
connection_pool (1.0.0)
crack (0.3.1) crack (0.3.1)
daemons (1.1.9) daemons (1.1.9)
devise (2.1.2) devise (2.1.2)
...@@ -164,6 +162,7 @@ GEM ...@@ -164,6 +162,7 @@ GEM
eventmachine (1.0.0) eventmachine (1.0.0)
execjs (1.4.0) execjs (1.4.0)
multi_json (~> 1.0) multi_json (~> 1.0)
facter (1.6.17)
factory_girl (4.1.0) factory_girl (4.1.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
factory_girl_rails (4.1.0) factory_girl_rails (4.1.0)
...@@ -190,6 +189,12 @@ GEM ...@@ -190,6 +189,12 @@ GEM
pygments.rb (>= 0.2.13) pygments.rb (>= 0.2.13)
github-markup (0.7.4) github-markup (0.7.4)
gitlab_meta (4.0) 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) gitolite (1.1.0)
gratr19 (~> 0.4.4.1) gratr19 (~> 0.4.4.1)
grit (~> 2.5.0) grit (~> 2.5.0)
...@@ -240,7 +245,7 @@ GEM ...@@ -240,7 +245,7 @@ GEM
jquery-ui-rails (2.0.2) jquery-ui-rails (2.0.2)
jquery-rails jquery-rails
railties (>= 3.1.0) railties (>= 3.1.0)
json (1.7.5) json (1.7.6)
jwt (0.1.5) jwt (0.1.5)
multi_json (>= 1.0) multi_json (>= 1.0)
kaminari (0.14.1) kaminari (0.14.1)
...@@ -264,7 +269,7 @@ GEM ...@@ -264,7 +269,7 @@ GEM
mime-types (1.19) mime-types (1.19)
modernizr (2.6.2) modernizr (2.6.2)
sprockets (~> 2.0) sprockets (~> 2.0)
multi_json (1.3.7) multi_json (1.5.0)
multi_xml (0.5.1) multi_xml (0.5.1)
multipart-post (1.1.5) multipart-post (1.1.5)
mysql2 (0.3.11) mysql2 (0.3.11)
...@@ -299,6 +304,7 @@ GEM ...@@ -299,6 +304,7 @@ GEM
pg (0.14.1) pg (0.14.1)
polyglot (0.3.3) polyglot (0.3.3)
posix-spawn (0.3.6) posix-spawn (0.3.6)
progressbar (0.12.0)
pry (0.9.10) pry (0.9.10)
coderay (~> 1.0.5) coderay (~> 1.0.5)
method_source (~> 0.8) method_source (~> 0.8)
...@@ -306,7 +312,7 @@ GEM ...@@ -306,7 +312,7 @@ GEM
pyu-ruby-sasl (0.0.3.3) pyu-ruby-sasl (0.0.3.3)
quiet_assets (1.0.1) quiet_assets (1.0.1)
railties (~> 3.1) railties (~> 3.1)
rack (1.4.1) rack (1.4.3)
rack-accept (0.4.5) rack-accept (0.4.5)
rack (>= 0.4) rack (>= 0.4)
rack-cache (1.2) rack-cache (1.2)
...@@ -315,33 +321,40 @@ GEM ...@@ -315,33 +321,40 @@ GEM
rack (>= 1.1.3) rack (>= 1.1.3)
rack-mount (0.8.3) rack-mount (0.8.3)
rack (>= 1.0.0) rack (>= 1.0.0)
rack-protection (1.2.0) rack-protection (1.3.2)
rack rack
rack-ssl (1.3.2) rack-ssl (1.3.2)
rack rack
rack-test (0.6.2) rack-test (0.6.2)
rack (>= 1.0) rack (>= 1.0)
rails (3.2.9) rails (3.2.11)
actionmailer (= 3.2.9) actionmailer (= 3.2.11)
actionpack (= 3.2.9) actionpack (= 3.2.11)
activerecord (= 3.2.9) activerecord (= 3.2.11)
activeresource (= 3.2.9) activeresource (= 3.2.11)
activesupport (= 3.2.9) activesupport (= 3.2.11)
bundler (~> 1.0) bundler (~> 1.0)
railties (= 3.2.9) railties (= 3.2.11)
rails-dev-tweaks (0.6.1) rails-dev-tweaks (0.6.1)
actionpack (~> 3.1) actionpack (~> 3.1)
railties (~> 3.1) railties (~> 3.1)
railties (3.2.9) rails_best_practices (1.13.2)
actionpack (= 3.2.9) activesupport
activesupport (= 3.2.9) 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) rack-ssl (~> 1.3.2)
rake (>= 0.8.7) rake (>= 0.8.7)
rdoc (~> 3.4) rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0) thor (>= 0.14.6, < 2.0)
raindrops (0.10.0) raindrops (0.10.0)
rake (10.0.1) rake (10.0.3)
raphael-rails (1.5.2)
rb-fsevent (0.9.2) rb-fsevent (0.9.2)
rb-inotify (0.8.8) rb-inotify (0.8.8)
ffi (>= 0.5.0) ffi (>= 0.5.0)
...@@ -351,16 +364,6 @@ GEM ...@@ -351,16 +364,6 @@ GEM
redis (3.0.2) redis (3.0.2)
redis-namespace (1.2.1) redis-namespace (1.2.1)
redis (~> 3.0.0) 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 (2.12.0)
rspec-core (~> 2.12.0) rspec-core (~> 2.12.0)
rspec-expectations (~> 2.12.0) rspec-expectations (~> 2.12.0)
...@@ -383,6 +386,9 @@ GEM ...@@ -383,6 +386,9 @@ GEM
railties (~> 3.2.0) railties (~> 3.2.0)
sass (>= 3.1.10) sass (>= 3.1.10)
tilt (~> 1.3) tilt (~> 1.3)
sdoc (0.3.20)
json (>= 1.1.3)
rdoc (~> 3.10)
seed-fu (2.2.0) seed-fu (2.2.0)
activerecord (~> 3.1) activerecord (~> 3.1)
activesupport (~> 3.1) activesupport (~> 3.1)
...@@ -392,8 +398,15 @@ GEM ...@@ -392,8 +398,15 @@ GEM
multi_json (~> 1.0) multi_json (~> 1.0)
rubyzip rubyzip
settingslogic (2.0.8) settingslogic (2.0.8)
sexp_processor (4.1.3)
shoulda-matchers (1.3.0) shoulda-matchers (1.3.0)
activesupport (>= 3.0.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) simplecov (0.7.1)
multi_json (~> 1.0) multi_json (~> 1.0)
simplecov-html (~> 0.7.1) simplecov-html (~> 0.7.1)
...@@ -403,6 +416,9 @@ GEM ...@@ -403,6 +416,9 @@ GEM
rack-protection (~> 1.2) rack-protection (~> 1.2)
tilt (~> 1.3, >= 1.3.3) tilt (~> 1.3, >= 1.3.3)
six (0.2.0) six (0.2.0)
slim (1.3.6)
temple (~> 0.5.5)
tilt (~> 1.3.3)
slop (3.3.3) slop (3.3.3)
spinach (0.5.2) spinach (0.5.2)
colorize colorize
...@@ -411,12 +427,13 @@ GEM ...@@ -411,12 +427,13 @@ GEM
capybara (~> 1) capybara (~> 1)
railties (>= 3) railties (>= 3)
spinach (>= 0.4) spinach (>= 0.4)
sprockets (2.2.1) sprockets (2.2.2)
hike (~> 1.2) hike (~> 1.2)
multi_json (~> 1.0) multi_json (~> 1.0)
rack (~> 1.0) rack (~> 1.0)
tilt (~> 1.1, != 1.3.0) tilt (~> 1.1, != 1.3.0)
stamp (0.3.0) stamp (0.3.0)
temple (0.5.5)
test_after_commit (0.0.1) test_after_commit (0.0.1)
therubyracer (0.10.2) therubyracer (0.10.2)
libv8 (~> 3.3.10) libv8 (~> 3.3.10)
...@@ -426,6 +443,7 @@ GEM ...@@ -426,6 +443,7 @@ GEM
rack (>= 1.0.0) rack (>= 1.0.0)
thor (0.16.0) thor (0.16.0)
tilt (1.3.3) tilt (1.3.3)
timers (1.0.2)
treetop (1.4.12) treetop (1.4.12)
polyglot polyglot
polyglot (>= 0.3.1) polyglot (>= 0.3.1)
...@@ -437,8 +455,6 @@ GEM ...@@ -437,8 +455,6 @@ GEM
kgio (~> 2.6) kgio (~> 2.6)
rack rack
raindrops (~> 0.7) raindrops (~> 0.7)
vegas (0.1.11)
rack (>= 1.0.0)
virtus (0.5.2) virtus (0.5.2)
backports (~> 2.6.1) backports (~> 2.6.1)
warden (1.2.1) warden (1.2.1)
...@@ -458,6 +474,8 @@ DEPENDENCIES ...@@ -458,6 +474,8 @@ DEPENDENCIES
acts-as-taggable-on (= 2.3.3) acts-as-taggable-on (= 2.3.3)
annotate! annotate!
awesome_print awesome_print
better_errors
binding_of_caller
bootstrap-sass (= 2.2.1.1) bootstrap-sass (= 2.2.1.1)
capybara capybara
carrierwave (~> 0.7.1) carrierwave (~> 0.7.1)
...@@ -477,6 +495,8 @@ DEPENDENCIES ...@@ -477,6 +495,8 @@ DEPENDENCIES
github-linguist (~> 2.3.4) github-linguist (~> 2.3.4)
github-markup (~> 0.7.4) github-markup (~> 0.7.4)
gitlab_meta (= 4.0) gitlab_meta (= 4.0)
gitlab_omniauth-ldap (= 1.0.2)
gitlab_yaml_db (= 1.0.0)
gitolite (= 1.1.0) gitolite (= 1.1.0)
grack! grack!
grape (~> 0.2.1) grape (~> 0.2.1)
...@@ -498,7 +518,6 @@ DEPENDENCIES ...@@ -498,7 +518,6 @@ DEPENDENCIES
omniauth (~> 1.1.1) omniauth (~> 1.1.1)
omniauth-github omniauth-github
omniauth-google-oauth2 omniauth-google-oauth2
omniauth-ldap!
omniauth-twitter omniauth-twitter
pg pg
poltergeist! poltergeist!
...@@ -506,22 +525,24 @@ DEPENDENCIES ...@@ -506,22 +525,24 @@ DEPENDENCIES
pygments.rb! pygments.rb!
quiet_assets (~> 1.0.1) quiet_assets (~> 1.0.1)
rack-mini-profiler rack-mini-profiler
rails (= 3.2.9) rails (= 3.2.11)
rails-dev-tweaks rails-dev-tweaks
raphael-rails (= 1.5.2) rails_best_practices
raphael-rails!
rb-fsevent rb-fsevent
rb-inotify rb-inotify
redcarpet (~> 2.2.2) redcarpet (~> 2.2.2)
resque (~> 1.23.0)
resque_mailer
resque_spec
rspec-rails rspec-rails
sass-rails (~> 3.2.5) sass-rails (~> 3.2.5)
sdoc
seed-fu seed-fu
settingslogic settingslogic
shoulda-matchers (= 1.3.0) shoulda-matchers (= 1.3.0)
sidekiq (= 2.6.4)
simplecov simplecov
sinatra
six six
slim
spinach-rails spinach-rails
stamp stamp
test_after_commit test_after_commit
...@@ -530,4 +551,3 @@ DEPENDENCIES ...@@ -530,4 +551,3 @@ DEPENDENCIES
uglifier (~> 1.3.0) uglifier (~> 1.3.0)
unicorn (~> 4.4.0) unicorn (~> 4.4.0)
webmock webmock
yaml_db!
web: bundle exec rails s -p $PORT 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
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
GitLab is a free project and repository management application GitLab is a free project and repository management application
![CI](http://ci.gitlab.org/projects/1/status?ref=master)
## Application details ## Application details
...@@ -39,6 +40,6 @@ Email ...@@ -39,6 +40,6 @@ Email
## Contribute ## 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. Want to help - send a pull request.
We'll accept good pull requests. We'll accept good pull requests.
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.
$ ->
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(",")
...@@ -11,7 +11,7 @@ function initIssuesSearch() { ...@@ -11,7 +11,7 @@ function initIssuesSearch() {
last_terms = terms; last_terms = terms;
if (terms.length >= 2 || terms.length == 0) { 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); $('#issues-table').html(response);
}); });
} }
......
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();
});
}
#
# * 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
$ -> $ ->
$('.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 ul.nav li a').click ->
$('.milestone-issue-filter li').toggleClass('active') $('.milestone-issue-filter li').toggleClass('active')
$('.milestone-issue-filter tr[data-closed]').toggleClass('hide') $('.milestone-issue-filter li[data-closed]').toggleClass('hide')
false 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 ul.nav li a').click ->
$('.milestone-merge-requests-filter li').toggleClass('active') $('.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 false
...@@ -4,9 +4,16 @@ var Pager = { ...@@ -4,9 +4,16 @@ var Pager = {
disable:false, disable:false,
init: init:
function(limit) { function(limit, preload) {
this.limit=limit; this.limit=limit;
this.offset=limit;
if(preload) {
this.offset = 0;
this.getOld();
} else {
this.offset = limit;
}
this.initLoadMore(); this.initLoadMore();
}, },
......
...@@ -117,34 +117,10 @@ span.update-author { ...@@ -117,34 +117,10 @@ span.update-author {
} }
.label { .label {
background-color: #474D57; padding: 0px 4px;
font-size: 10px;
&.label-tag { font-style: normal;
background: none; background-color: $link_color;
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;
}
}
&.label-success { &.label-success {
background-color: #8D8; background-color: #8D8;
...@@ -425,7 +401,7 @@ li.note { ...@@ -425,7 +401,7 @@ li.note {
.supp_diff_link, .supp_diff_link,
.mr_show_all_commits { .show-all-commits {
cursor: pointer; cursor: pointer;
} }
......
/** /**
* =================================== * ===================================
* Contain 3 main UI block elements: * Contain UI block elements:
* .main_box - for show pages * .ui-box - for any block & widgets
* .ui-box - for simple block & widgets
* =================================== * ===================================
*/ */
/** /**
* UI box element * UI Block
* contains top, middle, bottom blocks
* *
*/ */
.main_box { .ui-box {
@extend .borders; background: #F9F9F9;
@extend .prepend-top-20; margin-bottom: 25px;
@extend .append-bottom-20; border: 1px solid #CCC;
border-width: 1px;
@include solid-shade; @include solid-shade;
&.ui-box-show {
margin:20px 0;
background: #FFF;
}
img { max-width: 100%; } img { max-width: 100%; }
...@@ -27,9 +28,9 @@ ...@@ -27,9 +28,9 @@
} }
} }
.top_box_content, .ui-box-head,
.middle_box_content, .ui-box-body,
.bottom_box_content { .ui-box-bottom {
padding: 15px; padding: 15px;
word-wrap: break-word; word-wrap: break-word;
...@@ -39,19 +40,25 @@ ...@@ -39,19 +40,25 @@
border: none; border: none;
padding: 0; padding: 0;
} }
.clearfix {
margin: 0;
}
} }
.top_box_content { .ui-box-head {
.box-title { .box-title {
color: $style_color; color: $style_color;
font-size: 18px; font-size: 18px;
font-weight: normal; font-weight: normal;
line-height: 28px; line-height: 28px;
} }
h3 {
margin: 0;
}
} }
.middle_box_content { .ui-box-body {
@include border-radius(0);
border: none; border: none;
font-size: 12px; font-size: 12px;
background-color: #f5f5f5; background-color: #f5f5f5;
...@@ -59,24 +66,9 @@ ...@@ -59,24 +66,9 @@
border-top: 1px solid #eee; border-top: 1px solid #eee;
} }
.bottom_box_content { .ui-box-bottom {
border-top: 1px solid #eee; 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 { &.white {
background: #fff; background: #fff;
...@@ -86,47 +78,47 @@ ...@@ -86,47 +78,47 @@
margin: 0; margin: 0;
} }
h5, .title { .title {
padding: 0 10px;
@include border-radius(4px 4px 0 0);
@include bg-gray-gradient; @include bg-gray-gradient;
border-top: 1px solid #eaeaea; border-bottom: 1px solid #CCC;
border-bottom: 1px solid #bbb; color: #456;
font-size: 16px;
text-shadow: 0 1px 1px #fff;
padding: 0px 10px;
line-height: 36px;
font-size: 14px;
font-weight: normal;
> a { > a {
text-shadow: 0 1px 1px #fff; text-shadow: 0 1px 1px #fff;
} }
&.small {
line-height: 28px;
font-size: 14px;
line-height: 28px;
text-shadow: 0 1px 1px white;
}
form { form {
padding: 9px 0; margin-bottom: 0;
margin: 0px; margin-top: 3px;
} }
.nav-pills { .nav-pills {
li { > li {
padding: 3px 0; > a {
&.active a { background-color: $style_color; } padding: 13px;
a { margin: 0;
@include border-radius(7px); 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 { &.padded {
h5, .title { h5, .title {
margin: -20px; margin: -20px;
...@@ -143,6 +135,7 @@ ...@@ -143,6 +135,7 @@
color: #777; color: #777;
} }
} }
.row_title { .row_title {
font-weight: bold; font-weight: bold;
color: #444; color: #444;
...@@ -151,8 +144,4 @@ ...@@ -151,8 +144,4 @@
text-decoration: underline; text-decoration: underline;
} }
} }
.ui-box-body {
padding: 10px;
}
} }
...@@ -7,6 +7,10 @@ ...@@ -7,6 +7,10 @@
color: #333; color: #333;
} }
&.btn-white {
background: #FFF;
}
&.primary { &.primary {
background: #2a79A3; background: #2a79A3;
@include linear-gradient(#47A7b7, #2585b5); @include linear-gradient(#47A7b7, #2585b5);
......
...@@ -17,20 +17,43 @@ ...@@ -17,20 +17,43 @@
.padded { padding:20px } .padded { padding:20px }
.ipadded { padding:20px!important } .ipadded { padding:20px!important }
.lborder { border-left:1px solid #eee } .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; } .underlined_link { text-decoration: underline; }
.borders { border: 1px solid #ccc; @include shade; }
.hint { font-style: italic; color: #999; } .hint { font-style: italic; color: #999; }
.light { color: #888 } .light { color: #888 }
.tiny { font-weight: normal } .tiny { font-weight: normal }
/** PILLS & TABS**/ /** PILLS & TABS**/
.nav-pills a:hover { background-color: #888; } .nav-pills {
.nav-pills .active a { background-color: $style_color; } .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-pills > .active > a > i[class^="icon-"] { background: inherit; }
/**
* nav-tabs
*
*/
.nav-tabs > li > a, .nav-pills > li > a { color: $style_color; } .nav-tabs > li > a, .nav-pills > li > a { color: $style_color; }
.nav.nav-tabs { .nav.nav-tabs {
li { li {
......
@font-face{ @font-face{
font-family: Korolev; font-family: Yanone;
src: font-url('korolev-medium-compressed.otf'); src: font-url('YanoneKaffeesatz-Light.ttf');
} }
/** Typo **/ /** 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;
\ No newline at end of file
...@@ -23,14 +23,12 @@ ...@@ -23,14 +23,12 @@
border-bottom: 1px solid #ADF; border-bottom: 1px solid #ADF;
} }
&:first-child {
@include border-radius(4px 4px 0 0);
border-top: none;
}
&:last-child { &:last-child {
@include border-radius(0 0 4px 4px); border-bottom: none;
border: none;
&.bottom {
background: #f5f5f5;
}
} }
.author { color: #999; } .author { color: #999; }
......
...@@ -62,8 +62,8 @@ ...@@ -62,8 +62,8 @@
@mixin header-font { @mixin header-font {
color: $style_color; color: $style_color;
text-shadow: 0 1px 1px #FFF; text-shadow: 0 1px 1px #FFF;
font-family: 'Korolev', sans-serif; font-family: 'Yanone', sans-serif;
font-size: 28px; font-size: 26px;
line-height: 48px; line-height: 42px;
font-weight: normal; font-weight: normal;
} }
...@@ -25,7 +25,7 @@ table { ...@@ -25,7 +25,7 @@ table {
} }
th, td { th, td {
padding: 8px; padding: 10px;
line-height: 18px; line-height: 18px;
text-align: left; text-align: left;
} }
......
.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 SHOw
* *
*/ */
.commit-committer-link,
.commit-author-link {
font-size: 13px;
color: #555;
&:hover {
color: #999;
}
}
.diff_file { .diff_file {
border: 1px solid #CCC; border: 1px solid #CCC;
margin-bottom: 1em; margin-bottom: 1em;
...@@ -258,13 +201,6 @@ ...@@ -258,13 +201,6 @@
min-width: 65px; min-width: 65px;
font-family: $monospace; font-family: $monospace;
} }
.commit-author-name {
color: #777;
&:hover {
color: #999;
}
}
} }
.diff_file_header a, .diff_file_header a,
......
...@@ -47,6 +47,12 @@ ...@@ -47,6 +47,12 @@
.event-info { .event-info {
color: #666; color: #666;
} }
.event-note {
padding-top: 5px;
padding-left: 5px;
display: inline-block;
color: #777;
}
} }
.avatar { .avatar {
position: relative; position: relative;
......
...@@ -33,22 +33,29 @@ header { ...@@ -33,22 +33,29 @@ header {
* *
*/ */
.app_logo { .app_logo {
width: 170px;
float: left; float: left;
margin-right: 15px;
position: relative;
top: -5px;
padding-top: 5px;
a { a {
float: left; float: left;
padding: 0px; padding: 0px;
margin: 0 10px;
h1 { h1 {
width: 90px;
background: url('logo_dark.png') no-repeat 0px 2px; background: url('logo_dark.png') no-repeat 0px 2px;
float: left; float: left;
margin-left: 2px;
padding-left: 45px;
height: 40px; height: 40px;
width: 40px;
@include header-font; @include header-font;
text-indent: -9999px;
} }
} }
&:hover {
background-color: #EEE;
}
} }
/** /**
...@@ -60,7 +67,7 @@ header { ...@@ -60,7 +67,7 @@ header {
position: relative; position: relative;
float: left; float: left;
margin: 0; margin: 0;
margin-right: 30px; margin-left: 15px;
@include header-font; @include header-font;
} }
...@@ -233,7 +240,7 @@ header { ...@@ -233,7 +240,7 @@ header {
.app_logo { .app_logo {
a { a {
h1 { h1 {
background: url('logo_white.png') no-repeat 0px 2px; background: url('logo_white.png') no-repeat center center;
color: #fff; color: #fff;
text-shadow: 0 1px 1px #111; text-shadow: 0 1px 1px #111;
} }
...@@ -244,5 +251,23 @@ header { ...@@ -244,5 +251,23 @@ header {
text-shadow: 0 1px 1px #111; 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;
}
} }
.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 { .issues_table {
.issue { .issue {
padding: 7px 10px; padding: 10px;
.issue_check { .issue_check {
float: left; float: left;
...@@ -82,38 +54,34 @@ input.check_all_issues { ...@@ -82,38 +54,34 @@ input.check_all_issues {
} }
} }
@media (min-width: 800px) { .issues_filters select { width: 160px; } } @media (min-width: 800px) { .issues_filters select { width: 160px; } }
@media (min-width: 1000px) { .issues_filters select { width: 200px; } }
@media (min-width: 1200px) { .issues_filters select { width: 220px; } } @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-table-holder {
.issues_filters { .issues_filters {
form {
padding: 0;
margin: 0;
margin-top:7px
}
} }
.issues_bulk_update { .issues_bulk_update {
margin: 0; margin: 0;
form { form {
padding: 0; float:left;
margin: 0;
margin-top:7px
} }
.update_selected_issues { .update_selected_issues {
position: relative; position: relative;
top:-2px; top:5px;
margin-left: 4px; margin-left: 4px;
float: left; float: left;
} }
.update_issues_text { .update_issues_text {
padding: 3px; padding: 3px;
line-height: 18px; line-height: 28px;
float: left; float: left;
color: #479;
} }
} }
} }
......
/**
* MR form
*
*/
.mr_branch_box {
@extend .ui-box;
margin-bottom: 20px;
.body {
background: #f1f1f1;
}
}
/** /**
* MR -> show: Automerge widget * MR -> show: Automerge widget
...@@ -47,6 +33,7 @@ ...@@ -47,6 +33,7 @@
} }
label { label {
color: #444; color: #444;
text-align: left
} }
} }
...@@ -56,7 +43,7 @@ ...@@ -56,7 +43,7 @@
} }
} }
.mr_nav_tabs { .merge-request .nav-tabs{
li { li {
a { a {
font-weight: bold; font-weight: bold;
...@@ -67,7 +54,7 @@ ...@@ -67,7 +54,7 @@
} }
li.merge_request { li.merge_request {
padding: 7px 10px; padding: 10px;
img.avatar { img.avatar {
width: 32px; width: 32px;
margin-top: 1px; margin-top: 1px;
...@@ -78,7 +65,7 @@ li.merge_request { ...@@ -78,7 +65,7 @@ li.merge_request {
} }
} }
.merge_in_progress { .merge-in-progress {
@extend .padded; @extend .padded;
@extend .append-bottom-10; @extend .append-bottom-10;
} }
...@@ -120,19 +107,3 @@ li.merge_request { ...@@ -120,19 +107,3 @@ li.merge_request {
.mr_direction_tip { .mr_direction_tip {
margin-top:40px 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;
}
}
}
...@@ -294,6 +294,8 @@ ul.notes { ...@@ -294,6 +294,8 @@ ul.notes {
padding: 4px 6px; padding: 4px 6px;
} }
.note_text { .note_text {
border: 1px solid #DDD;
box-shadow: none;
font-size: 14px; font-size: 14px;
height: 80px; height: 80px;
width: 98.6%; width: 98.6%;
......
...@@ -8,16 +8,12 @@ ...@@ -8,16 +8,12 @@
.groups_box, .groups_box,
.projects_box { .projects_box {
> h5 { > .title {
color: $style_color; padding: 2px 15px;
font-size: 16px;
text-shadow: 0 1px 1px #fff;
padding: 2px 10px;
line-height: 32px;
font-size: 14px;
} }
.nav-projects-tabs li { padding: 0; } .nav-projects-tabs li { padding: 0; }
.well-list { .well-list {
li { padding: 15px; }
.arrow { .arrow {
float: right; float: right;
padding: 10px; padding: 10px;
...@@ -109,7 +105,7 @@ ul.nav.nav-projects-tabs { ...@@ -109,7 +105,7 @@ ul.nav.nav-projects-tabs {
li { li {
a { a {
padding: 4px 20px; padding: 6px 25px;
margin-top: 2px; margin-top: 2px;
border-color: #DDD; border-color: #DDD;
background-color: #EEE; background-color: #EEE;
......
...@@ -4,21 +4,8 @@ ...@@ -4,21 +4,8 @@
* *
*/ */
.ui_basic { .ui_basic {
.app_logo {
.separator {
margin-left: 0;
margin-right: 0;
}
}
.separator { .separator {
float: left;
height: 60px;
width: 1px;
background: white; background: white;
border-left: 1px solid #DDD; border-left: 1px solid #DDD;
margin-top: -10px;
margin-left: 10px;
margin-right: 10px;
} }
} }
...@@ -17,6 +17,15 @@ ...@@ -17,6 +17,15 @@
&.navbar-gitlab { &.navbar-gitlab {
.navbar-inner { .navbar-inner {
background: #657; background: #657;
.app_logo {
&:hover {
background-color: #6A5A7A;
}
}
.separator {
background: #546;
border-left: 1px solid #706080;
}
} }
} }
} }
......
...@@ -17,6 +17,15 @@ ...@@ -17,6 +17,15 @@
&.navbar-gitlab { &.navbar-gitlab {
.navbar-inner { .navbar-inner {
background: #708090; background: #708090;
.app_logo {
&:hover {
background-color: #6A7A8A;
}
}
.separator {
background: #607080;
border-left: 1px solid #8090A0;
}
} }
} }
} }
......
...@@ -46,21 +46,26 @@ ...@@ -46,21 +46,26 @@
.app_logo { .app_logo {
a { a {
h1 { h1 {
background: url('logo_white.png') no-repeat 0px 2px; background: url('logo_white.png') no-repeat center center;
color: #eee; color: #eee;
text-shadow: 0 1px 1px #111; text-shadow: 0 1px 1px #111;
} }
} }
.separator { &:hover {
display: none; background-color: #41464e;
} }
} }
.project_name { .project_name {
color: #eee; color: #eee;
text-shadow: 0 1px 1px #111; text-shadow: 0 1px 1px #111;
} }
} }
.separator {
background: #31363E;
border-left: 1px solid #666;
}
/* /*
* End of Application Header * End of Application Header
* *
......
...@@ -17,6 +17,15 @@ ...@@ -17,6 +17,15 @@
&.navbar-gitlab { &.navbar-gitlab {
.navbar-inner { .navbar-inner {
background: #567; background: #567;
.app_logo {
&:hover {
background-color: #516171;
}
}
.separator {
background: #456;
border-left: 1px solid #678;
}
} }
} }
} }
......
...@@ -9,16 +9,16 @@ class CommitLoadContext < BaseContext ...@@ -9,16 +9,16 @@ class CommitLoadContext < BaseContext
status: :ok status: :ok
} }
commit = project.commit(params[:id]) commit = project.repository.commit(params[:id])
if commit if commit
commit = CommitDecorator.decorate(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[:commit] = commit
result[:note] = project.build_commit_note(commit) result[:note] = project.build_commit_note(commit)
result[:line_notes] = line_notes 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 begin
result[:suppress_diff] = true if commit.diffs.size > Commit::DIFF_SAFE_SIZE && !params[:force_show_diff] result[:suppress_diff] = true if commit.diffs.size > Commit::DIFF_SAFE_SIZE && !params[:force_show_diff]
......
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
...@@ -4,7 +4,7 @@ class IssuesListContext < BaseContext ...@@ -4,7 +4,7 @@ class IssuesListContext < BaseContext
attr_accessor :issues attr_accessor :issues
def execute def execute
@issues = case params[:f] @issues = case params[:status]
when issues_filter[:all] then @project.issues when issues_filter[:all] then @project.issues
when issues_filter[:closed] then @project.issues.closed when issues_filter[:closed] then @project.issues.closed
when issues_filter[:to_me] then @project.issues.opened.assigned(current_user) when issues_filter[:to_me] then @project.issues.opened.assigned(current_user)
......
...@@ -9,7 +9,7 @@ module Notes ...@@ -9,7 +9,7 @@ module Notes
@notes = case target_type @notes = case target_type
when "commit" when "commit"
project.commit_notes(project.commit(target_id)).fresh project.notes.for_commit_id(target_id).not_inline.fresh
when "issue" when "issue"
project.issues.find(target_id).notes.inc_author.fresh project.issues.find(target_id).notes.inc_author.fresh
when "merge_request" when "merge_request"
...@@ -18,7 +18,7 @@ module Notes ...@@ -18,7 +18,7 @@ module Notes
project.snippets.find(target_id).notes.fresh project.snippets.find(target_id).notes.fresh
when "wall" when "wall"
# this is the only case, where the order is DESC # 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 end
@notes = if after_id @notes = if after_id
......
class TestHookContext < BaseContext class TestHookContext < BaseContext
def execute def execute
hook = project.hooks.find(params[:id]) 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) data = project.post_receive_data(commits.last.id, commits.first.id, "refs/heads/#{project.default_branch}", current_user)
hook.execute(data) hook.execute(data)
end end
......
...@@ -3,10 +3,6 @@ class Admin::DashboardController < AdminController ...@@ -3,10 +3,6 @@ class Admin::DashboardController < AdminController
@projects = Project.order("created_at DESC").limit(10) @projects = Project.order("created_at DESC").limit(10)
@users = User.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 rescue Redis::InheritedError
@resque_accessible = false @resque_accessible = false
end end
......
class Admin::GroupsController < AdminController 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 def index
@groups = Group.order('name ASC') @groups = Group.order('name ASC')
...@@ -12,6 +12,8 @@ class Admin::GroupsController < AdminController ...@@ -12,6 +12,8 @@ class Admin::GroupsController < AdminController
@projects = @projects.not_in_group(@group) if @group.projects.present? @projects = @projects.not_in_group(@group) if @group.projects.present?
@projects = @projects.all @projects = @projects.all
@projects.reject!(&:empty_repo?) @projects.reject!(&:empty_repo?)
@users = User.active
end end
def new def new
...@@ -65,7 +67,14 @@ class Admin::GroupsController < AdminController ...@@ -65,7 +67,14 @@ class Admin::GroupsController < AdminController
redirect_to :back, notice: 'Group was successfully updated.' redirect_to :back, notice: 'Group was successfully updated.'
end 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 def destroy
@group.truncate_teams
@group.destroy @group.destroy
redirect_to admin_groups_path, notice: 'Group was successfully deleted.' redirect_to admin_groups_path, notice: 'Group was successfully deleted.'
......
...@@ -10,6 +10,7 @@ class Admin::ProjectsController < AdminController ...@@ -10,6 +10,7 @@ class Admin::ProjectsController < AdminController
end end
def show def show
@repository = @project.repository
@users = User.active @users = User.active
@users = @users.not_in_project(@project) if @project.users.present? @users = @users.not_in_project(@project) if @project.users.present?
@users = @users.all @users = @users.all
...@@ -19,7 +20,7 @@ class Admin::ProjectsController < AdminController ...@@ -19,7 +20,7 @@ class Admin::ProjectsController < AdminController
end end
def team_update 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.' redirect_to [:admin, @project], notice: 'Project was successfully updated.'
end end
...@@ -35,6 +36,9 @@ class Admin::ProjectsController < AdminController ...@@ -35,6 +36,9 @@ class Admin::ProjectsController < AdminController
end end
def destroy def destroy
# Delete team first in order to prevent multiple gitolite calls
@project.team.truncate
@project.destroy @project.destroy
redirect_to admin_projects_path, notice: 'Project was successfully deleted.' redirect_to admin_projects_path, notice: 'Project was successfully deleted.'
......
...@@ -3,13 +3,13 @@ class Admin::UsersController < AdminController ...@@ -3,13 +3,13 @@ class Admin::UsersController < AdminController
@admin_users = User.scoped @admin_users = User.scoped
@admin_users = @admin_users.filter(params[:filter]) @admin_users = @admin_users.filter(params[:filter])
@admin_users = @admin_users.search(params[:name]) if params[:name].present? @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 end
def show def show
@admin_user = User.find(params[:id]) @admin_user = User.find(params[:id])
@projects = if @admin_user.projects.empty? @projects = if @admin_user.authorized_projects.empty?
Project Project
else else
Project.without_user(@admin_user) Project.without_user(@admin_user)
...@@ -19,9 +19,9 @@ class Admin::UsersController < AdminController ...@@ -19,9 +19,9 @@ class Admin::UsersController < AdminController
def team_update def team_update
@admin_user = User.find(params[:id]) @admin_user = User.find(params[:id])
UsersProject.user_bulk_import( UsersProject.add_users_into_projects(
@admin_user,
params[:project_ids], params[:project_ids],
[@admin_user.id],
params[:project_access] params[:project_access]
) )
...@@ -98,7 +98,7 @@ class Admin::UsersController < AdminController ...@@ -98,7 +98,7 @@ class Admin::UsersController < AdminController
def destroy def destroy
@admin_user = User.find(params[:id]) @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 redirect_to admin_users_path, alert: "User is a project owner and can't be removed." and return
end end
@admin_user.destroy @admin_user.destroy
......
...@@ -76,6 +76,12 @@ class ApplicationController < ActionController::Base ...@@ -76,6 +76,12 @@ class ApplicationController < ActionController::Base
end end
end end
def repository
@repository ||= project.repository
rescue Grit::NoSuchPathError
nil
end
def add_abilities def add_abilities
abilities << Ability abilities << Ability
end end
......
...@@ -9,10 +9,10 @@ class CommitsController < ProjectResourceController ...@@ -9,10 +9,10 @@ class CommitsController < ProjectResourceController
before_filter :require_non_empty_project before_filter :require_non_empty_project
def show def show
@repo = @project.repo @repo = @project.repository
@limit, @offset = (params[:limit] || 40), (params[:offset] || 0) @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) @commits = CommitDecorator.decorate(@commits)
respond_to do |format| respond_to do |format|
......
...@@ -20,7 +20,7 @@ class DashboardController < ApplicationController ...@@ -20,7 +20,7 @@ class DashboardController < ApplicationController
@projects = @projects.page(params[:page]).per(30) @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 = @event_filter.apply_filter(@events)
@events = @events.limit(20).offset(params[:offset] || 0) @events = @events.limit(20).offset(params[:offset] || 0)
...@@ -36,14 +36,14 @@ class DashboardController < ApplicationController ...@@ -36,14 +36,14 @@ class DashboardController < ApplicationController
# Get authored or assigned open merge requests # Get authored or assigned open merge requests
def merge_requests def merge_requests
@merge_requests = current_user.cared_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) @merge_requests = @merge_requests.recent.page(params[:page]).per(20)
end end
# Get only assigned issues # Get only assigned issues
def issues def issues
@issues = current_user.assigned_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.recent.page(params[:page]).per(20)
@issues = @issues.includes(:author, :project) @issues = @issues.includes(:author, :project)
...@@ -60,25 +60,7 @@ class DashboardController < ApplicationController ...@@ -60,25 +60,7 @@ class DashboardController < ApplicationController
end end
def event_filter def event_filter
@event_filter ||= EventFilter.new(params[:event_filter]) filters = cookies['event_filter'].split(',') if cookies['event_filter']
end @event_filter ||= EventFilter.new(filters)
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
end end
end end
...@@ -21,15 +21,16 @@ class GroupsController < ApplicationController ...@@ -21,15 +21,16 @@ class GroupsController < ApplicationController
# Get authored or assigned open merge requests # Get authored or assigned open merge requests
def merge_requests def merge_requests
@merge_requests = current_user.cared_merge_requests.opened @merge_requests = current_user.cared_merge_requests.of_group(@group)
@merge_requests = @merge_requests.of_group(@group).recent.page(params[:page]).per(20) @merge_requests = FilterContext.new(@merge_requests, params).execute
@merge_requests = @merge_requests.recent.page(params[:page]).per(20)
end end
# Get only assigned issues # Get only assigned issues
def issues def issues
@user = current_user @issues = current_user.assigned_issues.of_group(@group)
@issues = current_user.assigned_issues.opened @issues = FilterContext.new(@issues, params).execute
@issues = @issues.of_group(@group).recent.page(params[:page]).per(20) @issues = @issues.recent.page(params[:page]).per(20)
@issues = @issues.includes(:author, :project) @issues = @issues.includes(:author, :project)
respond_to do |format| respond_to do |format|
...@@ -44,6 +45,7 @@ class GroupsController < ApplicationController ...@@ -44,6 +45,7 @@ class GroupsController < ApplicationController
@projects = result[:projects] @projects = result[:projects]
@merge_requests = result[:merge_requests] @merge_requests = result[:merge_requests]
@issues = result[:issues] @issues = result[:issues]
@wiki_pages = result[:wiki_pages]
end end
def people def people
...@@ -53,9 +55,16 @@ class GroupsController < ApplicationController ...@@ -53,9 +55,16 @@ class GroupsController < ApplicationController
if @project if @project
@team_member = @project.users_projects.new @team_member = @project.users_projects.new
else
@team_member = UsersProject.new
end end
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 protected
def group def group
...@@ -63,7 +72,7 @@ class GroupsController < ApplicationController ...@@ -63,7 +72,7 @@ class GroupsController < ApplicationController
end end
def projects 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 end
def project_ids def project_ids
......
...@@ -74,6 +74,8 @@ class MergeRequestsController < ProjectResourceController ...@@ -74,6 +74,8 @@ class MergeRequestsController < ProjectResourceController
@merge_request.check_if_can_be_merged @merge_request.check_if_can_be_merged
end end
render json: {state: @merge_request.human_state} render json: {state: @merge_request.human_state}
rescue Gitlab::SatelliteNotExistError
render json: {state: :no_satellite}
end end
def automerge def automerge
...@@ -88,12 +90,12 @@ class MergeRequestsController < ProjectResourceController ...@@ -88,12 +90,12 @@ class MergeRequestsController < ProjectResourceController
end end
def branch_from def branch_from
@commit = project.commit(params[:ref]) @commit = @repository.commit(params[:ref])
@commit = CommitDecorator.decorate(@commit) @commit = CommitDecorator.decorate(@commit)
end end
def branch_to def branch_to
@commit = project.commit(params[:ref]) @commit = @repository.commit(params[:ref])
@commit = CommitDecorator.decorate(@commit) @commit = CommitDecorator.decorate(@commit)
end end
......
class ProjectResourceController < ApplicationController class ProjectResourceController < ApplicationController
before_filter :project before_filter :project
before_filter :repository
end end
...@@ -2,6 +2,7 @@ require Rails.root.join('lib', 'gitlab', 'graph', 'json_builder') ...@@ -2,6 +2,7 @@ require Rails.root.join('lib', 'gitlab', 'graph', 'json_builder')
class ProjectsController < ProjectResourceController class ProjectsController < ProjectResourceController
skip_before_filter :project, only: [:new, :create] skip_before_filter :project, only: [:new, :create]
skip_before_filter :repository, only: [:new, :create]
# Authorize # Authorize
before_filter :authorize_read_project!, except: [:index, :new, :create] before_filter :authorize_read_project!, except: [:index, :new, :create]
...@@ -58,7 +59,7 @@ class ProjectsController < ProjectResourceController ...@@ -58,7 +59,7 @@ class ProjectsController < ProjectResourceController
respond_to do |format| respond_to do |format|
format.html do format.html do
unless @project.empty_repo? if @project.repository && !@project.repository.empty?
@last_push = current_user.recent_push(@project.id) @last_push = current_user.recent_push(@project.id)
render :show render :show
else else
...@@ -102,11 +103,10 @@ class ProjectsController < ProjectResourceController ...@@ -102,11 +103,10 @@ class ProjectsController < ProjectResourceController
def destroy def destroy
return access_denied! unless can?(current_user, :remove_project, project) return access_denied! unless can?(current_user, :remove_project, project)
# Disable the UsersProject update_repository call, otherwise it will be # Delete team first in order to prevent multiple gitolite calls
# called once for every person removed from the project project.team.truncate
UsersProject.skip_callback(:destroy, :after, :update_repository)
project.destroy project.destroy
UsersProject.set_callback(:destroy, :after, :update_repository)
respond_to do |format| respond_to do |format|
format.html { redirect_to root_path } format.html { redirect_to root_path }
......
...@@ -12,7 +12,7 @@ class RefsController < ProjectResourceController ...@@ -12,7 +12,7 @@ class RefsController < ProjectResourceController
respond_to do |format| respond_to do |format|
format.html do format.html do
new_path = if params[:destination] == "tree" new_path = if params[:destination] == "tree"
project_tree_path(@project, @ref) project_tree_path(@project, (@ref + "/" + params[:path]))
else else
project_commits_path(@project, @ref) project_commits_path(@project, @ref)
end end
...@@ -31,7 +31,7 @@ class RefsController < ProjectResourceController ...@@ -31,7 +31,7 @@ class RefsController < ProjectResourceController
contents = @tree.contents contents = @tree.contents
@logs = contents.map do |content| @logs = contents.map do |content|
file = params[:path] ? File.join(params[:path], content.name) : content.name 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) last_commit = CommitDecorator.decorate(last_commit)
{ {
file_name: content.name, file_name: content.name,
...@@ -45,10 +45,10 @@ class RefsController < ProjectResourceController ...@@ -45,10 +45,10 @@ class RefsController < ProjectResourceController
def define_tree_vars def define_tree_vars
params[:path] = nil if params[:path].blank? params[:path] = nil if params[:path].blank?
@repo = project.repo @repo = project.repository
@commit = project.commit(@ref) @commit = @repo.commit(@ref)
@commit = CommitDecorator.decorate(@commit) @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) @tree = TreeDecorator.new(@tree)
@hex_path = Digest::SHA1.hexdigest(params[:path] || "") @hex_path = Digest::SHA1.hexdigest(params[:path] || "")
......
...@@ -5,19 +5,19 @@ class RepositoriesController < ProjectResourceController ...@@ -5,19 +5,19 @@ class RepositoriesController < ProjectResourceController
before_filter :require_non_empty_project before_filter :require_non_empty_project
def show def show
@activities = @project.commits_with_refs(20) @activities = @repository.commits_with_refs(20)
end end
def branches def branches
@branches = @project.branches @branches = @repository.branches
end end
def tags def tags
@tags = @project.tags @tags = @repository.tags
end end
def stats def stats
@stats = Gitlab::GitStats.new(@project.repo, @project.root_ref) @stats = Gitlab::GitStats.new(@repository.raw, @repository.root_ref)
@graph = @stats.graph @graph = @stats.graph
end end
...@@ -27,7 +27,7 @@ class RepositoriesController < ProjectResourceController ...@@ -27,7 +27,7 @@ class RepositoriesController < ProjectResourceController
end end
file_path = @project.archive_repo(params[:ref]) file_path = @repository.archive_repo(params[:ref])
if file_path if file_path
# Send file to user # Send file to user
......
class SearchController < ApplicationController class SearchController < ApplicationController
def show 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] @projects = result[:projects]
@merge_requests = result[:merge_requests] @merge_requests = result[:merge_requests]
......
...@@ -26,7 +26,7 @@ class ServicesController < ProjectResourceController ...@@ -26,7 +26,7 @@ class ServicesController < ProjectResourceController
end end
def test 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) data = project.post_receive_data(commits.last.id, commits.first.id, "refs/heads/#{project.default_branch}", current_user)
@service = project.gitlab_ci_service @service = project.gitlab_ci_service
......
...@@ -16,7 +16,7 @@ class SnippetsController < ProjectResourceController ...@@ -16,7 +16,7 @@ class SnippetsController < ProjectResourceController
respond_to :html respond_to :html
def index def index
@snippets = @project.snippets.fresh @snippets = @project.snippets.fresh.non_expired
end end
def new def new
......
...@@ -16,10 +16,9 @@ class TeamMembersController < ProjectResourceController ...@@ -16,10 +16,9 @@ class TeamMembersController < ProjectResourceController
end end
def create def create
@project.add_users_ids_to_team( users = User.where(id: params[:user_ids])
params[:user_ids],
params[:project_access] @project.team << [users, params[:project_access]]
)
if params[:redirect_to] if params[:redirect_to]
redirect_to params[:redirect_to] redirect_to params[:redirect_to]
...@@ -50,7 +49,7 @@ class TeamMembersController < ProjectResourceController ...@@ -50,7 +49,7 @@ class TeamMembersController < ProjectResourceController
def apply_import def apply_import
giver = Project.find(params[:source_project_id]) giver = Project.find(params[:source_project_id])
status = UsersProject.import_team(giver, project) status = @project.team.import(giver)
notice = status ? "Succesfully imported" : "Import failed" notice = status ? "Succesfully imported" : "Import failed"
redirect_to project_team_members_path(project), notice: notice redirect_to project_team_members_path(project), notice: notice
......
...@@ -22,7 +22,7 @@ class TreeController < ProjectResourceController ...@@ -22,7 +22,7 @@ class TreeController < ProjectResourceController
end end
def edit def edit
@last_commit = @project.last_commit_for(@ref, @path).sha @last_commit = @project.repository.last_commit_for(@ref, @path).sha
end end
def update def update
......
...@@ -6,16 +6,14 @@ class TreeDecorator < ApplicationDecorator ...@@ -6,16 +6,14 @@ class TreeDecorator < ApplicationDecorator
part_path = "" part_path = ""
parts = path.split("\/") parts = path.split("\/")
#parts = parts[0...-1] if is_blob? yield('..', nil) if parts.count > max_links
yield(h.link_to("..", "#")) if parts.count > max_links
parts.each do |part| parts.each do |part|
part_path = File.join(part_path, part) unless part_path.empty? part_path = File.join(part_path, part) unless part_path.empty?
part_path = part if part_path.empty? part_path = part if part_path.empty?
next unless parts.last(2).include?(part) if parts.count > max_links 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 end
end end
...@@ -26,7 +24,7 @@ class TreeDecorator < ApplicationDecorator ...@@ -26,7 +24,7 @@ class TreeDecorator < ApplicationDecorator
def up_dir_path def up_dir_path
file = File.join(path, "..") file = File.join(path, "..")
h.project_tree_path(project, h.tree_join(ref, file)) h.tree_join(ref, file)
end end
def readme def readme
......
...@@ -53,7 +53,7 @@ module ApplicationHelper ...@@ -53,7 +53,7 @@ module ApplicationHelper
def last_commit(project) def last_commit(project)
if project.repo_exists? if project.repo_exists?
time_ago_in_words(project.commit.committed_date) + " ago" time_ago_in_words(project.repository.commit.committed_date) + " ago"
else else
"Never" "Never"
end end
...@@ -62,9 +62,11 @@ module ApplicationHelper ...@@ -62,9 +62,11 @@ module ApplicationHelper
end end
def grouped_options_refs(destination = :tree) def grouped_options_refs(destination = :tree)
repository = @project.repository
options = [ options = [
["Branch", @project.branch_names ], ["Branch", repository.branch_names ],
[ "Tag", @project.tag_names ] [ "Tag", repository.tag_names ]
] ]
# If reference is commit id - # If reference is commit id -
...@@ -78,7 +80,8 @@ module ApplicationHelper ...@@ -78,7 +80,8 @@ module ApplicationHelper
end end
def search_autocomplete_source 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 = [ default_nav = [
{ label: "My Profile", url: profile_path }, { label: "My Profile", url: profile_path },
...@@ -99,21 +102,21 @@ module ApplicationHelper ...@@ -99,21 +102,21 @@ module ApplicationHelper
] ]
project_nav = [] project_nav = []
if @project && !@project.new_record? if @project && @project.repository && @project.repository.root_ref
project_nav = [ project_nav = [
{ label: "#{@project.name} Issues", url: project_issues_path(@project) }, { 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} Merge Requests", url: project_merge_requests_path(@project) },
{ label: "#{@project.name} Milestones", url: project_milestones_path(@project) }, { label: "#{@project.name} Milestones", url: project_milestones_path(@project) },
{ label: "#{@project.name} Snippets", url: project_snippets_path(@project) }, { label: "#{@project.name} Snippets", url: project_snippets_path(@project) },
{ label: "#{@project.name} Team", url: project_team_index_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} Wall", url: wall_project_path(@project) },
{ label: "#{@project.name} Wiki", url: project_wikis_path(@project) }, { label: "#{@project.name} Wiki", url: project_wikis_path(@project) },
] ]
end end
[projects, default_nav, project_nav, help_nav].flatten.to_json [groups, projects, default_nav, project_nav, help_nav].flatten.to_json
end end
def emoji_autocomplete_source def emoji_autocomplete_source
...@@ -139,6 +142,7 @@ module ApplicationHelper ...@@ -139,6 +142,7 @@ module ApplicationHelper
event.last_push_to_non_root? && event.last_push_to_non_root? &&
!event.rm_ref? && !event.rm_ref? &&
event.project && event.project &&
event.project.repository &&
event.project.merge_requests_enabled event.project.merge_requests_enabled
end end
......
...@@ -70,4 +70,12 @@ module CommitsHelper ...@@ -70,4 +70,12 @@ module CommitsHelper
escape_javascript(render 'commits/commit', commit: commit) escape_javascript(render 'commits/commit', commit: commit)
end end
end end
def diff_line_content(line)
if line.blank?
" &nbsp;"
else
line
end
end
end end
...@@ -20,25 +20,8 @@ module EventsHelper ...@@ -20,25 +20,8 @@ module EventsHelper
[event.action_name, target].join(" ") [event.action_name, target].join(" ")
end 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 def event_filter_link key, tooltip
key = key.to_s key = key.to_s
filter = @event_filter.options key
inactive = if @event_filter.active? key inactive = if @event_filter.active? key
nil nil
else else
...@@ -46,7 +29,7 @@ module EventsHelper ...@@ -46,7 +29,7 @@ module EventsHelper
end end
content_tag :div, class: "filter_icon #{inactive}" do 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" image_tag "event_filter_#{key}.png"
end end
end end
......
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
...@@ -4,7 +4,7 @@ module MergeRequestsHelper ...@@ -4,7 +4,7 @@ module MergeRequestsHelper
event.project, event.project,
merge_request: { merge_request: {
source_branch: event.branch_name, source_branch: event.branch_name,
target_branch: event.project.root_ref, target_branch: event.project.repository.root_ref,
title: event.branch_name.titleize title: event.branch_name.titleize
} }
) )
......
module NamespacesHelper module NamespacesHelper
def namespaces_options(selected = :current_user, scope = :default) 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 users = if scope == :all
Namespace.root Namespace.root
......
class Notify < ActionMailer::Base class Notify < ActionMailer::Base
include Resque::Mailer
add_template_helper ApplicationHelper add_template_helper ApplicationHelper
add_template_helper GitlabMarkdownHelper add_template_helper GitlabMarkdownHelper
default_url_options[:host] = Gitlab.config.gitlab.host default_url_options[:host] = Gitlab.config.gitlab.host
default_url_options[:protocol] = Gitlab.config.gitlab.protocol 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[: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 default from: Gitlab.config.gitlab.email_from
...@@ -87,7 +88,7 @@ class Notify < ActionMailer::Base ...@@ -87,7 +88,7 @@ class Notify < ActionMailer::Base
def note_wall_email(recipient_id, note_id) def note_wall_email(recipient_id, note_id)
@note = Note.find(note_id) @note = Note.find(note_id)
@project = @note.project @project = @note.project
mail(to: recipient(recipient_id), subject: subject) mail(to: recipient(recipient_id), subject: subject("note on wall"))
end end
...@@ -147,12 +148,15 @@ class Notify < ActionMailer::Base ...@@ -147,12 +148,15 @@ class Notify < ActionMailer::Base
# >> @project = Project.last # >> @project = Project.last
# => #<Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...> # => #<Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
# >> subject('Lorem ipsum') # >> subject('Lorem ipsum')
# => "GitLab | Lorem ipsum | Ruby on Rails" # => "GitLab | Ruby on Rails | Lorem ipsum "
# #
# # Accepts multiple arguments # # Accepts multiple arguments
# >> subject('Lorem ipsum', 'Dolor sit amet') # >> subject('Lorem ipsum', 'Dolor sit amet')
# => "GitLab | Lorem ipsum | Dolor sit amet" # => "GitLab | Lorem ipsum | Dolor sit amet"
def subject(*extra) 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
end end
...@@ -15,35 +15,26 @@ class Ability ...@@ -15,35 +15,26 @@ class Ability
def project_abilities(user, project) def project_abilities(user, project)
rules = [] rules = []
team = project.team
# Rules based on role in project # Rules based on role in project
if project.master_access_for?(user) if team.masters.include?(user)
rules << project_master_rules rules << project_master_rules
elsif project.dev_access_for?(user) elsif team.developers.include?(user)
rules << project_dev_rules rules << project_dev_rules
elsif project.report_access_for?(user) elsif team.reporters.include?(user)
rules << project_report_rules rules << project_report_rules
elsif project.guest_access_for?(user) elsif team.guests.include?(user)
rules << project_guest_rules rules << project_guest_rules
end end
if project.namespace if project.owner == user
# If user own project namespace rules << project_admin_rules
# (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
end end
rules.flatten rules.flatten
end end
...@@ -107,9 +98,12 @@ class Ability ...@@ -107,9 +98,12 @@ class Ability
def group_abilities user, group def group_abilities user, group
rules = [] rules = []
rules << [ # Only group owner and administrators can manage group
:manage_group if group.owner == user || user.admin?
] if group.owner == user rules << [
:manage_group
]
end
rules.flatten rules.flatten
end end
......
...@@ -11,7 +11,7 @@ class Commit ...@@ -11,7 +11,7 @@ class Commit
attr_accessor :commit, :head, :refs attr_accessor :commit, :head, :refs
delegate :message, :authored_date, :committed_date, :parents, :sha, 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 :to_patch, to: :commit
class << self class << self
...@@ -83,8 +83,8 @@ class Commit ...@@ -83,8 +83,8 @@ class Commit
return result unless from && to return result unless from && to
first = project.commit(to.try(:strip)) first = project.repository.commit(to.try(:strip))
last = project.commit(from.try(:strip)) last = project.repository.commit(from.try(:strip))
if first && last if first && last
result[:same] = (first.id == last.id) result[:same] = (first.id == last.id)
...@@ -98,6 +98,8 @@ class Commit ...@@ -98,6 +98,8 @@ class Commit
end end
def initialize(raw_commit, head = nil) def initialize(raw_commit, head = nil)
raise "Nil as raw commit passed" unless raw_commit
@commit = raw_commit @commit = raw_commit
@head = head @head = head
end end
...@@ -136,17 +138,17 @@ class Commit ...@@ -136,17 +138,17 @@ class Commit
end end
def prev_commit def prev_commit
parents.try :first @prev_commit ||= if parents.present?
Commit.new(parents.first)
else
nil
end
end end
def prev_commit_id def prev_commit_id
prev_commit.try :id prev_commit.try :id
end end
def parents_count
parents && parents.count || 0
end
# Shows the diff between the commit's parent and the commit. # 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. # Cuts out the header and stats from #to_patch and returns only the diff.
......
# == Issuable concern
#
# Contains common functionality shared between Issues and MergeRequests # Contains common functionality shared between Issues and MergeRequests
module IssueCommonality #
# Used by Issue, MergeRequest
#
module Issuable
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
...@@ -64,4 +69,38 @@ module IssueCommonality ...@@ -64,4 +69,38 @@ module IssueCommonality
closed_changed? && !closed closed_changed? && !closed
end 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 end
...@@ -15,9 +15,6 @@ ...@@ -15,9 +15,6 @@
# #
class Event < ActiveRecord::Base class Event < ActiveRecord::Base
include NoteEvent
include PushEvent
attr_accessible :project, :action, :data, :author_id, :project_id, attr_accessible :project, :action, :data, :author_id, :project_id,
:target_id, :target_type :target_id, :target_type
...@@ -113,26 +110,6 @@ class Event < ActiveRecord::Base ...@@ -113,26 +110,6 @@ class Event < ActiveRecord::Base
target_type == "MergeRequest" target_type == "MergeRequest"
end 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? def joined?
action == Joined action == Joined
end end
...@@ -170,4 +147,143 @@ class Event < ActiveRecord::Base ...@@ -170,4 +147,143 @@ class Event < ActiveRecord::Base
"opened" "opened"
end end
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 end
...@@ -23,20 +23,12 @@ class GitlabCiService < Service ...@@ -23,20 +23,12 @@ class GitlabCiService < Service
after_save :compose_service_hook, if: :activated? after_save :compose_service_hook, if: :activated?
def activated?
active
end
def compose_service_hook def compose_service_hook
hook = service_hook || build_service_hook hook = service_hook || build_service_hook
hook.url = [project_url, "/build", "?token=#{token}"].join("") hook.url = [project_url, "/build", "?token=#{token}"].join("")
hook.save hook.save
end end
def commit_badge_path sha
project_url + "/status?sha=#{sha}"
end
def commit_status_path sha def commit_status_path sha
project_url + "/builds/#{sha}/status.json?token=#{token}" project_url + "/builds/#{sha}/status.json?token=#{token}"
end end
......
...@@ -12,6 +12,14 @@ ...@@ -12,6 +12,14 @@
# #
class Group < Namespace 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 def users
users = User.joins(:users_projects).where(users_projects: {project_id: project_ids}) users = User.joins(:users_projects).where(users_projects: {project_id: project_ids})
users = users << owner users = users << owner
...@@ -21,4 +29,8 @@ class Group < Namespace ...@@ -21,4 +29,8 @@ class Group < Namespace
def human_name def human_name
name name
end end
def truncate_teams
UsersProject.truncate_teams(project_ids)
end
end end
...@@ -17,8 +17,7 @@ ...@@ -17,8 +17,7 @@
# #
class Issue < ActiveRecord::Base class Issue < ActiveRecord::Base
include IssueCommonality include Issuable
include Votes
attr_accessible :title, :assignee_id, :closed, :position, :description, attr_accessible :title, :assignee_id, :closed, :position, :description,
:milestone_id, :label_list, :author_id_of_changes :milestone_id, :label_list, :author_id_of_changes
......
...@@ -73,7 +73,7 @@ class Key < ActiveRecord::Base ...@@ -73,7 +73,7 @@ class Key < ActiveRecord::Base
if is_deploy_key if is_deploy_key
[project] [project]
else else
user.projects user.authorized_projects
end end
end end
......
...@@ -20,11 +20,10 @@ ...@@ -20,11 +20,10 @@
# #
require Rails.root.join("app/models/commit") 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 class MergeRequest < ActiveRecord::Base
include IssueCommonality include Issuable
include Votes
attr_accessible :title, :assignee_id, :closed, :target_branch, :source_branch, :milestone_id, attr_accessible :title, :assignee_id, :closed, :target_branch, :source_branch, :milestone_id,
:author_id_of_changes :author_id_of_changes
......
...@@ -29,7 +29,7 @@ class Milestone < ActiveRecord::Base ...@@ -29,7 +29,7 @@ class Milestone < ActiveRecord::Base
def expired? def expired?
if due_date if due_date
due_date < Date.today due_date.past?
else else
false false
end end
...@@ -58,7 +58,13 @@ class Milestone < ActiveRecord::Base ...@@ -58,7 +58,13 @@ class Milestone < ActiveRecord::Base
end end
def expires_at 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 end
def can_be_closed? def can_be_closed?
......
...@@ -27,10 +27,13 @@ class Namespace < ActiveRecord::Base ...@@ -27,10 +27,13 @@ class Namespace < ActiveRecord::Base
after_create :ensure_dir_exist after_create :ensure_dir_exist
after_update :move_dir after_update :move_dir
after_commit :update_gitolite, on: :update, if: :require_update_gitolite
after_destroy :rm_dir after_destroy :rm_dir
scope :root, where('type IS NULL') scope :root, where('type IS NULL')
attr_accessor :require_update_gitolite
def self.search query def self.search query
where("name LIKE :query OR path LIKE :query", query: "%#{query}%") where("name LIKE :query OR path LIKE :query", query: "%#{query}%")
end end
...@@ -48,8 +51,17 @@ class Namespace < ActiveRecord::Base ...@@ -48,8 +51,17 @@ class Namespace < ActiveRecord::Base
end end
def ensure_dir_exist def ensure_dir_exist
namespace_dir_path = File.join(Gitlab.config.gitolite.repos_path, path) unless dir_exists?
system("mkdir -m 770 #{namespace_dir_path}") unless File.exists?(namespace_dir_path) 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 end
def move_dir def move_dir
...@@ -59,16 +71,25 @@ class Namespace < ActiveRecord::Base ...@@ -59,16 +71,25 @@ class Namespace < ActiveRecord::Base
if File.exists?(new_path) if File.exists?(new_path)
raise "Already exists" raise "Already exists"
end end
if system("mv #{old_path} #{new_path}") begin
FileUtils.mv( old_path, new_path )
send_update_instructions send_update_instructions
@require_update_gitolite = true
rescue Exception => e
raise "Namespace move error #{old_path} #{new_path}"
end end
end end
end end
def update_gitolite
@require_update_gitolite = false
projects.each(&:update_repository)
end
def rm_dir def rm_dir
dir_path = File.join(Gitlab.config.gitolite.repos_path, path) dir_path = File.join(Gitlab.config.gitolite.repos_path, path)
system("rm -rf #{dir_path}") FileUtils.rm_r( dir_path, force: true )
end end
def send_update_instructions def send_update_instructions
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
# #
# id :integer not null, primary key # id :integer not null, primary key
# note :text # note :text
# noteable_id :string(255)
# noteable_type :string(255) # noteable_type :string(255)
# author_id :integer # author_id :integer
# created_at :datetime not null # created_at :datetime not null
...@@ -12,6 +11,8 @@ ...@@ -12,6 +11,8 @@
# project_id :integer # project_id :integer
# attachment :string(255) # attachment :string(255)
# line_code :string(255) # line_code :string(255)
# commit_id :string(255)
# noteable_id :integer
# #
require 'carrierwave/orm/activerecord' require 'carrierwave/orm/activerecord'
...@@ -41,11 +42,11 @@ class Note < ActiveRecord::Base ...@@ -41,11 +42,11 @@ class Note < ActiveRecord::Base
mount_uploader :attachment, AttachmentUploader mount_uploader :attachment, AttachmentUploader
# Scopes # Scopes
scope :for_commits, ->{ where(noteable_type: "Commit") } scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
scope :common, ->{ where(noteable_id: nil, commit_id: nil) } scope :inline, where("line_code IS NOT NULL")
scope :today, ->{ where("created_at >= :date", date: Date.today) } scope :not_inline, where("line_code IS NULL")
scope :last_week, ->{ where("created_at >= :date", date: (Date.today - 7.days)) }
scope :since, ->(day) { where("created_at >= :date", date: (day)) } scope :common, ->{ where(noteable_type: ["", nil]) }
scope :fresh, ->{ order("created_at ASC, id ASC") } scope :fresh, ->{ order("created_at ASC, id ASC") }
scope :inc_author_project, ->{ includes(:project, :author) } scope :inc_author_project, ->{ includes(:project, :author) }
scope :inc_author, ->{ includes(:author) } scope :inc_author, ->{ includes(:author) }
...@@ -126,7 +127,7 @@ class Note < ActiveRecord::Base ...@@ -126,7 +127,7 @@ class Note < ActiveRecord::Base
# override to return commits, which are not active record # override to return commits, which are not active record
def noteable def noteable
if for_commit? if for_commit?
project.commit(commit_id) project.repository.commit(commit_id)
else else
super super
end end
......
This diff is collapsed.
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# #
class ProtectedBranch < ActiveRecord::Base class ProtectedBranch < ActiveRecord::Base
include GitHost include Gitolited
attr_accessible :name attr_accessible :name
...@@ -22,10 +22,10 @@ class ProtectedBranch < ActiveRecord::Base ...@@ -22,10 +22,10 @@ class ProtectedBranch < ActiveRecord::Base
after_destroy :update_repository after_destroy :update_repository
def update_repository def update_repository
git_host.update_repository(project) gitolite.update_repository(project)
end end
def commit def commit
project.commit(self.name) project.repository.commit(self.name)
end end
end end
module Repository class Repository
include GitHost # Repository directory name with namespace direcotry
# Examples:
# gitlab/gitolite
# diaspora
#
attr_accessor :path_with_namespace
# 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
def valid_repo? # Init grit repo object
repo
end
def raw
repo repo
rescue
errors.add(:path, "Invalid repository path")
false
end end
def empty_repo? def path_to_repo
!repo_exists? || !has_commits? @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 end
def commit(commit_id = nil) def commit(commit_id = nil)
...@@ -40,10 +60,6 @@ module Repository ...@@ -40,10 +60,6 @@ module Repository
Commit.commits_between(repo, from, to) Commit.commits_between(repo, from, to)
end end
def satellite
@satellite ||= Gitlab::Satellite::Satellite.new(self)
end
def has_post_receive_file? def has_post_receive_file?
!!hook_file !!hook_file
end end
...@@ -88,36 +104,6 @@ module Repository ...@@ -88,36 +104,6 @@ module Repository
[branch_names + tag_names].flatten [branch_names + tag_names].flatten
end 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 def heads
@heads ||= repo.heads @heads ||= repo.heads
end end
...@@ -128,13 +114,14 @@ module Repository ...@@ -128,13 +114,14 @@ module Repository
path ? (tree / path) : tree path ? (tree / path) : tree
end end
def open_branches def has_commits?
if protected_branches.empty? !!commit
self.repo.heads rescue Grit::NoSuchPathError
else false
pnames = protected_branches.map(&:name) end
self.repo.heads.reject { |h| pnames.include?(h.name) }
end.sort_by(&:name) def empty?
!has_commits?
end end
# Discovers the default branch based on the repository's available branches # Discovers the default branch based on the repository's available branches
...@@ -153,20 +140,6 @@ module Repository ...@@ -153,20 +140,6 @@ module Repository
end end
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 # Archive Project to .tar.gz
# #
# Already packed repo archives stored at # Already packed repo archives stored at
...@@ -193,17 +166,4 @@ module Repository ...@@ -193,17 +166,4 @@ module Repository
file_path file_path
end 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 end
...@@ -20,4 +20,8 @@ class Service < ActiveRecord::Base ...@@ -20,4 +20,8 @@ class Service < ActiveRecord::Base
has_one :service_hook has_one :service_hook
validates :project_id, presence: true validates :project_id, presence: true
def activated?
active
end
end end
...@@ -19,6 +19,6 @@ class SystemHook < WebHook ...@@ -19,6 +19,6 @@ class SystemHook < WebHook
end end
def async_execute(data) def async_execute(data)
Resque.enqueue(SystemHookWorker, id, data) Sidekiq::Client.enqueue(SystemHookWorker, id, data)
end end
end end
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
class Tree class Tree
include Linguist::BlobHelper include Linguist::BlobHelper
attr_accessor :path, :tree, :project, :ref
attr_accessor :path, :tree, :ref
delegate :contents, :basename, :name, :data, :mime_type, delegate :contents, :basename, :name, :data, :mime_type,
:mode, :size, :text?, :colorize, to: :tree :mode, :size, :text?, :colorize, to: :tree
def initialize(raw_tree, project, ref = nil, path = nil) def initialize(raw_tree, ref = nil, path = nil)
@project, @ref, @path = project, ref, path @ref, @path = ref, path
@tree = if path.present? @tree = if path.present?
raw_tree / path raw_tree / path
else else
......
...@@ -34,8 +34,6 @@ ...@@ -34,8 +34,6 @@
# #
class User < ActiveRecord::Base class User < ActiveRecord::Base
include Account
devise :database_authenticatable, :token_authenticatable, :lockable, devise :database_authenticatable, :token_authenticatable, :lockable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable :recoverable, :rememberable, :trackable, :validatable, :omniauthable
...@@ -51,7 +49,6 @@ class User < ActiveRecord::Base ...@@ -51,7 +49,6 @@ class User < ActiveRecord::Base
has_many :groups, class_name: "Group", foreign_key: :owner_id has_many :groups, class_name: "Group", foreign_key: :owner_id
has_many :keys, dependent: :destroy has_many :keys, dependent: :destroy
has_many :projects, through: :users_projects
has_many :users_projects, dependent: :destroy has_many :users_projects, dependent: :destroy
has_many :issues, foreign_key: :author_id, dependent: :destroy has_many :issues, foreign_key: :author_id, dependent: :destroy
has_many :notes, foreign_key: :author_id, dependent: :destroy has_many :notes, foreign_key: :author_id, dependent: :destroy
...@@ -70,6 +67,8 @@ class User < ActiveRecord::Base ...@@ -70,6 +67,8 @@ class User < ActiveRecord::Base
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
validate :namespace_uniq, if: ->(user) { user.username_changed? }
before_validation :generate_password, on: :create before_validation :generate_password, on: :create
before_save :ensure_authentication_token before_save :ensure_authentication_token
alias_attribute :private_token, :authentication_token alias_attribute :private_token, :authentication_token
...@@ -77,11 +76,14 @@ class User < ActiveRecord::Base ...@@ -77,11 +76,14 @@ class User < ActiveRecord::Base
delegate :path, to: :namespace, allow_nil: true, prefix: true delegate :path, to: :namespace, allow_nil: true, prefix: true
# Scopes # Scopes
scope :not_in_project, ->(project) { where("id not in (:ids)", ids: project.users.map(&:id) ) }
scope :admins, where(admin: true) scope :admins, where(admin: true)
scope :blocked, where(blocked: true) scope :blocked, where(blocked: true)
scope :active, where(blocked: false) scope :active, where(blocked: false)
scope :alphabetically, order('name ASC')
#
# Class methods
#
class << self class << self
def filter filter_name def filter filter_name
case filter_name case filter_name
...@@ -93,6 +95,14 @@ class User < ActiveRecord::Base ...@@ -93,6 +95,14 @@ class User < ActiveRecord::Base
end end
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 def without_projects
where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)') where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)')
end end
...@@ -118,9 +128,158 @@ class User < ActiveRecord::Base ...@@ -118,9 +128,158 @@ class User < ActiveRecord::Base
end end
end end
#
# Instance methods
#
def generate_password def generate_password
if self.force_random_password if self.force_random_password
self.password = self.password_confirmation = Devise.friendly_token.first(8) self.password = self.password_confirmation = Devise.friendly_token.first(8)
end end
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 end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
class UsersProject < ActiveRecord::Base class UsersProject < ActiveRecord::Base
include GitHost include Gitolited
GUEST = 10 GUEST = 10
REPORTER = 20 REPORTER = 20
...@@ -23,87 +23,96 @@ class UsersProject < ActiveRecord::Base ...@@ -23,87 +23,96 @@ class UsersProject < ActiveRecord::Base
belongs_to :user belongs_to :user
belongs_to :project belongs_to :project
after_save :update_repository attr_accessor :skip_git
after_destroy :update_repository
after_save :update_repository, unless: :skip_git?
after_destroy :update_repository, unless: :skip_git?
validates :user, presence: true 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_access, inclusion: { in: [GUEST, REPORTER, DEVELOPER, MASTER] }, presence: true
validates :project, presence: true validates :project, presence: true
delegate :name, :email, to: :user, prefix: 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 class << self
def import_team(source_project, target_project)
UsersProject.without_repository_callback do # Add users to project teams with passed access option
UsersProject.transaction do #
team = source_project.users_projects.all # access can be an integer representing a access code
# or symbol like :master representing role
team.each do |tm| #
# Skip if user already present in team # Ex.
next if target_project.users.include?(tm.user) # add_users_into_projects(
# project_ids,
new_tm = tm.dup # user_ids,
new_tm.id = nil # UsersProject::MASTER
new_tm.project_id = target_project.id # )
new_tm.save #
# 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
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
end end
Gitlab::Gitolite.new.update_repositories(Project.where(id: project_ids))
end end
target_project.update_repository
true true
rescue rescue
false false
end end
def without_repository_callback def truncate_teams(project_ids)
UsersProject.skip_callback(:destroy, :after, :update_repository)
yield
UsersProject.set_callback(:destroy, :after, :update_repository)
end
def bulk_delete(project, user_ids)
UsersProject.transaction do 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 users_project.destroy
end end
Gitlab::Gitolite.new.update_repositories(Project.where(id: project_ids))
end end
end
def bulk_update(project, user_ids, project_access) true
UsersProject.transaction do rescue
UsersProject.where(:user_id => user_ids, :project_id => project.id).each do |users_project| false
users_project.project_access = project_access
users_project.save
end
end
end end
def bulk_import(project, user_ids, project_access) def truncate_team project
UsersProject.transaction do truncate_teams [project.id]
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 end
def user_bulk_import(user, project_ids, project_access) def roles_hash
UsersProject.transaction do {
project_ids.each do |project_id| guest: GUEST,
users_project = UsersProject.new( reporter: REPORTER,
project_access: project_access, developer: DEVELOPER,
) master: MASTER
users_project.project_id = project_id }
users_project.user_id = user.id
users_project.save
end
end
end end
def access_roles def access_roles
...@@ -116,12 +125,8 @@ class UsersProject < ActiveRecord::Base ...@@ -116,12 +125,8 @@ class UsersProject < ActiveRecord::Base
end end
end end
def role_access
project_access
end
def update_repository def update_repository
git_host.update_repository(project) gitolite.update_repository(project)
end end
def project_access_human def project_access_human
...@@ -131,4 +136,8 @@ class UsersProject < ActiveRecord::Base ...@@ -131,4 +136,8 @@ class UsersProject < ActiveRecord::Base
def repo_access_human def repo_access_human
self.class.access_roles.invert[self.project_access] self.class.access_roles.invert[self.project_access]
end end
def skip_git?
!!@skip_git
end
end end
...@@ -50,5 +50,4 @@ class Wiki < ActiveRecord::Base ...@@ -50,5 +50,4 @@ class Wiki < ActiveRecord::Base
def set_slug def set_slug
self.slug = self.title.parameterize self.slug = self.title.parameterize
end end
end end
...@@ -3,7 +3,7 @@ class IssueObserver < ActiveRecord::Observer ...@@ -3,7 +3,7 @@ class IssueObserver < ActiveRecord::Observer
def after_create(issue) def after_create(issue)
if issue.assignee && issue.assignee != current_user if issue.assignee && issue.assignee != current_user
Notify.new_issue_email(issue.id).deliver Notify.delay.new_issue_email(issue.id)
end end
end end
...@@ -16,7 +16,7 @@ class IssueObserver < ActiveRecord::Observer ...@@ -16,7 +16,7 @@ class IssueObserver < ActiveRecord::Observer
if status if status
Note.create_status_change_note(issue, current_user, status) Note.create_status_change_note(issue, current_user, status)
[issue.author, issue.assignee].compact.each do |recipient| [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 end
end end
...@@ -27,7 +27,7 @@ class IssueObserver < ActiveRecord::Observer ...@@ -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 = [issue.assignee_id, issue.assignee_id_was].keep_if {|id| id && id != current_user.id }
recipient_ids.each do |recipient_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 end
end end
class KeyObserver < ActiveRecord::Observer class KeyObserver < ActiveRecord::Observer
include GitHost include Gitolited
def after_save(key) def after_save(key)
git_host.set_key(key.identifier, key.key, key.projects) gitolite.set_key(key.identifier, key.key, key.projects)
end end
def after_destroy(key) def after_destroy(key)
return if key.is_deploy_key && !key.last_deploy? 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
end end
...@@ -3,7 +3,7 @@ class MergeRequestObserver < ActiveRecord::Observer ...@@ -3,7 +3,7 @@ class MergeRequestObserver < ActiveRecord::Observer
def after_create(merge_request) def after_create(merge_request)
if merge_request.assignee && merge_request.assignee != current_user 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
end end
...@@ -25,7 +25,7 @@ class MergeRequestObserver < ActiveRecord::Observer ...@@ -25,7 +25,7 @@ class MergeRequestObserver < ActiveRecord::Observer
recipients_ids.delete current_user.id recipients_ids.delete current_user.id
recipients_ids.each do |recipient_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 end
end end
...@@ -11,7 +11,7 @@ class NoteObserver < ActiveRecord::Observer ...@@ -11,7 +11,7 @@ class NoteObserver < ActiveRecord::Observer
notify_team(note) notify_team(note)
elsif note.notify_author elsif note.notify_author
# Notify only author of resource # 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 else
# Otherwise ignore it # Otherwise ignore it
nil nil
...@@ -26,7 +26,7 @@ class NoteObserver < ActiveRecord::Observer ...@@ -26,7 +26,7 @@ class NoteObserver < ActiveRecord::Observer
if Notify.respond_to? notify_method if Notify.respond_to? notify_method
team_without_note_author(note).map do |u| 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 end
end end
......
...@@ -2,7 +2,7 @@ class UserObserver < ActiveRecord::Observer ...@@ -2,7 +2,7 @@ class UserObserver < ActiveRecord::Observer
def after_create(user) def after_create(user)
log_info("User \"#{user.name}\" (#{user.email}) was created") 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 end
def after_destroy user def after_destroy user
...@@ -14,7 +14,7 @@ class UserObserver < ActiveRecord::Observer ...@@ -14,7 +14,7 @@ class UserObserver < ActiveRecord::Observer
if user.namespace if user.namespace
user.namespace.update_attributes(path: user.username) user.namespace.update_attributes(path: user.username)
else else
user.create_namespace!(path: user.username, name: user.name) user.create_namespace!(path: user.username, name: user.username)
end end
end end
end end
......
class UsersProjectObserver < ActiveRecord::Observer class UsersProjectObserver < ActiveRecord::Observer
def after_commit(users_project) def after_commit(users_project)
return if users_project.destroyed? return if users_project.destroyed?
Notify.project_access_granted_email(users_project.id).deliver Notify.delay.project_access_granted_email(users_project.id)
end end
def after_create(users_project) def after_create(users_project)
......
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
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
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment