Commit 30227869482bbbdbfea153f2b45ef3bb9a9fd218

Authored by Riyad Preukschas
2 parents 8ee5fce9 aca0caa8

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
Showing 938 changed files with 81195 additions and 104503 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 938 files displayed.

.gitignore
... ... @@ -19,8 +19,10 @@ config/gitlab.yml
19 19 config/database.yml
20 20 config/initializers/omniauth.rb
21 21 config/unicorn.rb
  22 +config/resque.yml
22 23 db/data.yml
23 24 .idea
24 25 .DS_Store
25 26 .chef
26 27 vendor/bundle/*
  28 +rails_best_practices_output.html
... ...
.simplecov 0 → 100644
... ... @@ -0,0 +1,4 @@
  1 +# .simplecov
  2 +SimpleCov.start 'rails' do
  3 + merge_timeout 3600
  4 +end
... ...
.travis.yml
... ... @@ -3,16 +3,12 @@ env:
3 3 - DB=mysql
4 4 before_install:
5 5 - sudo apt-get install libicu-dev -y
6   - - wget -P /tmp http://phantomjs.googlecode.com/files/phantomjs-1.7.0-linux-i686.tar.bz2
7   - - tar -xf /tmp/phantomjs-1.7.0-linux-i686.tar.bz2 -C /tmp/
8   - - sudo rm -rf /usr/local/phantomjs
9   - - sudo mv /tmp/phantomjs-1.7.0-linux-i686 /usr/local/phantomjs
10 6 - gem install charlock_holmes -v="0.6.9"
11 7 branches:
12 8 only:
13 9 - 'master'
14 10 rvm:
15   - - 1.9.3
  11 + - 1.9.2
16 12 services:
17 13 - mysql
18 14 - postgresql
... ...
CHANGELOG
1 1 v 4.0.0
  2 + - Remove project code and path from API. Use id instead
  3 + - Return valid clonable url to repo for web hook
  4 + - Fixed backup issue
2 5 - Reorganized settings
3 6 - Fixed commits compare
4 7 - Refactored scss
... ...
Gemfile
... ... @@ -8,7 +8,7 @@ def linux_only(require_as)
8 8 RUBY_PLATFORM.include?('linux') && require_as
9 9 end
10 10  
11   -gem "rails", "3.2.9"
  11 +gem "rails", "3.2.11"
12 12  
13 13 # Supported DBs
14 14 gem "mysql2", group: :mysql
... ... @@ -23,11 +23,15 @@ gem 'omniauth-github'
23 23  
24 24 # GITLAB patched libs
25 25 gem "grit", git: "https://github.com/gitlabhq/grit.git", ref: '7f35cb98ff17d534a07e3ce6ec3d580f67402837'
26   -gem "omniauth-ldap", git: "https://github.com/gitlabhq/omniauth-ldap.git", ref: 'f038dd852d7bd473a557e385d5d7c2fd5dc1dc2e'
27   -gem 'yaml_db', git: "https://github.com/gitlabhq/yaml_db.git", ref: '98e9a5dca43e3fedd3268c76a73af40d1bdf1dfd'
28 26 gem 'grack', git: "https://github.com/gitlabhq/grack.git", ref: 'ba46f3b0845c6a09d488ae6abdce6ede37e227e8'
29 27 gem 'grit_ext', git: "https://github.com/gitlabhq/grit_ext.git", ref: '8e6afc2da821354774aa4d1ee8a1aa2082f84a3e'
30 28  
  29 +# LDAP Auth
  30 +gem 'gitlab_omniauth-ldap', '1.0.2', require: "omniauth-ldap"
  31 +
  32 +# Dump db to yml file. Mostly used to migrate from sqlite to mysql
  33 +gem 'gitlab_yaml_db', '1.0.0', require: "yaml_db"
  34 +
31 35 # Gitolite client (for work with gitolite-admin repo)
32 36 gem "gitolite", '1.1.0'
33 37  
... ... @@ -77,8 +81,9 @@ gem "acts-as-taggable-on", "2.3.3"
77 81 gem "draper", "~> 0.18.0"
78 82  
79 83 # Background jobs
80   -gem "resque", "~> 1.23.0"
81   -gem 'resque_mailer'
  84 +gem 'slim'
  85 +gem 'sinatra', :require => nil
  86 +gem 'sidekiq', '2.6.4'
82 87  
83 88 # HTTP requests
84 89 gem "httparty"
... ... @@ -104,7 +109,7 @@ group :assets do
104 109 gem "jquery-rails", "2.1.3"
105 110 gem "jquery-ui-rails", "2.0.2"
106 111 gem "modernizr", "2.6.2"
107   - gem "raphael-rails", "1.5.2"
  112 + gem "raphael-rails", git: "https://github.com/gitlabhq/raphael-rails.git"
108 113 gem 'bootstrap-sass', "2.2.1.1"
109 114 gem "font-awesome-sass-rails", "~> 2.0.0"
110 115 gem "gemoji", "~> 1.2.1", require: 'emoji/railtie'
... ... @@ -115,6 +120,14 @@ group :development do
115 120 gem "letter_opener"
116 121 gem 'quiet_assets', '~> 1.0.1'
117 122 gem 'rack-mini-profiler'
  123 + # Better errors handler
  124 + gem 'better_errors'
  125 + gem 'binding_of_caller'
  126 +
  127 + gem 'rails_best_practices'
  128 +
  129 + # Docs generator
  130 + gem "sdoc"
118 131 end
119 132  
120 133 group :development, :test do
... ... @@ -145,7 +158,6 @@ group :test do
145 158 gem "simplecov", require: false
146 159 gem "shoulda-matchers", "1.3.0"
147 160 gem 'email_spec'
148   - gem 'resque_spec'
149 161 gem "webmock"
150 162 gem 'test_after_commit'
151 163 end
... ...
Gemfile.lock
... ... @@ -40,17 +40,6 @@ GIT
40 40 charlock_holmes (~> 0.6.9)
41 41  
42 42 GIT
43   - remote: https://github.com/gitlabhq/omniauth-ldap.git
44   - revision: f038dd852d7bd473a557e385d5d7c2fd5dc1dc2e
45   - ref: f038dd852d7bd473a557e385d5d7c2fd5dc1dc2e
46   - specs:
47   - omniauth-ldap (1.0.2)
48   - net-ldap (~> 0.2.2)
49   - omniauth (~> 1.0)
50   - pyu-ruby-sasl (~> 0.0.3.1)
51   - rubyntlm (~> 0.1.1)
52   -
53   -GIT
54 43 remote: https://github.com/gitlabhq/pygments.rb.git
55 44 revision: db1da0343adf86b49bdc3add04d02d2e80438d38
56 45 branch: master
... ... @@ -60,11 +49,10 @@ GIT
60 49 yajl-ruby (~> 1.1.0)
61 50  
62 51 GIT
63   - remote: https://github.com/gitlabhq/yaml_db.git
64   - revision: 98e9a5dca43e3fedd3268c76a73af40d1bdf1dfd
65   - ref: 98e9a5dca43e3fedd3268c76a73af40d1bdf1dfd
  52 + remote: https://github.com/gitlabhq/raphael-rails.git
  53 + revision: cb2c92a040b9b941a5f1aa1ea866cc26e944fe58
66 54 specs:
67   - yaml_db (0.2.2)
  55 + raphael-rails (2.1.0)
68 56  
69 57 GIT
70 58 remote: https://github.com/jonleighton/poltergeist.git
... ... @@ -81,12 +69,12 @@ GIT
81 69 GEM
82 70 remote: http://rubygems.org/
83 71 specs:
84   - actionmailer (3.2.9)
85   - actionpack (= 3.2.9)
  72 + actionmailer (3.2.11)
  73 + actionpack (= 3.2.11)
86 74 mail (~> 2.4.4)
87   - actionpack (3.2.9)
88   - activemodel (= 3.2.9)
89   - activesupport (= 3.2.9)
  75 + actionpack (3.2.11)
  76 + activemodel (= 3.2.11)
  77 + activesupport (= 3.2.11)
90 78 builder (~> 3.0.0)
91 79 erubis (~> 2.7.0)
92 80 journey (~> 1.0.4)
... ... @@ -94,18 +82,18 @@ GEM
94 82 rack-cache (~> 1.2)
95 83 rack-test (~> 0.6.1)
96 84 sprockets (~> 2.2.1)
97   - activemodel (3.2.9)
98   - activesupport (= 3.2.9)
  85 + activemodel (3.2.11)
  86 + activesupport (= 3.2.11)
99 87 builder (~> 3.0.0)
100   - activerecord (3.2.9)
101   - activemodel (= 3.2.9)
102   - activesupport (= 3.2.9)
  88 + activerecord (3.2.11)
  89 + activemodel (= 3.2.11)
  90 + activesupport (= 3.2.11)
103 91 arel (~> 3.0.2)
104 92 tzinfo (~> 0.3.29)
105   - activeresource (3.2.9)
106   - activemodel (= 3.2.9)
107   - activesupport (= 3.2.9)
108   - activesupport (3.2.9)
  93 + activeresource (3.2.11)
  94 + activemodel (= 3.2.11)
  95 + activesupport (= 3.2.11)
  96 + activesupport (3.2.11)
109 97 i18n (~> 0.6)
110 98 multi_json (~> 1.0)
111 99 acts-as-taggable-on (2.3.3)
... ... @@ -115,6 +103,10 @@ GEM
115 103 awesome_print (1.1.0)
116 104 backports (2.6.5)
117 105 bcrypt-ruby (3.0.1)
  106 + better_errors (0.3.2)
  107 + coderay (>= 1.0.0)
  108 + erubis (>= 2.7.0)
  109 + binding_of_caller (0.6.8)
118 110 blankslate (3.1.2)
119 111 bootstrap-sass (2.2.1.1)
120 112 sass (~> 3.2)
... ... @@ -129,12 +121,17 @@ GEM
129 121 carrierwave (0.7.1)
130 122 activemodel (>= 3.2.0)
131 123 activesupport (>= 3.2.0)
  124 + celluloid (0.12.4)
  125 + facter (>= 1.6.12)
  126 + timers (>= 1.0.0)
132 127 charlock_holmes (0.6.9)
133 128 childprocess (0.3.6)
134 129 ffi (~> 1.0, >= 1.0.6)
135 130 chosen-rails (0.9.8)
136 131 railties (~> 3.0)
137 132 thor (~> 0.14)
  133 + code_analyzer (0.3.1)
  134 + sexp_processor
138 135 coderay (1.0.8)
139 136 coffee-rails (3.2.2)
140 137 coffee-script (>= 2.2.0)
... ... @@ -145,6 +142,7 @@ GEM
145 142 coffee-script-source (1.4.0)
146 143 colored (1.2)
147 144 colorize (0.5.8)
  145 + connection_pool (1.0.0)
148 146 crack (0.3.1)
149 147 daemons (1.1.9)
150 148 devise (2.1.2)
... ... @@ -164,6 +162,7 @@ GEM
164 162 eventmachine (1.0.0)
165 163 execjs (1.4.0)
166 164 multi_json (~> 1.0)
  165 + facter (1.6.17)
167 166 factory_girl (4.1.0)
168 167 activesupport (>= 3.0.0)
169 168 factory_girl_rails (4.1.0)
... ... @@ -190,6 +189,12 @@ GEM
190 189 pygments.rb (>= 0.2.13)
191 190 github-markup (0.7.4)
192 191 gitlab_meta (4.0)
  192 + gitlab_omniauth-ldap (1.0.2)
  193 + net-ldap (~> 0.2.2)
  194 + omniauth (~> 1.0)
  195 + pyu-ruby-sasl (~> 0.0.3.1)
  196 + rubyntlm (~> 0.1.1)
  197 + gitlab_yaml_db (1.0.0)
193 198 gitolite (1.1.0)
194 199 gratr19 (~> 0.4.4.1)
195 200 grit (~> 2.5.0)
... ... @@ -240,7 +245,7 @@ GEM
240 245 jquery-ui-rails (2.0.2)
241 246 jquery-rails
242 247 railties (>= 3.1.0)
243   - json (1.7.5)
  248 + json (1.7.6)
244 249 jwt (0.1.5)
245 250 multi_json (>= 1.0)
246 251 kaminari (0.14.1)
... ... @@ -264,7 +269,7 @@ GEM
264 269 mime-types (1.19)
265 270 modernizr (2.6.2)
266 271 sprockets (~> 2.0)
267   - multi_json (1.3.7)
  272 + multi_json (1.5.0)
268 273 multi_xml (0.5.1)
269 274 multipart-post (1.1.5)
270 275 mysql2 (0.3.11)
... ... @@ -299,6 +304,7 @@ GEM
299 304 pg (0.14.1)
300 305 polyglot (0.3.3)
301 306 posix-spawn (0.3.6)
  307 + progressbar (0.12.0)
302 308 pry (0.9.10)
303 309 coderay (~> 1.0.5)
304 310 method_source (~> 0.8)
... ... @@ -306,7 +312,7 @@ GEM
306 312 pyu-ruby-sasl (0.0.3.3)
307 313 quiet_assets (1.0.1)
308 314 railties (~> 3.1)
309   - rack (1.4.1)
  315 + rack (1.4.3)
310 316 rack-accept (0.4.5)
311 317 rack (>= 0.4)
312 318 rack-cache (1.2)
... ... @@ -315,33 +321,40 @@ GEM
315 321 rack (>= 1.1.3)
316 322 rack-mount (0.8.3)
317 323 rack (>= 1.0.0)
318   - rack-protection (1.2.0)
  324 + rack-protection (1.3.2)
319 325 rack
320 326 rack-ssl (1.3.2)
321 327 rack
322 328 rack-test (0.6.2)
323 329 rack (>= 1.0)
324   - rails (3.2.9)
325   - actionmailer (= 3.2.9)
326   - actionpack (= 3.2.9)
327   - activerecord (= 3.2.9)
328   - activeresource (= 3.2.9)
329   - activesupport (= 3.2.9)
  330 + rails (3.2.11)
  331 + actionmailer (= 3.2.11)
  332 + actionpack (= 3.2.11)
  333 + activerecord (= 3.2.11)
  334 + activeresource (= 3.2.11)
  335 + activesupport (= 3.2.11)
330 336 bundler (~> 1.0)
331   - railties (= 3.2.9)
  337 + railties (= 3.2.11)
332 338 rails-dev-tweaks (0.6.1)
333 339 actionpack (~> 3.1)
334 340 railties (~> 3.1)
335   - railties (3.2.9)
336   - actionpack (= 3.2.9)
337   - activesupport (= 3.2.9)
  341 + rails_best_practices (1.13.2)
  342 + activesupport
  343 + awesome_print
  344 + code_analyzer
  345 + colored
  346 + erubis
  347 + i18n
  348 + progressbar
  349 + railties (3.2.11)
  350 + actionpack (= 3.2.11)
  351 + activesupport (= 3.2.11)
338 352 rack-ssl (~> 1.3.2)
339 353 rake (>= 0.8.7)
340 354 rdoc (~> 3.4)
341 355 thor (>= 0.14.6, < 2.0)
342 356 raindrops (0.10.0)
343   - rake (10.0.1)
344   - raphael-rails (1.5.2)
  357 + rake (10.0.3)
345 358 rb-fsevent (0.9.2)
346 359 rb-inotify (0.8.8)
347 360 ffi (>= 0.5.0)
... ... @@ -351,16 +364,6 @@ GEM
351 364 redis (3.0.2)
352 365 redis-namespace (1.2.1)
353 366 redis (~> 3.0.0)
354   - resque (1.23.0)
355   - multi_json (~> 1.0)
356   - redis-namespace (~> 1.0)
357   - sinatra (>= 0.9.2)
358   - vegas (~> 0.1.2)
359   - resque_mailer (2.1.0)
360   - actionmailer (~> 3.0)
361   - resque_spec (0.12.5)
362   - resque (>= 1.19.0)
363   - rspec (>= 2.5.0)
364 367 rspec (2.12.0)
365 368 rspec-core (~> 2.12.0)
366 369 rspec-expectations (~> 2.12.0)
... ... @@ -383,6 +386,9 @@ GEM
383 386 railties (~> 3.2.0)
384 387 sass (>= 3.1.10)
385 388 tilt (~> 1.3)
  389 + sdoc (0.3.20)
  390 + json (>= 1.1.3)
  391 + rdoc (~> 3.10)
386 392 seed-fu (2.2.0)
387 393 activerecord (~> 3.1)
388 394 activesupport (~> 3.1)
... ... @@ -392,8 +398,15 @@ GEM
392 398 multi_json (~> 1.0)
393 399 rubyzip
394 400 settingslogic (2.0.8)
  401 + sexp_processor (4.1.3)
395 402 shoulda-matchers (1.3.0)
396 403 activesupport (>= 3.0.0)
  404 + sidekiq (2.6.4)
  405 + celluloid (~> 0.12.0)
  406 + connection_pool (~> 1.0)
  407 + multi_json (~> 1)
  408 + redis (~> 3)
  409 + redis-namespace
397 410 simplecov (0.7.1)
398 411 multi_json (~> 1.0)
399 412 simplecov-html (~> 0.7.1)
... ... @@ -403,6 +416,9 @@ GEM
403 416 rack-protection (~> 1.2)
404 417 tilt (~> 1.3, >= 1.3.3)
405 418 six (0.2.0)
  419 + slim (1.3.6)
  420 + temple (~> 0.5.5)
  421 + tilt (~> 1.3.3)
406 422 slop (3.3.3)
407 423 spinach (0.5.2)
408 424 colorize
... ... @@ -411,12 +427,13 @@ GEM
411 427 capybara (~> 1)
412 428 railties (>= 3)
413 429 spinach (>= 0.4)
414   - sprockets (2.2.1)
  430 + sprockets (2.2.2)
415 431 hike (~> 1.2)
416 432 multi_json (~> 1.0)
417 433 rack (~> 1.0)
418 434 tilt (~> 1.1, != 1.3.0)
419 435 stamp (0.3.0)
  436 + temple (0.5.5)
420 437 test_after_commit (0.0.1)
421 438 therubyracer (0.10.2)
422 439 libv8 (~> 3.3.10)
... ... @@ -426,6 +443,7 @@ GEM
426 443 rack (>= 1.0.0)
427 444 thor (0.16.0)
428 445 tilt (1.3.3)
  446 + timers (1.0.2)
429 447 treetop (1.4.12)
430 448 polyglot
431 449 polyglot (>= 0.3.1)
... ... @@ -437,8 +455,6 @@ GEM
437 455 kgio (~> 2.6)
438 456 rack
439 457 raindrops (~> 0.7)
440   - vegas (0.1.11)
441   - rack (>= 1.0.0)
442 458 virtus (0.5.2)
443 459 backports (~> 2.6.1)
444 460 warden (1.2.1)
... ... @@ -458,6 +474,8 @@ DEPENDENCIES
458 474 acts-as-taggable-on (= 2.3.3)
459 475 annotate!
460 476 awesome_print
  477 + better_errors
  478 + binding_of_caller
461 479 bootstrap-sass (= 2.2.1.1)
462 480 capybara
463 481 carrierwave (~> 0.7.1)
... ... @@ -477,6 +495,8 @@ DEPENDENCIES
477 495 github-linguist (~> 2.3.4)
478 496 github-markup (~> 0.7.4)
479 497 gitlab_meta (= 4.0)
  498 + gitlab_omniauth-ldap (= 1.0.2)
  499 + gitlab_yaml_db (= 1.0.0)
480 500 gitolite (= 1.1.0)
481 501 grack!
482 502 grape (~> 0.2.1)
... ... @@ -498,7 +518,6 @@ DEPENDENCIES
498 518 omniauth (~> 1.1.1)
499 519 omniauth-github
500 520 omniauth-google-oauth2
501   - omniauth-ldap!
502 521 omniauth-twitter
503 522 pg
504 523 poltergeist!
... ... @@ -506,22 +525,24 @@ DEPENDENCIES
506 525 pygments.rb!
507 526 quiet_assets (~> 1.0.1)
508 527 rack-mini-profiler
509   - rails (= 3.2.9)
  528 + rails (= 3.2.11)
510 529 rails-dev-tweaks
511   - raphael-rails (= 1.5.2)
  530 + rails_best_practices
  531 + raphael-rails!
512 532 rb-fsevent
513 533 rb-inotify
514 534 redcarpet (~> 2.2.2)
515   - resque (~> 1.23.0)
516   - resque_mailer
517   - resque_spec
518 535 rspec-rails
519 536 sass-rails (~> 3.2.5)
  537 + sdoc
520 538 seed-fu
521 539 settingslogic
522 540 shoulda-matchers (= 1.3.0)
  541 + sidekiq (= 2.6.4)
523 542 simplecov
  543 + sinatra
524 544 six
  545 + slim
525 546 spinach-rails
526 547 stamp
527 548 test_after_commit
... ... @@ -530,4 +551,3 @@ DEPENDENCIES
530 551 uglifier (~> 1.3.0)
531 552 unicorn (~> 4.4.0)
532 553 webmock
533   - yaml_db!
... ...
Procfile
1 1 web: bundle exec rails s -p $PORT
2   -worker: bundle exec rake environment resque:work QUEUE=* VVERBOSE=1
  2 +worker: bundle exec sidekiq -q post_receive,mailer,system_hook,common,default
... ...
README.md
... ... @@ -2,6 +2,7 @@
2 2  
3 3 GitLab is a free project and repository management application
4 4  
  5 +![CI](http://ci.gitlab.org/projects/1/status?ref=master)
5 6  
6 7 ## Application details
7 8  
... ... @@ -39,6 +40,6 @@ Email
39 40  
40 41 ## Contribute
41 42  
42   -[Development Tips](https://github.com/gitlabhq/gitlabhq/blob/master/doc/development.md)
  43 +[Developer Guide](https://github.com/gitlabhq/gitlabhq/wiki/Developer-Guide)
43 44 Want to help - send a pull request.
44 45 We'll accept good pull requests.
... ...
VERSION
1   -4.0.0rc1
  1 +4.1.0pre
... ...
app/assets/fonts/OFL.txt 0 → 100644
... ... @@ -0,0 +1,92 @@
  1 +Copyright (c) 2010, Jan Gerner (post@yanone.de)
  2 +This Font Software is licensed under the SIL Open Font License, Version 1.1.
  3 +This license is copied below, and is also available with a FAQ at:
  4 +http://scripts.sil.org/OFL
  5 +
  6 +
  7 +-----------------------------------------------------------
  8 +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
  9 +-----------------------------------------------------------
  10 +
  11 +PREAMBLE
  12 +The goals of the Open Font License (OFL) are to stimulate worldwide
  13 +development of collaborative font projects, to support the font creation
  14 +efforts of academic and linguistic communities, and to provide a free and
  15 +open framework in which fonts may be shared and improved in partnership
  16 +with others.
  17 +
  18 +The OFL allows the licensed fonts to be used, studied, modified and
  19 +redistributed freely as long as they are not sold by themselves. The
  20 +fonts, including any derivative works, can be bundled, embedded,
  21 +redistributed and/or sold with any software provided that any reserved
  22 +names are not used by derivative works. The fonts and derivatives,
  23 +however, cannot be released under any other type of license. The
  24 +requirement for fonts to remain under this license does not apply
  25 +to any document created using the fonts or their derivatives.
  26 +
  27 +DEFINITIONS
  28 +"Font Software" refers to the set of files released by the Copyright
  29 +Holder(s) under this license and clearly marked as such. This may
  30 +include source files, build scripts and documentation.
  31 +
  32 +"Reserved Font Name" refers to any names specified as such after the
  33 +copyright statement(s).
  34 +
  35 +"Original Version" refers to the collection of Font Software components as
  36 +distributed by the Copyright Holder(s).
  37 +
  38 +"Modified Version" refers to any derivative made by adding to, deleting,
  39 +or substituting -- in part or in whole -- any of the components of the
  40 +Original Version, by changing formats or by porting the Font Software to a
  41 +new environment.
  42 +
  43 +"Author" refers to any designer, engineer, programmer, technical
  44 +writer or other person who contributed to the Font Software.
  45 +
  46 +PERMISSION & CONDITIONS
  47 +Permission is hereby granted, free of charge, to any person obtaining
  48 +a copy of the Font Software, to use, study, copy, merge, embed, modify,
  49 +redistribute, and sell modified and unmodified copies of the Font
  50 +Software, subject to the following conditions:
  51 +
  52 +1) Neither the Font Software nor any of its individual components,
  53 +in Original or Modified Versions, may be sold by itself.
  54 +
  55 +2) Original or Modified Versions of the Font Software may be bundled,
  56 +redistributed and/or sold with any software, provided that each copy
  57 +contains the above copyright notice and this license. These can be
  58 +included either as stand-alone text files, human-readable headers or
  59 +in the appropriate machine-readable metadata fields within text or
  60 +binary files as long as those fields can be easily viewed by the user.
  61 +
  62 +3) No Modified Version of the Font Software may use the Reserved Font
  63 +Name(s) unless explicit written permission is granted by the corresponding
  64 +Copyright Holder. This restriction only applies to the primary font name as
  65 +presented to the users.
  66 +
  67 +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
  68 +Software shall not be used to promote, endorse or advertise any
  69 +Modified Version, except to acknowledge the contribution(s) of the
  70 +Copyright Holder(s) and the Author(s) or with their explicit written
  71 +permission.
  72 +
  73 +5) The Font Software, modified or unmodified, in part or in whole,
  74 +must be distributed entirely under this license, and must not be
  75 +distributed under any other license. The requirement for fonts to
  76 +remain under this license does not apply to any document created
  77 +using the Font Software.
  78 +
  79 +TERMINATION
  80 +This license becomes null and void if any of the above conditions are
  81 +not met.
  82 +
  83 +DISCLAIMER
  84 +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  85 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
  86 +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
  87 +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
  88 +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  89 +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
  90 +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  91 +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
  92 +OTHER DEALINGS IN THE FONT SOFTWARE.
... ...
app/assets/fonts/YanoneKaffeesatz-Light.ttf 0 → 100644
No preview for this file type
app/assets/fonts/korolev-medium-compressed.otf
No preview for this file type
app/assets/images/download.png

674 Bytes

app/assets/javascripts/dashboard.js.coffee 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +$ ->
  2 + dashboardPage()
  3 +
  4 +dashboardPage = ->
  5 + Pager.init 20, true
  6 + $(".event_filter_link").bind "click", (event) ->
  7 + event.preventDefault()
  8 + toggleFilter $(this)
  9 + reloadActivities()
  10 +
  11 +reloadActivities = ->
  12 + $(".content_list").html ''
  13 + Pager.init 20, true
  14 +
  15 +toggleFilter = (sender) ->
  16 + sender.parent().toggleClass "inactive"
  17 + event_filters = $.cookie("event_filter")
  18 + filter = sender.attr("id").split("_")[0]
  19 + if event_filters
  20 + event_filters = event_filters.split(",")
  21 + else
  22 + event_filters = new Array()
  23 +
  24 + index = event_filters.indexOf(filter)
  25 + if index is -1
  26 + event_filters.push filter
  27 + else
  28 + event_filters.splice index, 1
  29 +
  30 + $.cookie "event_filter", event_filters.join(",")
... ...
app/assets/javascripts/issues.js
... ... @@ -11,7 +11,7 @@ function initIssuesSearch() {
11 11 last_terms = terms;
12 12  
13 13 if (terms.length >= 2 || terms.length == 0) {
14   - $.get(href, { 'f': status, 'terms': terms, 'milestone_id': milestone_id }, function(response) {
  14 + $.get(href, { 'status': status, 'terms': terms, 'milestone_id': milestone_id }, function(response) {
15 15 $('#issues-table').html(response);
16 16 });
17 17 }
... ...
app/assets/javascripts/merge_requests.js
... ... @@ -1,132 +0,0 @@
1   -var MergeRequest = {
2   - diffs_loaded: false,
3   - commits_loaded: false,
4   - opts: false,
5   -
6   - init:
7   - function(opts) {
8   - var self = this;
9   - self.opts = opts;
10   -
11   - self.initTabs();
12   - self.initMergeWidget();
13   -
14   - $(".mr_show_all_commits").bind("click", function() {
15   - self.showAllCommits();
16   - });
17   - },
18   -
19   - initMergeWidget:
20   - function() {
21   - var self = this;
22   - self.showState(self.opts.current_state);
23   -
24   - if($(".automerge_widget").length && self.opts.check_enable){
25   - $.get(self.opts.url_to_automerge_check, function(data){
26   - self.showState(data.state);
27   - }, "json");
28   - }
29   -
30   - if(self.opts.ci_enable){
31   - $.get(self.opts.url_to_ci_check, function(data){
32   - self.showCiState(data.status);
33   - }, "json");
34   - }
35   - },
36   -
37   - initTabs:
38   - function() {
39   - $(".mr_nav_tabs a").live("click", function() {
40   - $(".mr_nav_tabs a").parent().removeClass("active");
41   - $(this).parent().addClass("active");
42   - });
43   -
44   - var current_tab;
45   - if(this.opts.action == "diffs") {
46   - current_tab = $(".mr_nav_tabs .merge-diffs-tab");
47   - } else {
48   - current_tab = $(".mr_nav_tabs .merge-notes-tab");
49   - }
50   - current_tab.parent().addClass("active");
51   -
52   - this.initNotesTab();
53   - this.initDiffTab();
54   - },
55   -
56   - initNotesTab:
57   - function() {
58   - $(".mr_nav_tabs a.merge-notes-tab").live("click", function(e) {
59   - $(".merge-request-diffs").hide();
60   - $(".merge_request_notes").show();
61   - var mr_path = $(".merge-notes-tab").attr("data-url");
62   - history.pushState({ path: mr_path }, '', mr_path);
63   - e.preventDefault();
64   - });
65   - },
66   -
67   - initDiffTab:
68   - function() {
69   - $(".mr_nav_tabs a.merge-diffs-tab").live("click", function(e) {
70   - if(!MergeRequest.diffs_loaded) {
71   - MergeRequest.loadDiff();
72   - }
73   - $(".merge_request_notes").hide();
74   - $(".merge-request-diffs").show();
75   - var mr_diff_path = $(".merge-diffs-tab").attr("data-url");
76   - history.pushState({ path: mr_diff_path }, '', mr_diff_path);
77   - e.preventDefault();
78   - });
79   -
80   - },
81   -
82   - showState:
83   - function(state){
84   - $(".automerge_widget").hide();
85   - $(".automerge_widget." + state).show();
86   - },
87   -
88   - showCiState:
89   - function(state){
90   - $(".ci_widget").hide();
91   - $(".ci_widget.ci-" + state).show();
92   - },
93   -
94   - loadDiff:
95   - function() {
96   - $(".dashboard-loader").show();
97   - $.ajax({
98   - type: "GET",
99   - url: $(".merge-diffs-tab").attr("data-url"),
100   - beforeSend: function(){ $('.status').addClass("loading")},
101   - complete: function(){
102   - MergeRequest.diffs_loaded = true;
103   - $(".merge_request_notes").hide();
104   - $('.status').removeClass("loading");
105   - },
106   - dataType: "script"});
107   - },
108   -
109   - showAllCommits:
110   - function() {
111   - $(".first_mr_commits").remove();
112   - $(".all_mr_commits").removeClass("hide");
113   - },
114   -
115   - already_cannot_be_merged:
116   - function(){
117   - $(".automerge_widget").hide();
118   - $(".merge_in_progress").hide();
119   - $(".automerge_widget.already_cannot_be_merged").show();
120   - }
121   -};
122   -
123   -/*
124   - * Filter merge requests
125   - */
126   -function merge_requestsPage() {
127   - $("#assignee_id").chosen();
128   - $("#milestone_id").chosen();
129   - $("#milestone_id, #assignee_id").on("change", function(){
130   - $(this).closest("form").submit();
131   - });
132   -}
app/assets/javascripts/merge_requests.js.coffee 0 → 100644
... ... @@ -0,0 +1,97 @@
  1 +#
  2 +# * Filter merge requests
  3 +#
  4 +@merge_requestsPage = ->
  5 + $('#assignee_id').chosen()
  6 + $('#milestone_id').chosen()
  7 + $('#milestone_id, #assignee_id').on 'change', ->
  8 + $(this).closest('form').submit()
  9 +
  10 +class MergeRequest
  11 +
  12 + constructor: (@opts) ->
  13 + this.$el = $('.merge-request')
  14 + @diffs_loaded = false
  15 + @commits_loaded = false
  16 +
  17 + this.activateTab(@opts.action)
  18 +
  19 + this.bindEvents()
  20 +
  21 + this.initMergeWidget()
  22 + this.$('.show-all-commits').on 'click', =>
  23 + this.showAllCommits()
  24 +
  25 + # Local jQuery finder
  26 + $: (selector) ->
  27 + this.$el.find(selector)
  28 +
  29 + initMergeWidget: ->
  30 + this.showState( @opts.current_state )
  31 +
  32 + if this.$('.automerge_widget').length and @opts.check_enable
  33 + $.get @opts.url_to_automerge_check, (data) =>
  34 + this.showState( data.state )
  35 + , 'json'
  36 +
  37 + if @opts.ci_enable
  38 + $.get @opts.url_to_ci_check, (data) =>
  39 + this.showCiState data.status
  40 + , 'json'
  41 +
  42 + bindEvents: ->
  43 + this.$('.nav-tabs').on 'click', 'a', (event) =>
  44 + a = $(event.currentTarget)
  45 +
  46 + href = a.attr('href')
  47 + History.replaceState {path: href}, document.title, href
  48 +
  49 + event.preventDefault()
  50 +
  51 + this.$('.nav-tabs').on 'click', 'li', (event) =>
  52 + this.activateTab($(event.currentTarget).data('action'))
  53 +
  54 + activateTab: (action) ->
  55 + this.$('.nav-tabs li').removeClass 'active'
  56 + this.$('.tab-content').hide()
  57 + switch action
  58 + when 'diffs'
  59 + this.$('.nav-tabs .diffs-tab').addClass 'active'
  60 + this.loadDiff() unless @diffs_loaded
  61 + this.$('.diffs').show()
  62 + else
  63 + this.$('.nav-tabs .notes-tab').addClass 'active'
  64 + this.$('.notes').show()
  65 +
  66 + showState: (state) ->
  67 + $('.automerge_widget').hide()
  68 + $('.automerge_widget.' + state).show()
  69 +
  70 + showCiState: (state) ->
  71 + $('.ci_widget').hide()
  72 + $('.ci_widget.ci-' + state).show()
  73 +
  74 + loadDiff: (event) ->
  75 + $('.dashboard-loader').show()
  76 + $.ajax
  77 + type: 'GET'
  78 + url: this.$('.nav-tabs .diffs-tab a').attr('href')
  79 + beforeSend: =>
  80 + this.$('.status').addClass 'loading'
  81 +
  82 + complete: =>
  83 + @diffs_loaded = true
  84 + this.$('.status').removeClass 'loading'
  85 +
  86 + dataType: 'script'
  87 +
  88 + showAllCommits: ->
  89 + this.$('.first-commits').remove()
  90 + this.$('.all-commits').removeClass 'hide'
  91 +
  92 + alreadyOrCannotBeMerged: ->
  93 + this.$('.automerge_widget').hide()
  94 + this.$('.merge-in-progress').hide()
  95 + this.$('.automerge_widget.already_cannot_be_merged').show()
  96 +
  97 +this.MergeRequest = MergeRequest
... ...
app/assets/javascripts/milestones.js.coffee
1 1 $ ->
2   - $('.milestone-issue-filter tr[data-closed]').addClass('hide')
  2 + $('.milestone-issue-filter li[data-closed]').addClass('hide')
3 3  
4 4 $('.milestone-issue-filter ul.nav li a').click ->
5 5 $('.milestone-issue-filter li').toggleClass('active')
6   - $('.milestone-issue-filter tr[data-closed]').toggleClass('hide')
  6 + $('.milestone-issue-filter li[data-closed]').toggleClass('hide')
7 7 false
8 8  
9   - $('.milestone-merge-requests-filter tr[data-closed]').addClass('hide')
  9 + $('.milestone-merge-requests-filter li[data-closed]').addClass('hide')
10 10  
11 11 $('.milestone-merge-requests-filter ul.nav li a').click ->
12 12 $('.milestone-merge-requests-filter li').toggleClass('active')
13   - $('.milestone-merge-requests-filter tr[data-closed]').toggleClass('hide')
  13 + $('.milestone-merge-requests-filter li[data-closed]').toggleClass('hide')
14 14 false
... ...
app/assets/javascripts/pager.js
... ... @@ -4,9 +4,16 @@ var Pager = {
4 4 disable:false,
5 5  
6 6 init:
7   - function(limit) {
  7 + function(limit, preload) {
8 8 this.limit=limit;
9   - this.offset=limit;
  9 +
  10 + if(preload) {
  11 + this.offset = 0;
  12 + this.getOld();
  13 + } else {
  14 + this.offset = limit;
  15 + }
  16 +
10 17 this.initLoadMore();
11 18 },
12 19  
... ...
app/assets/stylesheets/common.scss
... ... @@ -117,34 +117,10 @@ span.update-author {
117 117 }
118 118  
119 119 .label {
120   - background-color: #474D57;
121   -
122   - &.label-tag {
123   - background: none;
124   - border: none;
125   - padding: 4px 6px;
126   - color: #444;
127   - text-shadow: 0 0 1px #fff;
128   -
129   - &.grouped {
130   - float: left;
131   - margin-right: 6px;
132   - padding: 6px;
133   - }
134   - }
135   - &.label-issue {
136   - background-color: #eee;
137   - border: 1px solid #ccc;
138   - padding: 4px 6px;
139   - color: #444;
140   - text-shadow: 0 0 1px #fff;
141   -
142   - &.grouped {
143   - float: left;
144   - margin-right: 6px;
145   - padding: 6px;
146   - }
147   - }
  120 + padding: 0px 4px;
  121 + font-size: 10px;
  122 + font-style: normal;
  123 + background-color: $link_color;
148 124  
149 125 &.label-success {
150 126 background-color: #8D8;
... ... @@ -425,7 +401,7 @@ li.note {
425 401  
426 402  
427 403 .supp_diff_link,
428   -.mr_show_all_commits {
  404 +.show-all-commits {
429 405 cursor: pointer;
430 406 }
431 407  
... ...
app/assets/stylesheets/gitlab_bootstrap/blocks.scss
1 1 /**
2 2 * ===================================
3   - * Contain 3 main UI block elements:
4   - * .main_box - for show pages
5   - * .ui-box - for simple block & widgets
  3 + * Contain UI block elements:
  4 + * .ui-box - for any block & widgets
6 5 * ===================================
7 6 */
8 7  
9 8 /**
10   - * UI box element
11   - * contains top, middle, bottom blocks
  9 + * UI Block
12 10 *
13 11 */
14   -.main_box {
15   - @extend .borders;
16   - @extend .prepend-top-20;
17   - @extend .append-bottom-20;
18   - border-width: 1px;
  12 +.ui-box {
  13 + background: #F9F9F9;
  14 + margin-bottom: 25px;
  15 + border: 1px solid #CCC;
19 16 @include solid-shade;
20 17  
  18 + &.ui-box-show {
  19 + margin:20px 0;
  20 + background: #FFF;
  21 + }
21 22  
22 23 img { max-width: 100%; }
23 24  
... ... @@ -27,9 +28,9 @@
27 28 }
28 29 }
29 30  
30   - .top_box_content,
31   - .middle_box_content,
32   - .bottom_box_content {
  31 + .ui-box-head,
  32 + .ui-box-body,
  33 + .ui-box-bottom {
33 34 padding: 15px;
34 35 word-wrap: break-word;
35 36  
... ... @@ -39,19 +40,25 @@
39 40 border: none;
40 41 padding: 0;
41 42 }
  43 +
  44 + .clearfix {
  45 + margin: 0;
  46 + }
42 47 }
43 48  
44   - .top_box_content {
  49 + .ui-box-head {
45 50 .box-title {
46 51 color: $style_color;
47 52 font-size: 18px;
48 53 font-weight: normal;
49 54 line-height: 28px;
50 55 }
  56 + h3 {
  57 + margin: 0;
  58 + }
51 59 }
52 60  
53   - .middle_box_content {
54   - @include border-radius(0);
  61 + .ui-box-body {
55 62 border: none;
56 63 font-size: 12px;
57 64 background-color: #f5f5f5;
... ... @@ -59,24 +66,9 @@
59 66 border-top: 1px solid #eee;
60 67 }
61 68  
62   - .bottom_box_content {
  69 + .ui-box-bottom {
63 70 border-top: 1px solid #eee;
64 71 }
65   -}
66   -
67   -/**
68   - * Big UI Block for show page content
69   - *
70   - */
71   -.ui-box {
72   - background: #F9F9F9;
73   - margin-bottom: 25px;
74   -
75   - border: 1px solid #eaeaea;
76   - @include border-radius(4px);
77   -
78   - border-color: #CCC;
79   - @include solid-shade;
80 72  
81 73 &.white {
82 74 background: #fff;
... ... @@ -86,47 +78,47 @@
86 78 margin: 0;
87 79 }
88 80  
89   - h5, .title {
90   - padding: 0 10px;
91   - @include border-radius(4px 4px 0 0);
  81 + .title {
92 82 @include bg-gray-gradient;
93   - border-top: 1px solid #eaeaea;
94   - border-bottom: 1px solid #bbb;
  83 + border-bottom: 1px solid #CCC;
  84 + color: #456;
  85 + font-size: 16px;
  86 + text-shadow: 0 1px 1px #fff;
  87 + padding: 0px 10px;
  88 + line-height: 36px;
  89 + font-size: 14px;
  90 + font-weight: normal;
95 91  
96 92 > a {
97 93 text-shadow: 0 1px 1px #fff;
98 94 }
99 95  
100   - &.small {
101   - line-height: 28px;
102   - font-size: 14px;
103   - line-height: 28px;
104   - text-shadow: 0 1px 1px white;
105   - }
106   -
107 96 form {
108   - padding: 9px 0;
109   - margin: 0px;
  97 + margin-bottom: 0;
  98 + margin-top: 3px;
110 99 }
111 100  
112 101 .nav-pills {
113   - li {
114   - padding: 3px 0;
115   - &.active a { background-color: $style_color; }
116   - a {
117   - @include border-radius(7px);
  102 + > li {
  103 + > a {
  104 + padding: 13px;
  105 + margin: 0;
  106 + font-size: 13px;
  107 + }
  108 + &.active {
  109 + > a {
  110 + background: #D5D5D5;
  111 + color: $style_color;
  112 + @include border-radius(0);
  113 + border-radius: 0;
  114 + border-left: 1px solid #CCC;
  115 + border-right: 1px solid #CCC;
  116 + }
118 117 }
119 118 }
120 119 }
121 120 }
122 121  
123   - .bottom {
124   - @include bg-gray-gradient;
125   - @include border-radius(0 0 4px 4px);
126   - border-bottom: none;
127   - border-top: 1px solid #bbb;
128   - }
129   -
130 122 &.padded {
131 123 h5, .title {
132 124 margin: -20px;
... ... @@ -143,6 +135,7 @@
143 135 color: #777;
144 136 }
145 137 }
  138 +
146 139 .row_title {
147 140 font-weight: bold;
148 141 color: #444;
... ... @@ -151,8 +144,4 @@
151 144 text-decoration: underline;
152 145 }
153 146 }
154   -
155   - .ui-box-body {
156   - padding: 10px;
157   - }
158 147 }
... ...
app/assets/stylesheets/gitlab_bootstrap/buttons.scss
... ... @@ -7,6 +7,10 @@
7 7 color: #333;
8 8 }
9 9  
  10 + &.btn-white {
  11 + background: #FFF;
  12 + }
  13 +
10 14 &.primary {
11 15 background: #2a79A3;
12 16 @include linear-gradient(#47A7b7, #2585b5);
... ...
app/assets/stylesheets/gitlab_bootstrap/common.scss
... ... @@ -17,20 +17,43 @@
17 17 .padded { padding:20px }
18 18 .ipadded { padding:20px!important }
19 19 .lborder { border-left:1px solid #eee }
20   -.no-padding { padding:0 !important; }
21   -.underlined { border-bottom: 1px solid #CCC; }
22   -.no-borders { border: none; }
23   -.vlink { color: $link_color !important; }
24 20 .underlined_link { text-decoration: underline; }
25   -.borders { border: 1px solid #ccc; @include shade; }
26 21 .hint { font-style: italic; color: #999; }
27 22 .light { color: #888 }
28 23 .tiny { font-weight: normal }
29 24  
30 25 /** PILLS & TABS**/
31   -.nav-pills a:hover { background-color: #888; }
32   -.nav-pills .active a { background-color: $style_color; }
  26 +.nav-pills {
  27 + .active a {
  28 + background: $primary_color;
  29 + }
  30 +
  31 + > li > a {
  32 + @include border-radius(0);
  33 + }
  34 + &.nav-stacked {
  35 + > li > a {
  36 + border-left: 4px solid #EEE;
  37 + padding: 12px;
  38 + }
  39 + > .active > a {
  40 + border-color: #29B;
  41 + border-radius: 0;
  42 + background: #F1F1F1;
  43 + color: $style_color;
  44 + font-weight: bold;
  45 + }
  46 + }
  47 +}
  48 +
33 49 .nav-pills > .active > a > i[class^="icon-"] { background: inherit; }
  50 +
  51 +
  52 +
  53 +/**
  54 + * nav-tabs
  55 + *
  56 + */
34 57 .nav-tabs > li > a, .nav-pills > li > a { color: $style_color; }
35 58 .nav.nav-tabs {
36 59 li {
... ...
app/assets/stylesheets/gitlab_bootstrap/fonts.scss
1   -@font-face{
2   - font-family: Korolev;
3   - src: font-url('korolev-medium-compressed.otf');
  1 +@font-face{
  2 + font-family: Yanone;
  3 + src: font-url('YanoneKaffeesatz-Light.ttf');
4 4 }
5 5  
6 6 /** Typo **/
7   -$monospace: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace;
8 7 \ No newline at end of file
  8 +$monospace: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace;
... ...
app/assets/stylesheets/gitlab_bootstrap/lists.scss
... ... @@ -23,14 +23,12 @@
23 23 border-bottom: 1px solid #ADF;
24 24 }
25 25  
26   - &:first-child {
27   - @include border-radius(4px 4px 0 0);
28   - border-top: none;
29   - }
30   -
31 26 &:last-child {
32   - @include border-radius(0 0 4px 4px);
33   - border: none;
  27 + border-bottom: none;
  28 +
  29 + &.bottom {
  30 + background: #f5f5f5;
  31 + }
34 32 }
35 33  
36 34 .author { color: #999; }
... ...
app/assets/stylesheets/gitlab_bootstrap/mixins.scss
... ... @@ -62,8 +62,8 @@
62 62 @mixin header-font {
63 63 color: $style_color;
64 64 text-shadow: 0 1px 1px #FFF;
65   - font-family: 'Korolev', sans-serif;
66   - font-size: 28px;
67   - line-height: 48px;
  65 + font-family: 'Yanone', sans-serif;
  66 + font-size: 26px;
  67 + line-height: 42px;
68 68 font-weight: normal;
69 69 }
... ...
app/assets/stylesheets/gitlab_bootstrap/tables.scss
... ... @@ -25,7 +25,7 @@ table {
25 25 }
26 26  
27 27 th, td {
28   - padding: 8px;
  28 + padding: 10px;
29 29 line-height: 18px;
30 30 text-align: left;
31 31 }
... ...
app/assets/stylesheets/sections/commits.scss
1   -.commit-box {
2   - @extend .main_box;
3   -
4   - .commit-head {
5   - @extend .top_box_content;
6   -
7   - .commit-title {
8   - line-height: 26px;
9   - margin: 0;
10   - }
11   -
12   - .commit-description {
13   - font-size: 14px;
14   - border: none;
15   - background-color: white;
16   - padding-top: 10px;
17   - }
18   -
19   - .browse-button {
20   - @extend .btn;
21   - @extend .btn-small;
22   - float: right;
23   - }
24   - }
25   -
26   - .commit-info {
27   - @extend .middle_box_content;
28   - @extend .clearfix;
29   -
30   - .sha-block {
31   - text-align: right;
32   - &:first-child {
33   - padding-bottom: 6px;
34   - }
35   -
36   - a {
37   - border-bottom: 1px solid #aaa;
38   - margin-left: 9px;
39   - }
40   - }
41   -
42   - &.merge-commit .sha-block {
43   - clear: right;
44   - }
45   -
46   - .committer {
47   - padding-left: 32px;
48   - }
49   -
50   - .author a,
51   - .committer a {
52   - font-size: 14px;
53   - line-height: 22px;
54   - text-shadow: 0 1px 1px #fff;
55   - color: #777;
56   - &:hover {
57   - color: #999;
58   - }
59   - }
60   -
61   - .avatar {
62   - margin-right: 10px;
63   - }
64   - }
65   -}
66   -
67 1 /**
68 2 *
69 3 * COMMIT SHOw
70 4 *
71 5 */
  6 +.commit-committer-link,
  7 +.commit-author-link {
  8 + font-size: 13px;
  9 + color: #555;
  10 + &:hover {
  11 + color: #999;
  12 + }
  13 +}
  14 +
72 15 .diff_file {
73 16 border: 1px solid #CCC;
74 17 margin-bottom: 1em;
... ... @@ -258,13 +201,6 @@
258 201 min-width: 65px;
259 202 font-family: $monospace;
260 203 }
261   -
262   - .commit-author-name {
263   - color: #777;
264   - &:hover {
265   - color: #999;
266   - }
267   - }
268 204 }
269 205  
270 206 .diff_file_header a,
... ...
app/assets/stylesheets/sections/events.scss
... ... @@ -47,6 +47,12 @@
47 47 .event-info {
48 48 color: #666;
49 49 }
  50 + .event-note {
  51 + padding-top: 5px;
  52 + padding-left: 5px;
  53 + display: inline-block;
  54 + color: #777;
  55 + }
50 56 }
51 57 .avatar {
52 58 position: relative;
... ...
app/assets/stylesheets/sections/header.scss
... ... @@ -33,22 +33,29 @@ header {
33 33 *
34 34 */
35 35 .app_logo {
36   - width: 170px;
37 36 float: left;
  37 + margin-right: 15px;
  38 + position: relative;
  39 + top: -5px;
  40 + padding-top: 5px;
  41 +
38 42 a {
39 43 float: left;
40 44 padding: 0px;
  45 + margin: 0 10px;
41 46  
42 47 h1 {
43   - width: 90px;
44 48 background: url('logo_dark.png') no-repeat 0px 2px;
45 49 float: left;
46   - margin-left: 2px;
47   - padding-left: 45px;
48 50 height: 40px;
  51 + width: 40px;
49 52 @include header-font;
  53 + text-indent: -9999px;
50 54 }
51 55 }
  56 + &:hover {
  57 + background-color: #EEE;
  58 + }
52 59 }
53 60  
54 61 /**
... ... @@ -60,7 +67,7 @@ header {
60 67 position: relative;
61 68 float: left;
62 69 margin: 0;
63   - margin-right: 30px;
  70 + margin-left: 15px;
64 71 @include header-font;
65 72 }
66 73  
... ... @@ -233,7 +240,7 @@ header {
233 240 .app_logo {
234 241 a {
235 242 h1 {
236   - background: url('logo_white.png') no-repeat 0px 2px;
  243 + background: url('logo_white.png') no-repeat center center;
237 244 color: #fff;
238 245 text-shadow: 0 1px 1px #111;
239 246 }
... ... @@ -244,5 +251,23 @@ header {
244 251 text-shadow: 0 1px 1px #111;
245 252 }
246 253 }
  254 +
  255 + .app_logo {
  256 + .separator {
  257 + margin-left: 0;
  258 + margin-right: 0;
  259 + }
  260 + }
  261 +
  262 + .separator {
  263 + float: left;
  264 + height: 60px;
  265 + width: 1px;
  266 + background: white;
  267 + border-left: 1px solid #DDD;
  268 + margin-top: -10px;
  269 + margin-left: 10px;
  270 + margin-right: 10px;
  271 + }
247 272 }
248 273  
... ...
app/assets/stylesheets/sections/issues.scss
1   -.issue_form_box {
2   - @extend .main_box;
3   - .issue_title {
4   - @extend .top_box_content;
5   - .clearfix {
6   - margin-bottom: 0px;
7   - input {
8   - @extend .span8;
9   - }
10   - }
11   - }
12   - .issue_middle_block {
13   - @extend .middle_box_content;
14   - height: 30px;
15   - .issue_assignee {
16   - @extend .span6;
17   - float: left;
18   - }
19   - .issue_milestone {
20   - @extend .span4;
21   - float: left;
22   - }
23   - }
24   - .issue_description {
25   - @extend .bottom_box_content;
26   - }
27   -}
28   -
29 1 .issues_table {
30 2 .issue {
31   - padding: 7px 10px;
  3 + padding: 10px;
32 4  
33 5 .issue_check {
34 6 float: left;
... ... @@ -82,38 +54,34 @@ input.check_all_issues {
82 54 }
83 55 }
84 56  
85   -@media (min-width: 800px) { .issues_filters select { width: 160px; } }
86   -@media (min-width: 1000px) { .issues_filters select { width: 200px; } }
  57 +@media (min-width: 800px) { .issues_filters select { width: 160px; } }
87 58 @media (min-width: 1200px) { .issues_filters select { width: 220px; } }
88 59  
  60 +@media (min-width: 800px) { .issues_bulk_update select { width: 120px; } }
  61 +@media (min-width: 1200px) { .issues_bulk_update select { width: 160px; } }
89 62  
90 63 #issues-table-holder {
91 64 .issues_filters {
92   - form {
93   - padding: 0;
94   - margin: 0;
95   - margin-top:7px
96   - }
97 65 }
98 66  
99 67 .issues_bulk_update {
100 68 margin: 0;
101 69 form {
102   - padding: 0;
103   - margin: 0;
104   - margin-top:7px
  70 + float:left;
105 71 }
  72 +
106 73 .update_selected_issues {
107 74 position: relative;
108   - top:-2px;
  75 + top:5px;
109 76 margin-left: 4px;
110 77 float: left;
111 78 }
112 79  
113 80 .update_issues_text {
114 81 padding: 3px;
115   - line-height: 18px;
  82 + line-height: 28px;
116 83 float: left;
  84 + color: #479;
117 85 }
118 86 }
119 87 }
... ...
app/assets/stylesheets/sections/merge_requests.scss
1   -/**
2   - * MR form
3   - *
4   - */
5   -
6   -.mr_branch_box {
7   - @extend .ui-box;
8   - margin-bottom: 20px;
9   -
10   - .body {
11   - background: #f1f1f1;
12   - }
13   -
14   -}
15 1  
16 2 /**
17 3 * MR -> show: Automerge widget
... ... @@ -47,6 +33,7 @@
47 33 }
48 34 label {
49 35 color: #444;
  36 + text-align: left
50 37 }
51 38 }
52 39  
... ... @@ -56,7 +43,7 @@
56 43 }
57 44 }
58 45  
59   -.mr_nav_tabs {
  46 +.merge-request .nav-tabs{
60 47 li {
61 48 a {
62 49 font-weight: bold;
... ... @@ -67,7 +54,7 @@
67 54 }
68 55  
69 56 li.merge_request {
70   - padding: 7px 10px;
  57 + padding: 10px;
71 58 img.avatar {
72 59 width: 32px;
73 60 margin-top: 1px;
... ... @@ -78,7 +65,7 @@ li.merge_request {
78 65 }
79 66 }
80 67  
81   -.merge_in_progress {
  68 +.merge-in-progress {
82 69 @extend .padded;
83 70 @extend .append-bottom-10;
84 71 }
... ... @@ -120,19 +107,3 @@ li.merge_request {
120 107 .mr_direction_tip {
121 108 margin-top:40px
122 109 }
123   -
124   -.merge_requests_form_box {
125   - @extend .main_box;
126   - .merge_requests_middle_box {
127   - @extend .middle_box_content;
128   - height: 30px;
129   - .merge_requests_assignee {
130   - @extend .span6;
131   - float: left;
132   - }
133   - .merge_requests_milestone {
134   - @extend .span4;
135   - float: left;
136   - }
137   - }
138   -}
... ...
app/assets/stylesheets/sections/notes.scss
... ... @@ -294,6 +294,8 @@ ul.notes {
294 294 padding: 4px 6px;
295 295 }
296 296 .note_text {
  297 + border: 1px solid #DDD;
  298 + box-shadow: none;
297 299 font-size: 14px;
298 300 height: 80px;
299 301 width: 98.6%;
... ...
app/assets/stylesheets/sections/projects.scss
... ... @@ -8,16 +8,12 @@
8 8  
9 9 .groups_box,
10 10 .projects_box {
11   - > h5 {
12   - color: $style_color;
13   - font-size: 16px;
14   - text-shadow: 0 1px 1px #fff;
15   - padding: 2px 10px;
16   - line-height: 32px;
17   - font-size: 14px;
  11 + > .title {
  12 + padding: 2px 15px;
18 13 }
19 14 .nav-projects-tabs li { padding: 0; }
20 15 .well-list {
  16 + li { padding: 15px; }
21 17 .arrow {
22 18 float: right;
23 19 padding: 10px;
... ... @@ -109,7 +105,7 @@ ul.nav.nav-projects-tabs {
109 105  
110 106 li {
111 107 a {
112   - padding: 4px 20px;
  108 + padding: 6px 25px;
113 109 margin-top: 2px;
114 110 border-color: #DDD;
115 111 background-color: #EEE;
... ...
app/assets/stylesheets/themes/ui_basic.scss
... ... @@ -4,21 +4,8 @@
4 4 *
5 5 */
6 6 .ui_basic {
7   - .app_logo {
8   - .separator {
9   - margin-left: 0;
10   - margin-right: 0;
11   - }
12   - }
13   -
14 7 .separator {
15   - float: left;
16   - height: 60px;
17   - width: 1px;
18 8 background: white;
19 9 border-left: 1px solid #DDD;
20   - margin-top: -10px;
21   - margin-left: 10px;
22   - margin-right: 10px;
23 10 }
24 11 }
... ...
app/assets/stylesheets/themes/ui_color.scss
... ... @@ -17,6 +17,15 @@
17 17 &.navbar-gitlab {
18 18 .navbar-inner {
19 19 background: #657;
  20 + .app_logo {
  21 + &:hover {
  22 + background-color: #6A5A7A;
  23 + }
  24 + }
  25 + .separator {
  26 + background: #546;
  27 + border-left: 1px solid #706080;
  28 + }
20 29 }
21 30 }
22 31 }
... ...
app/assets/stylesheets/themes/ui_gray.scss
... ... @@ -17,6 +17,15 @@
17 17 &.navbar-gitlab {
18 18 .navbar-inner {
19 19 background: #708090;
  20 + .app_logo {
  21 + &:hover {
  22 + background-color: #6A7A8A;
  23 + }
  24 + }
  25 + .separator {
  26 + background: #607080;
  27 + border-left: 1px solid #8090A0;
  28 + }
20 29 }
21 30 }
22 31 }
... ...
app/assets/stylesheets/themes/ui_mars.scss
... ... @@ -46,21 +46,26 @@
46 46 .app_logo {
47 47 a {
48 48 h1 {
49   - background: url('logo_white.png') no-repeat 0px 2px;
  49 + background: url('logo_white.png') no-repeat center center;
50 50 color: #eee;
51 51 text-shadow: 0 1px 1px #111;
52 52 }
53 53 }
54   - .separator {
55   - display: none;
  54 + &:hover {
  55 + background-color: #41464e;
56 56 }
57   -
58 57 }
59 58 .project_name {
60 59 color: #eee;
61 60 text-shadow: 0 1px 1px #111;
62 61 }
63 62 }
  63 +
  64 + .separator {
  65 + background: #31363E;
  66 + border-left: 1px solid #666;
  67 + }
  68 +
64 69 /*
65 70 * End of Application Header
66 71 *
... ...
app/assets/stylesheets/themes/ui_modern.scss
... ... @@ -17,6 +17,15 @@
17 17 &.navbar-gitlab {
18 18 .navbar-inner {
19 19 background: #567;
  20 + .app_logo {
  21 + &:hover {
  22 + background-color: #516171;
  23 + }
  24 + }
  25 + .separator {
  26 + background: #456;
  27 + border-left: 1px solid #678;
  28 + }
20 29 }
21 30 }
22 31 }
... ...
app/contexts/commit_load_context.rb
... ... @@ -9,16 +9,16 @@ class CommitLoadContext &lt; BaseContext
9 9 status: :ok
10 10 }
11 11  
12   - commit = project.commit(params[:id])
  12 + commit = project.repository.commit(params[:id])
13 13  
14 14 if commit
15 15 commit = CommitDecorator.decorate(commit)
16   - line_notes = project.commit_line_notes(commit)
  16 + line_notes = project.notes.for_commit_id(commit.id).inline
17 17  
18 18 result[:commit] = commit
19 19 result[:note] = project.build_commit_note(commit)
20 20 result[:line_notes] = line_notes
21   - result[:notes_count] = line_notes.count + project.commit_notes(commit).count
  21 + result[:notes_count] = project.notes.for_commit_id(commit.id).count
22 22  
23 23 begin
24 24 result[:suppress_diff] = true if commit.diffs.size > Commit::DIFF_SAFE_SIZE && !params[:force_show_diff]
... ...
app/contexts/filter_context.rb 0 → 100644
... ... @@ -0,0 +1,31 @@
  1 +class FilterContext
  2 + attr_accessor :items, :params
  3 +
  4 + def initialize(items, params)
  5 + @items = items
  6 + @params = params
  7 + end
  8 +
  9 + def execute
  10 + apply_filter(items)
  11 + end
  12 +
  13 + def apply_filter items
  14 + if params[:project_id]
  15 + items = items.where(project_id: params[:project_id])
  16 + end
  17 +
  18 + if params[:search].present?
  19 + items = items.search(params[:search])
  20 + end
  21 +
  22 + case params[:status]
  23 + when 'closed'
  24 + items.closed
  25 + when 'all'
  26 + items
  27 + else
  28 + items.opened
  29 + end
  30 + end
  31 +end
... ...
app/contexts/issues_list_context.rb
... ... @@ -4,7 +4,7 @@ class IssuesListContext &lt; BaseContext
4 4 attr_accessor :issues
5 5  
6 6 def execute
7   - @issues = case params[:f]
  7 + @issues = case params[:status]
8 8 when issues_filter[:all] then @project.issues
9 9 when issues_filter[:closed] then @project.issues.closed
10 10 when issues_filter[:to_me] then @project.issues.opened.assigned(current_user)
... ...
app/contexts/notes/load_context.rb
... ... @@ -9,7 +9,7 @@ module Notes
9 9  
10 10 @notes = case target_type
11 11 when "commit"
12   - project.commit_notes(project.commit(target_id)).fresh
  12 + project.notes.for_commit_id(target_id).not_inline.fresh
13 13 when "issue"
14 14 project.issues.find(target_id).notes.inc_author.fresh
15 15 when "merge_request"
... ... @@ -18,7 +18,7 @@ module Notes
18 18 project.snippets.find(target_id).notes.fresh
19 19 when "wall"
20 20 # this is the only case, where the order is DESC
21   - project.common_notes.order("created_at DESC, id DESC").limit(50)
  21 + project.notes.common.inc_author_project.order("created_at DESC, id DESC").limit(50)
22 22 end
23 23  
24 24 @notes = if after_id
... ...
app/contexts/test_hook_context.rb
1 1 class TestHookContext < BaseContext
2 2 def execute
3 3 hook = project.hooks.find(params[:id])
4   - commits = project.commits(project.default_branch, nil, 3)
  4 + commits = project.repository.commits(project.default_branch, nil, 3)
5 5 data = project.post_receive_data(commits.last.id, commits.first.id, "refs/heads/#{project.default_branch}", current_user)
6 6 hook.execute(data)
7 7 end
... ...
app/controllers/admin/dashboard_controller.rb
... ... @@ -3,10 +3,6 @@ class Admin::DashboardController &lt; AdminController
3 3 @projects = Project.order("created_at DESC").limit(10)
4 4 @users = User.order("created_at DESC").limit(10)
5 5  
6   - @resque_accessible = true
7   - @workers = Resque.workers
8   - @pending_jobs = Resque.size(:post_receive)
9   -
10 6 rescue Redis::InheritedError
11 7 @resque_accessible = false
12 8 end
... ...
app/controllers/admin/groups_controller.rb
1 1 class Admin::GroupsController < AdminController
2   - before_filter :group, only: [:edit, :show, :update, :destroy, :project_update]
  2 + before_filter :group, only: [:edit, :show, :update, :destroy, :project_update, :project_teams_update]
3 3  
4 4 def index
5 5 @groups = Group.order('name ASC')
... ... @@ -12,6 +12,8 @@ class Admin::GroupsController &lt; AdminController
12 12 @projects = @projects.not_in_group(@group) if @group.projects.present?
13 13 @projects = @projects.all
14 14 @projects.reject!(&:empty_repo?)
  15 +
  16 + @users = User.active
15 17 end
16 18  
17 19 def new
... ... @@ -65,7 +67,14 @@ class Admin::GroupsController &lt; AdminController
65 67 redirect_to :back, notice: 'Group was successfully updated.'
66 68 end
67 69  
  70 + def project_teams_update
  71 + @group.add_users_to_project_teams(params[:user_ids], params[:project_access])
  72 + redirect_to [:admin, @group], notice: 'Users was successfully added.'
  73 + end
  74 +
68 75 def destroy
  76 + @group.truncate_teams
  77 +
69 78 @group.destroy
70 79  
71 80 redirect_to admin_groups_path, notice: 'Group was successfully deleted.'
... ...
app/controllers/admin/projects_controller.rb
... ... @@ -10,6 +10,7 @@ class Admin::ProjectsController &lt; AdminController
10 10 end
11 11  
12 12 def show
  13 + @repository = @project.repository
13 14 @users = User.active
14 15 @users = @users.not_in_project(@project) if @project.users.present?
15 16 @users = @users.all
... ... @@ -19,7 +20,7 @@ class Admin::ProjectsController &lt; AdminController
19 20 end
20 21  
21 22 def team_update
22   - @project.add_users_ids_to_team(params[:user_ids], params[:project_access])
  23 + @project.team.add_users_ids(params[:user_ids], params[:project_access])
23 24  
24 25 redirect_to [:admin, @project], notice: 'Project was successfully updated.'
25 26 end
... ... @@ -35,6 +36,9 @@ class Admin::ProjectsController &lt; AdminController
35 36 end
36 37  
37 38 def destroy
  39 + # Delete team first in order to prevent multiple gitolite calls
  40 + @project.team.truncate
  41 +
38 42 @project.destroy
39 43  
40 44 redirect_to admin_projects_path, notice: 'Project was successfully deleted.'
... ...
app/controllers/admin/users_controller.rb
... ... @@ -3,13 +3,13 @@ class Admin::UsersController &lt; AdminController
3 3 @admin_users = User.scoped
4 4 @admin_users = @admin_users.filter(params[:filter])
5 5 @admin_users = @admin_users.search(params[:name]) if params[:name].present?
6   - @admin_users = @admin_users.order("name ASC").page(params[:page])
  6 + @admin_users = @admin_users.alphabetically.page(params[:page])
7 7 end
8 8  
9 9 def show
10 10 @admin_user = User.find(params[:id])
11 11  
12   - @projects = if @admin_user.projects.empty?
  12 + @projects = if @admin_user.authorized_projects.empty?
13 13 Project
14 14 else
15 15 Project.without_user(@admin_user)
... ... @@ -19,9 +19,9 @@ class Admin::UsersController &lt; AdminController
19 19 def team_update
20 20 @admin_user = User.find(params[:id])
21 21  
22   - UsersProject.user_bulk_import(
23   - @admin_user,
  22 + UsersProject.add_users_into_projects(
24 23 params[:project_ids],
  24 + [@admin_user.id],
25 25 params[:project_access]
26 26 )
27 27  
... ... @@ -98,7 +98,7 @@ class Admin::UsersController &lt; AdminController
98 98  
99 99 def destroy
100 100 @admin_user = User.find(params[:id])
101   - if @admin_user.my_own_projects.count > 0
  101 + if @admin_user.personal_projects.count > 0
102 102 redirect_to admin_users_path, alert: "User is a project owner and can't be removed." and return
103 103 end
104 104 @admin_user.destroy
... ...
app/controllers/application_controller.rb
... ... @@ -76,6 +76,12 @@ class ApplicationController &lt; ActionController::Base
76 76 end
77 77 end
78 78  
  79 + def repository
  80 + @repository ||= project.repository
  81 + rescue Grit::NoSuchPathError
  82 + nil
  83 + end
  84 +
79 85 def add_abilities
80 86 abilities << Ability
81 87 end
... ...
app/controllers/commits_controller.rb
... ... @@ -9,10 +9,10 @@ class CommitsController &lt; ProjectResourceController
9 9 before_filter :require_non_empty_project
10 10  
11 11 def show
12   - @repo = @project.repo
  12 + @repo = @project.repository
13 13 @limit, @offset = (params[:limit] || 40), (params[:offset] || 0)
14 14  
15   - @commits = @project.commits(@ref, @path, @limit, @offset)
  15 + @commits = @repo.commits(@ref, @path, @limit, @offset)
16 16 @commits = CommitDecorator.decorate(@commits)
17 17  
18 18 respond_to do |format|
... ...
app/controllers/dashboard_controller.rb
... ... @@ -20,7 +20,7 @@ class DashboardController &lt; ApplicationController
20 20  
21 21 @projects = @projects.page(params[:page]).per(30)
22 22  
23   - @events = Event.in_projects(current_user.project_ids)
  23 + @events = Event.in_projects(current_user.authorized_projects.pluck(:id))
24 24 @events = @event_filter.apply_filter(@events)
25 25 @events = @events.limit(20).offset(params[:offset] || 0)
26 26  
... ... @@ -36,14 +36,14 @@ class DashboardController &lt; ApplicationController
36 36 # Get authored or assigned open merge requests
37 37 def merge_requests
38 38 @merge_requests = current_user.cared_merge_requests
39   - @merge_requests = dashboard_filter(@merge_requests)
  39 + @merge_requests = FilterContext.new(@merge_requests, params).execute
40 40 @merge_requests = @merge_requests.recent.page(params[:page]).per(20)
41 41 end
42 42  
43 43 # Get only assigned issues
44 44 def issues
45 45 @issues = current_user.assigned_issues
46   - @issues = dashboard_filter(@issues)
  46 + @issues = FilterContext.new(@issues, params).execute
47 47 @issues = @issues.recent.page(params[:page]).per(20)
48 48 @issues = @issues.includes(:author, :project)
49 49  
... ... @@ -60,25 +60,7 @@ class DashboardController &lt; ApplicationController
60 60 end
61 61  
62 62 def event_filter
63   - @event_filter ||= EventFilter.new(params[:event_filter])
64   - end
65   -
66   - def dashboard_filter items
67   - if params[:project_id]
68   - items = items.where(project_id: params[:project_id])
69   - end
70   -
71   - if params[:search].present?
72   - items = items.search(params[:search])
73   - end
74   -
75   - case params[:status]
76   - when 'closed'
77   - items.closed
78   - when 'all'
79   - items
80   - else
81   - items.opened
82   - end
  63 + filters = cookies['event_filter'].split(',') if cookies['event_filter']
  64 + @event_filter ||= EventFilter.new(filters)
83 65 end
84 66 end
... ...
app/controllers/groups_controller.rb
... ... @@ -21,15 +21,16 @@ class GroupsController &lt; ApplicationController
21 21  
22 22 # Get authored or assigned open merge requests
23 23 def merge_requests
24   - @merge_requests = current_user.cared_merge_requests.opened
25   - @merge_requests = @merge_requests.of_group(@group).recent.page(params[:page]).per(20)
  24 + @merge_requests = current_user.cared_merge_requests.of_group(@group)
  25 + @merge_requests = FilterContext.new(@merge_requests, params).execute
  26 + @merge_requests = @merge_requests.recent.page(params[:page]).per(20)
26 27 end
27 28  
28 29 # Get only assigned issues
29 30 def issues
30   - @user = current_user
31   - @issues = current_user.assigned_issues.opened
32   - @issues = @issues.of_group(@group).recent.page(params[:page]).per(20)
  31 + @issues = current_user.assigned_issues.of_group(@group)
  32 + @issues = FilterContext.new(@issues, params).execute
  33 + @issues = @issues.recent.page(params[:page]).per(20)
33 34 @issues = @issues.includes(:author, :project)
34 35  
35 36 respond_to do |format|
... ... @@ -44,6 +45,7 @@ class GroupsController &lt; ApplicationController
44 45 @projects = result[:projects]
45 46 @merge_requests = result[:merge_requests]
46 47 @issues = result[:issues]
  48 + @wiki_pages = result[:wiki_pages]
47 49 end
48 50  
49 51 def people
... ... @@ -53,9 +55,16 @@ class GroupsController &lt; ApplicationController
53 55  
54 56 if @project
55 57 @team_member = @project.users_projects.new
  58 + else
  59 + @team_member = UsersProject.new
56 60 end
57 61 end
58 62  
  63 + def team_members
  64 + @group.add_users_to_project_teams(params[:user_ids], params[:project_access])
  65 + redirect_to people_group_path(@group), notice: 'Users was successfully added.'
  66 + end
  67 +
59 68 protected
60 69  
61 70 def group
... ... @@ -63,7 +72,7 @@ class GroupsController &lt; ApplicationController
63 72 end
64 73  
65 74 def projects
66   - @projects ||= group.projects.authorized_for(current_user).sorted_by_activity
  75 + @projects ||= current_user.authorized_projects.where(namespace_id: group.id).sorted_by_activity
67 76 end
68 77  
69 78 def project_ids
... ...
app/controllers/merge_requests_controller.rb
... ... @@ -74,6 +74,8 @@ class MergeRequestsController &lt; ProjectResourceController
74 74 @merge_request.check_if_can_be_merged
75 75 end
76 76 render json: {state: @merge_request.human_state}
  77 + rescue Gitlab::SatelliteNotExistError
  78 + render json: {state: :no_satellite}
77 79 end
78 80  
79 81 def automerge
... ... @@ -88,12 +90,12 @@ class MergeRequestsController &lt; ProjectResourceController
88 90 end
89 91  
90 92 def branch_from
91   - @commit = project.commit(params[:ref])
  93 + @commit = @repository.commit(params[:ref])
92 94 @commit = CommitDecorator.decorate(@commit)
93 95 end
94 96  
95 97 def branch_to
96   - @commit = project.commit(params[:ref])
  98 + @commit = @repository.commit(params[:ref])
97 99 @commit = CommitDecorator.decorate(@commit)
98 100 end
99 101  
... ...
app/controllers/project_resource_controller.rb
1 1 class ProjectResourceController < ApplicationController
2 2 before_filter :project
  3 + before_filter :repository
3 4 end
... ...
app/controllers/projects_controller.rb
... ... @@ -2,6 +2,7 @@ require Rails.root.join(&#39;lib&#39;, &#39;gitlab&#39;, &#39;graph&#39;, &#39;json_builder&#39;)
2 2  
3 3 class ProjectsController < ProjectResourceController
4 4 skip_before_filter :project, only: [:new, :create]
  5 + skip_before_filter :repository, only: [:new, :create]
5 6  
6 7 # Authorize
7 8 before_filter :authorize_read_project!, except: [:index, :new, :create]
... ... @@ -58,7 +59,7 @@ class ProjectsController &lt; ProjectResourceController
58 59  
59 60 respond_to do |format|
60 61 format.html do
61   - unless @project.empty_repo?
  62 + if @project.repository && !@project.repository.empty?
62 63 @last_push = current_user.recent_push(@project.id)
63 64 render :show
64 65 else
... ... @@ -102,11 +103,10 @@ class ProjectsController &lt; ProjectResourceController
102 103 def destroy
103 104 return access_denied! unless can?(current_user, :remove_project, project)
104 105  
105   - # Disable the UsersProject update_repository call, otherwise it will be
106   - # called once for every person removed from the project
107   - UsersProject.skip_callback(:destroy, :after, :update_repository)
  106 + # Delete team first in order to prevent multiple gitolite calls
  107 + project.team.truncate
  108 +
108 109 project.destroy
109   - UsersProject.set_callback(:destroy, :after, :update_repository)
110 110  
111 111 respond_to do |format|
112 112 format.html { redirect_to root_path }
... ...
app/controllers/refs_controller.rb
... ... @@ -12,7 +12,7 @@ class RefsController &lt; ProjectResourceController
12 12 respond_to do |format|
13 13 format.html do
14 14 new_path = if params[:destination] == "tree"
15   - project_tree_path(@project, @ref)
  15 + project_tree_path(@project, (@ref + "/" + params[:path]))
16 16 else
17 17 project_commits_path(@project, @ref)
18 18 end
... ... @@ -31,7 +31,7 @@ class RefsController &lt; ProjectResourceController
31 31 contents = @tree.contents
32 32 @logs = contents.map do |content|
33 33 file = params[:path] ? File.join(params[:path], content.name) : content.name
34   - last_commit = @project.commits(@commit.id, file, 1).last
  34 + last_commit = @repo.commits(@commit.id, file, 1).last
35 35 last_commit = CommitDecorator.decorate(last_commit)
36 36 {
37 37 file_name: content.name,
... ... @@ -45,10 +45,10 @@ class RefsController &lt; ProjectResourceController
45 45 def define_tree_vars
46 46 params[:path] = nil if params[:path].blank?
47 47  
48   - @repo = project.repo
49   - @commit = project.commit(@ref)
  48 + @repo = project.repository
  49 + @commit = @repo.commit(@ref)
50 50 @commit = CommitDecorator.decorate(@commit)
51   - @tree = Tree.new(@commit.tree, project, @ref, params[:path])
  51 + @tree = Tree.new(@commit.tree, @ref, params[:path])
52 52 @tree = TreeDecorator.new(@tree)
53 53 @hex_path = Digest::SHA1.hexdigest(params[:path] || "")
54 54  
... ...
app/controllers/repositories_controller.rb
... ... @@ -5,19 +5,19 @@ class RepositoriesController &lt; ProjectResourceController
5 5 before_filter :require_non_empty_project
6 6  
7 7 def show
8   - @activities = @project.commits_with_refs(20)
  8 + @activities = @repository.commits_with_refs(20)
9 9 end
10 10  
11 11 def branches
12   - @branches = @project.branches
  12 + @branches = @repository.branches
13 13 end
14 14  
15 15 def tags
16   - @tags = @project.tags
  16 + @tags = @repository.tags
17 17 end
18 18  
19 19 def stats
20   - @stats = Gitlab::GitStats.new(@project.repo, @project.root_ref)
  20 + @stats = Gitlab::GitStats.new(@repository.raw, @repository.root_ref)
21 21 @graph = @stats.graph
22 22 end
23 23  
... ... @@ -27,7 +27,7 @@ class RepositoriesController &lt; ProjectResourceController
27 27 end
28 28  
29 29  
30   - file_path = @project.archive_repo(params[:ref])
  30 + file_path = @repository.archive_repo(params[:ref])
31 31  
32 32 if file_path
33 33 # Send file to user
... ...
app/controllers/search_controller.rb
1 1 class SearchController < ApplicationController
2 2 def show
3   - result = SearchContext.new(current_user.project_ids, params).execute
  3 + result = SearchContext.new(current_user.authorized_projects.map(&:id), params).execute
4 4  
5 5 @projects = result[:projects]
6 6 @merge_requests = result[:merge_requests]
... ...
app/controllers/services_controller.rb
... ... @@ -26,7 +26,7 @@ class ServicesController &lt; ProjectResourceController
26 26 end
27 27  
28 28 def test
29   - commits = project.commits(project.default_branch, nil, 3)
  29 + commits = project.repository.commits(project.default_branch, nil, 3)
30 30 data = project.post_receive_data(commits.last.id, commits.first.id, "refs/heads/#{project.default_branch}", current_user)
31 31  
32 32 @service = project.gitlab_ci_service
... ...
app/controllers/snippets_controller.rb
... ... @@ -16,7 +16,7 @@ class SnippetsController &lt; ProjectResourceController
16 16 respond_to :html
17 17  
18 18 def index
19   - @snippets = @project.snippets.fresh
  19 + @snippets = @project.snippets.fresh.non_expired
20 20 end
21 21  
22 22 def new
... ...
app/controllers/team_members_controller.rb
... ... @@ -16,10 +16,9 @@ class TeamMembersController &lt; ProjectResourceController
16 16 end
17 17  
18 18 def create
19   - @project.add_users_ids_to_team(
20   - params[:user_ids],
21   - params[:project_access]
22   - )
  19 + users = User.where(id: params[:user_ids])
  20 +
  21 + @project.team << [users, params[:project_access]]
23 22  
24 23 if params[:redirect_to]
25 24 redirect_to params[:redirect_to]
... ... @@ -50,7 +49,7 @@ class TeamMembersController &lt; ProjectResourceController
50 49  
51 50 def apply_import
52 51 giver = Project.find(params[:source_project_id])
53   - status = UsersProject.import_team(giver, project)
  52 + status = @project.team.import(giver)
54 53 notice = status ? "Succesfully imported" : "Import failed"
55 54  
56 55 redirect_to project_team_members_path(project), notice: notice
... ...
app/controllers/tree_controller.rb
... ... @@ -22,7 +22,7 @@ class TreeController &lt; ProjectResourceController
22 22 end
23 23  
24 24 def edit
25   - @last_commit = @project.last_commit_for(@ref, @path).sha
  25 + @last_commit = @project.repository.last_commit_for(@ref, @path).sha
26 26 end
27 27  
28 28 def update
... ...
app/decorators/tree_decorator.rb
... ... @@ -6,16 +6,14 @@ class TreeDecorator &lt; ApplicationDecorator
6 6 part_path = ""
7 7 parts = path.split("\/")
8 8  
9   - #parts = parts[0...-1] if is_blob?
10   -
11   - yield(h.link_to("..", "#")) if parts.count > max_links
  9 + yield('..', nil) if parts.count > max_links
12 10  
13 11 parts.each do |part|
14 12 part_path = File.join(part_path, part) unless part_path.empty?
15 13 part_path = part if part_path.empty?
16 14  
17 15 next unless parts.last(2).include?(part) if parts.count > max_links
18   - yield(h.link_to(h.truncate(part, length: 40), h.project_tree_path(project, h.tree_join(ref, part_path))))
  16 + yield(part, h.tree_join(ref, part_path))
19 17 end
20 18 end
21 19 end
... ... @@ -26,7 +24,7 @@ class TreeDecorator &lt; ApplicationDecorator
26 24  
27 25 def up_dir_path
28 26 file = File.join(path, "..")
29   - h.project_tree_path(project, h.tree_join(ref, file))
  27 + h.tree_join(ref, file)
30 28 end
31 29  
32 30 def readme
... ...
app/helpers/application_helper.rb
... ... @@ -53,7 +53,7 @@ module ApplicationHelper
53 53  
54 54 def last_commit(project)
55 55 if project.repo_exists?
56   - time_ago_in_words(project.commit.committed_date) + " ago"
  56 + time_ago_in_words(project.repository.commit.committed_date) + " ago"
57 57 else
58 58 "Never"
59 59 end
... ... @@ -62,9 +62,11 @@ module ApplicationHelper
62 62 end
63 63  
64 64 def grouped_options_refs(destination = :tree)
  65 + repository = @project.repository
  66 +
65 67 options = [
66   - ["Branch", @project.branch_names ],
67   - [ "Tag", @project.tag_names ]
  68 + ["Branch", repository.branch_names ],
  69 + [ "Tag", repository.tag_names ]
68 70 ]
69 71  
70 72 # If reference is commit id -
... ... @@ -78,7 +80,8 @@ module ApplicationHelper
78 80 end
79 81  
80 82 def search_autocomplete_source
81   - projects = current_user.projects.map{ |p| { label: p.name_with_namespace, url: project_path(p) } }
  83 + projects = current_user.authorized_projects.map { |p| { label: p.name_with_namespace, url: project_path(p) } }
  84 + groups = current_user.authorized_groups.map { |group| { label: "<group> #{group.name}", url: group_path(group) } }
82 85  
83 86 default_nav = [
84 87 { label: "My Profile", url: profile_path },
... ... @@ -99,21 +102,21 @@ module ApplicationHelper
99 102 ]
100 103  
101 104 project_nav = []
102   - if @project && !@project.new_record?
  105 + if @project && @project.repository && @project.repository.root_ref
103 106 project_nav = [
104 107 { label: "#{@project.name} Issues", url: project_issues_path(@project) },
105   - { label: "#{@project.name} Commits", url: project_commits_path(@project, @ref || @project.root_ref) },
  108 + { label: "#{@project.name} Commits", url: project_commits_path(@project, @ref || @project.repository.root_ref) },
106 109 { label: "#{@project.name} Merge Requests", url: project_merge_requests_path(@project) },
107 110 { label: "#{@project.name} Milestones", url: project_milestones_path(@project) },
108 111 { label: "#{@project.name} Snippets", url: project_snippets_path(@project) },
109 112 { label: "#{@project.name} Team", url: project_team_index_path(@project) },
110   - { label: "#{@project.name} Tree", url: project_tree_path(@project, @ref || @project.root_ref) },
  113 + { label: "#{@project.name} Tree", url: project_tree_path(@project, @ref || @project.repository.root_ref) },
111 114 { label: "#{@project.name} Wall", url: wall_project_path(@project) },
112 115 { label: "#{@project.name} Wiki", url: project_wikis_path(@project) },
113 116 ]
114 117 end
115 118  
116   - [projects, default_nav, project_nav, help_nav].flatten.to_json
  119 + [groups, projects, default_nav, project_nav, help_nav].flatten.to_json
117 120 end
118 121  
119 122 def emoji_autocomplete_source
... ... @@ -139,6 +142,7 @@ module ApplicationHelper
139 142 event.last_push_to_non_root? &&
140 143 !event.rm_ref? &&
141 144 event.project &&
  145 + event.project.repository &&
142 146 event.project.merge_requests_enabled
143 147 end
144 148  
... ...
app/helpers/commits_helper.rb
... ... @@ -70,4 +70,12 @@ module CommitsHelper
70 70 escape_javascript(render 'commits/commit', commit: commit)
71 71 end
72 72 end
  73 +
  74 + def diff_line_content(line)
  75 + if line.blank?
  76 + " &nbsp;"
  77 + else
  78 + line
  79 + end
  80 + end
73 81 end
... ...
app/helpers/events_helper.rb
... ... @@ -20,25 +20,8 @@ module EventsHelper
20 20 [event.action_name, target].join(" ")
21 21 end
22 22  
23   - def event_image event
24   - event_image_path = if event.push?
25   - "event_push.png"
26   - elsif event.merged?
27   - "event_mr_merged.png"
28   - end
29   -
30   - return nil unless event_image_path
31   -
32   - content_tag :div, class: 'event_icon' do
33   - image_tag event_image_path
34   - end
35   - end
36   -
37 23 def event_filter_link key, tooltip
38 24 key = key.to_s
39   -
40   - filter = @event_filter.options key
41   -
42 25 inactive = if @event_filter.active? key
43 26 nil
44 27 else
... ... @@ -46,7 +29,7 @@ module EventsHelper
46 29 end
47 30  
48 31 content_tag :div, class: "filter_icon #{inactive}" do
49   - link_to dashboard_path(event_filter: filter), class: 'has_tooltip', 'data-original-title' => tooltip do
  32 + link_to dashboard_path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => tooltip do
50 33 image_tag "event_filter_#{key}.png"
51 34 end
52 35 end
... ...
app/helpers/groups_helper.rb 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +module GroupsHelper
  2 + def group_filter_path(entity, options={})
  3 + exist_opts = {
  4 + status: params[:status],
  5 + project_id: params[:project_id],
  6 + }
  7 +
  8 + options = exist_opts.merge(options)
  9 +
  10 + case entity
  11 + when 'issue' then
  12 + issues_group_path(@group, options)
  13 + when 'merge_request'
  14 + merge_requests_group_path(@group, options)
  15 + end
  16 + end
  17 +end
... ...
app/helpers/merge_requests_helper.rb
... ... @@ -4,7 +4,7 @@ module MergeRequestsHelper
4 4 event.project,
5 5 merge_request: {
6 6 source_branch: event.branch_name,
7   - target_branch: event.project.root_ref,
  7 + target_branch: event.project.repository.root_ref,
8 8 title: event.branch_name.titleize
9 9 }
10 10 )
... ...
app/helpers/namespaces_helper.rb
1 1 module NamespacesHelper
2 2 def namespaces_options(selected = :current_user, scope = :default)
3   - groups = current_user.namespaces.select {|n| n.type == 'Group'}
  3 + groups = current_user.owned_groups.select {|n| n.type == 'Group'}
4 4  
5 5 users = if scope == :all
6 6 Namespace.root
... ...
app/mailers/notify.rb
1 1 class Notify < ActionMailer::Base
2   - include Resque::Mailer
  2 +
3 3 add_template_helper ApplicationHelper
4 4 add_template_helper GitlabMarkdownHelper
5 5  
6 6 default_url_options[:host] = Gitlab.config.gitlab.host
7 7 default_url_options[:protocol] = Gitlab.config.gitlab.protocol
8 8 default_url_options[:port] = Gitlab.config.gitlab.port if Gitlab.config.gitlab_on_non_standard_port?
  9 + default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root
9 10  
10 11 default from: Gitlab.config.gitlab.email_from
11 12  
... ... @@ -87,7 +88,7 @@ class Notify &lt; ActionMailer::Base
87 88 def note_wall_email(recipient_id, note_id)
88 89 @note = Note.find(note_id)
89 90 @project = @note.project
90   - mail(to: recipient(recipient_id), subject: subject)
  91 + mail(to: recipient(recipient_id), subject: subject("note on wall"))
91 92 end
92 93  
93 94  
... ... @@ -147,12 +148,15 @@ class Notify &lt; ActionMailer::Base
147 148 # >> @project = Project.last
148 149 # => #<Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
149 150 # >> subject('Lorem ipsum')
150   - # => "GitLab | Lorem ipsum | Ruby on Rails"
  151 + # => "GitLab | Ruby on Rails | Lorem ipsum "
151 152 #
152 153 # # Accepts multiple arguments
153 154 # >> subject('Lorem ipsum', 'Dolor sit amet')
154 155 # => "GitLab | Lorem ipsum | Dolor sit amet"
155 156 def subject(*extra)
156   - "GitLab | " << extra.join(' | ') << (@project ? " | #{@project.name}" : "")
  157 + subject = "GitLab"
  158 + subject << (@project ? " | #{@project.name_with_namespace}" : "")
  159 + subject << " | " + extra.join(' | ') if extra.present?
  160 + subject
157 161 end
158 162 end
... ...
app/models/ability.rb
... ... @@ -15,35 +15,26 @@ class Ability
15 15 def project_abilities(user, project)
16 16 rules = []
17 17  
  18 + team = project.team
  19 +
18 20 # Rules based on role in project
19   - if project.master_access_for?(user)
  21 + if team.masters.include?(user)
20 22 rules << project_master_rules
21 23  
22   - elsif project.dev_access_for?(user)
  24 + elsif team.developers.include?(user)
23 25 rules << project_dev_rules
24 26  
25   - elsif project.report_access_for?(user)
  27 + elsif team.reporters.include?(user)
26 28 rules << project_report_rules
27 29  
28   - elsif project.guest_access_for?(user)
  30 + elsif team.guests.include?(user)
29 31 rules << project_guest_rules
30 32 end
31 33  
32   - if project.namespace
33   - # If user own project namespace
34   - # (Ex. group owner or account owner)
35   - if project.namespace.owner == user
36   - rules << project_admin_rules
37   - end
38   - else
39   - # For compatibility with global projects
40   - # use projects.owner_id
41   - if project.owner == user
42   - rules << project_admin_rules
43   - end
  34 + if project.owner == user
  35 + rules << project_admin_rules
44 36 end
45 37  
46   -
47 38 rules.flatten
48 39 end
49 40  
... ... @@ -107,9 +98,12 @@ class Ability
107 98 def group_abilities user, group
108 99 rules = []
109 100  
110   - rules << [
111   - :manage_group
112   - ] if group.owner == user
  101 + # Only group owner and administrators can manage group
  102 + if group.owner == user || user.admin?
  103 + rules << [
  104 + :manage_group
  105 + ]
  106 + end
113 107  
114 108 rules.flatten
115 109 end
... ...
app/models/commit.rb
... ... @@ -11,7 +11,7 @@ class Commit
11 11 attr_accessor :commit, :head, :refs
12 12  
13 13 delegate :message, :authored_date, :committed_date, :parents, :sha,
14   - :date, :committer, :author, :message, :diffs, :tree, :id,
  14 + :date, :committer, :author, :diffs, :tree, :id, :stats,
15 15 :to_patch, to: :commit
16 16  
17 17 class << self
... ... @@ -83,8 +83,8 @@ class Commit
83 83  
84 84 return result unless from && to
85 85  
86   - first = project.commit(to.try(:strip))
87   - last = project.commit(from.try(:strip))
  86 + first = project.repository.commit(to.try(:strip))
  87 + last = project.repository.commit(from.try(:strip))
88 88  
89 89 if first && last
90 90 result[:same] = (first.id == last.id)
... ... @@ -98,6 +98,8 @@ class Commit
98 98 end
99 99  
100 100 def initialize(raw_commit, head = nil)
  101 + raise "Nil as raw commit passed" unless raw_commit
  102 +
101 103 @commit = raw_commit
102 104 @head = head
103 105 end
... ... @@ -136,17 +138,17 @@ class Commit
136 138 end
137 139  
138 140 def prev_commit
139   - parents.try :first
  141 + @prev_commit ||= if parents.present?
  142 + Commit.new(parents.first)
  143 + else
  144 + nil
  145 + end
140 146 end
141 147  
142 148 def prev_commit_id
143 149 prev_commit.try :id
144 150 end
145 151  
146   - def parents_count
147   - parents && parents.count || 0
148   - end
149   -
150 152 # Shows the diff between the commit's parent and the commit.
151 153 #
152 154 # Cuts out the header and stats from #to_patch and returns only the diff.
... ...
app/models/concerns/issuable.rb 0 → 100644
... ... @@ -0,0 +1,106 @@
  1 +# == Issuable concern
  2 +#
  3 +# Contains common functionality shared between Issues and MergeRequests
  4 +#
  5 +# Used by Issue, MergeRequest
  6 +#
  7 +module Issuable
  8 + extend ActiveSupport::Concern
  9 +
  10 + included do
  11 + belongs_to :project
  12 + belongs_to :author, class_name: "User"
  13 + belongs_to :assignee, class_name: "User"
  14 + belongs_to :milestone
  15 + has_many :notes, as: :noteable, dependent: :destroy
  16 +
  17 + validates :project, presence: true
  18 + validates :author, presence: true
  19 + validates :title, presence: true, length: { within: 0..255 }
  20 + validates :closed, inclusion: { in: [true, false] }
  21 +
  22 + scope :opened, where(closed: false)
  23 + scope :closed, where(closed: true)
  24 + scope :of_group, ->(group) { where(project_id: group.project_ids) }
  25 + scope :assigned, ->(u) { where(assignee_id: u.id)}
  26 + scope :recent, order("created_at DESC")
  27 +
  28 + delegate :name,
  29 + :email,
  30 + to: :author,
  31 + prefix: true
  32 +
  33 + delegate :name,
  34 + :email,
  35 + to: :assignee,
  36 + allow_nil: true,
  37 + prefix: true
  38 +
  39 + attr_accessor :author_id_of_changes
  40 + end
  41 +
  42 + module ClassMethods
  43 + def search(query)
  44 + where("title like :query", query: "%#{query}%")
  45 + end
  46 + end
  47 +
  48 + def today?
  49 + Date.today == created_at.to_date
  50 + end
  51 +
  52 + def new?
  53 + today? && created_at == updated_at
  54 + end
  55 +
  56 + def is_assigned?
  57 + !!assignee_id
  58 + end
  59 +
  60 + def is_being_reassigned?
  61 + assignee_id_changed?
  62 + end
  63 +
  64 + def is_being_closed?
  65 + closed_changed? && closed
  66 + end
  67 +
  68 + def is_being_reopened?
  69 + closed_changed? && !closed
  70 + end
  71 +
  72 + #
  73 + # Votes
  74 + #
  75 +
  76 + # Return the number of -1 comments (downvotes)
  77 + def downvotes
  78 + notes.select(&:downvote?).size
  79 + end
  80 +
  81 + def downvotes_in_percent
  82 + if votes_count.zero?
  83 + 0
  84 + else
  85 + 100.0 - upvotes_in_percent
  86 + end
  87 + end
  88 +
  89 + # Return the number of +1 comments (upvotes)
  90 + def upvotes
  91 + notes.select(&:upvote?).size
  92 + end
  93 +
  94 + def upvotes_in_percent
  95 + if votes_count.zero?
  96 + 0
  97 + else
  98 + 100.0 / votes_count * upvotes
  99 + end
  100 + end
  101 +
  102 + # Return the total number of votes
  103 + def votes_count
  104 + upvotes + downvotes
  105 + end
  106 +end
... ...
app/models/event.rb
... ... @@ -15,9 +15,6 @@
15 15 #
16 16  
17 17 class Event < ActiveRecord::Base
18   - include NoteEvent
19   - include PushEvent
20   -
21 18 attr_accessible :project, :action, :data, :author_id, :project_id,
22 19 :target_id, :target_type
23 20  
... ... @@ -113,26 +110,6 @@ class Event &lt; ActiveRecord::Base
113 110 target_type == "MergeRequest"
114 111 end
115 112  
116   - def new_issue?
117   - target_type == "Issue" &&
118   - action == Created
119   - end
120   -
121   - def new_merge_request?
122   - target_type == "MergeRequest" &&
123   - action == Created
124   - end
125   -
126   - def changed_merge_request?
127   - target_type == "MergeRequest" &&
128   - [Closed, Reopened].include?(action)
129   - end
130   -
131   - def changed_issue?
132   - target_type == "Issue" &&
133   - [Closed, Reopened].include?(action)
134   - end
135   -
136 113 def joined?
137 114 action == Joined
138 115 end
... ... @@ -170,4 +147,143 @@ class Event &lt; ActiveRecord::Base
170 147 "opened"
171 148 end
172 149 end
  150 +
  151 + def valid_push?
  152 + data[:ref]
  153 + rescue => ex
  154 + false
  155 + end
  156 +
  157 + def tag?
  158 + data[:ref]["refs/tags"]
  159 + end
  160 +
  161 + def branch?
  162 + data[:ref]["refs/heads"]
  163 + end
  164 +
  165 + def new_branch?
  166 + commit_from =~ /^00000/
  167 + end
  168 +
  169 + def new_ref?
  170 + commit_from =~ /^00000/
  171 + end
  172 +
  173 + def rm_ref?
  174 + commit_to =~ /^00000/
  175 + end
  176 +
  177 + def md_ref?
  178 + !(rm_ref? || new_ref?)
  179 + end
  180 +
  181 + def commit_from
  182 + data[:before]
  183 + end
  184 +
  185 + def commit_to
  186 + data[:after]
  187 + end
  188 +
  189 + def ref_name
  190 + if tag?
  191 + tag_name
  192 + else
  193 + branch_name
  194 + end
  195 + end
  196 +
  197 + def branch_name
  198 + @branch_name ||= data[:ref].gsub("refs/heads/", "")
  199 + end
  200 +
  201 + def tag_name
  202 + @tag_name ||= data[:ref].gsub("refs/tags/", "")
  203 + end
  204 +
  205 + # Max 20 commits from push DESC
  206 + def commits
  207 + @commits ||= data[:commits].map { |commit| repository.commit(commit[:id]) }.reverse
  208 + end
  209 +
  210 + def commits_count
  211 + data[:total_commits_count] || commits.count || 0
  212 + end
  213 +
  214 + def ref_type
  215 + tag? ? "tag" : "branch"
  216 + end
  217 +
  218 + def push_action_name
  219 + if new_ref?
  220 + "pushed new"
  221 + elsif rm_ref?
  222 + "deleted"
  223 + else
  224 + "pushed to"
  225 + end
  226 + end
  227 +
  228 + def repository
  229 + project.repository
  230 + end
  231 +
  232 + def parent_commit
  233 + repository.commit(commit_from)
  234 + rescue => ex
  235 + nil
  236 + end
  237 +
  238 + def last_commit
  239 + repository.commit(commit_to)
  240 + rescue => ex
  241 + nil
  242 + end
  243 +
  244 + def push_with_commits?
  245 + md_ref? && commits.any? && parent_commit && last_commit
  246 + rescue Grit::NoSuchPathError
  247 + false
  248 + end
  249 +
  250 + def last_push_to_non_root?
  251 + branch? && project.default_branch != branch_name
  252 + end
  253 +
  254 + def note_commit_id
  255 + target.commit_id
  256 + end
  257 +
  258 + def note_short_commit_id
  259 + note_commit_id[0..8]
  260 + end
  261 +
  262 + def note_commit?
  263 + target.noteable_type == "Commit"
  264 + end
  265 +
  266 + def note_target
  267 + target.noteable
  268 + end
  269 +
  270 + def note_target_id
  271 + if note_commit?
  272 + target.commit_id
  273 + else
  274 + target.noteable_id.to_s
  275 + end
  276 + end
  277 +
  278 + def wall_note?
  279 + target.noteable_type.blank?
  280 + end
  281 +
  282 + def note_target_type
  283 + if target.noteable_type.present?
  284 + target.noteable_type.titleize
  285 + else
  286 + "Wall"
  287 + end.downcase
  288 + end
173 289 end
... ...
app/models/gitlab_ci_service.rb
... ... @@ -23,20 +23,12 @@ class GitlabCiService &lt; Service
23 23  
24 24 after_save :compose_service_hook, if: :activated?
25 25  
26   - def activated?
27   - active
28   - end
29   -
30 26 def compose_service_hook
31 27 hook = service_hook || build_service_hook
32 28 hook.url = [project_url, "/build", "?token=#{token}"].join("")
33 29 hook.save
34 30 end
35 31  
36   - def commit_badge_path sha
37   - project_url + "/status?sha=#{sha}"
38   - end
39   -
40 32 def commit_status_path sha
41 33 project_url + "/builds/#{sha}/status.json?token=#{token}"
42 34 end
... ...
app/models/group.rb
... ... @@ -12,6 +12,14 @@
12 12 #
13 13  
14 14 class Group < Namespace
  15 + def add_users_to_project_teams(user_ids, project_access)
  16 + UsersProject.add_users_into_projects(
  17 + projects.map(&:id),
  18 + user_ids,
  19 + project_access
  20 + )
  21 + end
  22 +
15 23 def users
16 24 users = User.joins(:users_projects).where(users_projects: {project_id: project_ids})
17 25 users = users << owner
... ... @@ -21,4 +29,8 @@ class Group &lt; Namespace
21 29 def human_name
22 30 name
23 31 end
  32 +
  33 + def truncate_teams
  34 + UsersProject.truncate_teams(project_ids)
  35 + end
24 36 end
... ...
app/models/issue.rb
... ... @@ -17,8 +17,7 @@
17 17 #
18 18  
19 19 class Issue < ActiveRecord::Base
20   - include IssueCommonality
21   - include Votes
  20 + include Issuable
22 21  
23 22 attr_accessible :title, :assignee_id, :closed, :position, :description,
24 23 :milestone_id, :label_list, :author_id_of_changes
... ...
app/models/key.rb
... ... @@ -73,7 +73,7 @@ class Key &lt; ActiveRecord::Base
73 73 if is_deploy_key
74 74 [project]
75 75 else
76   - user.projects
  76 + user.authorized_projects
77 77 end
78 78 end
79 79  
... ...
app/models/merge_request.rb
... ... @@ -20,11 +20,10 @@
20 20 #
21 21  
22 22 require Rails.root.join("app/models/commit")
23   -require Rails.root.join("app/roles/static_model")
  23 +require Rails.root.join("lib/static_model")
24 24  
25 25 class MergeRequest < ActiveRecord::Base
26   - include IssueCommonality
27   - include Votes
  26 + include Issuable
28 27  
29 28 attr_accessible :title, :assignee_id, :closed, :target_branch, :source_branch, :milestone_id,
30 29 :author_id_of_changes
... ...
app/models/milestone.rb
... ... @@ -29,7 +29,7 @@ class Milestone &lt; ActiveRecord::Base
29 29  
30 30 def expired?
31 31 if due_date
32   - due_date < Date.today
  32 + due_date.past?
33 33 else
34 34 false
35 35 end
... ... @@ -58,7 +58,13 @@ class Milestone &lt; ActiveRecord::Base
58 58 end
59 59  
60 60 def expires_at
61   - "expires at #{due_date.stamp("Aug 21, 2011")}" if due_date
  61 + if due_date
  62 + if due_date.past?
  63 + "expired at #{due_date.stamp("Aug 21, 2011")}"
  64 + else
  65 + "expires at #{due_date.stamp("Aug 21, 2011")}"
  66 + end
  67 + end
62 68 end
63 69  
64 70 def can_be_closed?
... ...
app/models/namespace.rb
... ... @@ -27,10 +27,13 @@ class Namespace &lt; ActiveRecord::Base
27 27  
28 28 after_create :ensure_dir_exist
29 29 after_update :move_dir
  30 + after_commit :update_gitolite, on: :update, if: :require_update_gitolite
30 31 after_destroy :rm_dir
31 32  
32 33 scope :root, where('type IS NULL')
33 34  
  35 + attr_accessor :require_update_gitolite
  36 +
34 37 def self.search query
35 38 where("name LIKE :query OR path LIKE :query", query: "%#{query}%")
36 39 end
... ... @@ -48,8 +51,17 @@ class Namespace &lt; ActiveRecord::Base
48 51 end
49 52  
50 53 def ensure_dir_exist
51   - namespace_dir_path = File.join(Gitlab.config.gitolite.repos_path, path)
52   - system("mkdir -m 770 #{namespace_dir_path}") unless File.exists?(namespace_dir_path)
  54 + unless dir_exists?
  55 + FileUtils.mkdir( namespace_full_path, mode: 0770 )
  56 + end
  57 + end
  58 +
  59 + def dir_exists?
  60 + File.exists?(namespace_full_path)
  61 + end
  62 +
  63 + def namespace_full_path
  64 + @namespace_full_path ||= File.join(Gitlab.config.gitolite.repos_path, path)
53 65 end
54 66  
55 67 def move_dir
... ... @@ -59,16 +71,25 @@ class Namespace &lt; ActiveRecord::Base
59 71 if File.exists?(new_path)
60 72 raise "Already exists"
61 73 end
62   -
63   - if system("mv #{old_path} #{new_path}")
  74 +
  75 + begin
  76 + FileUtils.mv( old_path, new_path )
64 77 send_update_instructions
  78 + @require_update_gitolite = true
  79 + rescue Exception => e
  80 + raise "Namespace move error #{old_path} #{new_path}"
65 81 end
66 82 end
67 83 end
68 84  
  85 + def update_gitolite
  86 + @require_update_gitolite = false
  87 + projects.each(&:update_repository)
  88 + end
  89 +
69 90 def rm_dir
70 91 dir_path = File.join(Gitlab.config.gitolite.repos_path, path)
71   - system("rm -rf #{dir_path}")
  92 + FileUtils.rm_r( dir_path, force: true )
72 93 end
73 94  
74 95 def send_update_instructions
... ...
app/models/note.rb
... ... @@ -4,7 +4,6 @@
4 4 #
5 5 # id :integer not null, primary key
6 6 # note :text
7   -# noteable_id :string(255)
8 7 # noteable_type :string(255)
9 8 # author_id :integer
10 9 # created_at :datetime not null
... ... @@ -12,6 +11,8 @@
12 11 # project_id :integer
13 12 # attachment :string(255)
14 13 # line_code :string(255)
  14 +# commit_id :string(255)
  15 +# noteable_id :integer
15 16 #
16 17  
17 18 require 'carrierwave/orm/activerecord'
... ... @@ -41,11 +42,11 @@ class Note &lt; ActiveRecord::Base
41 42 mount_uploader :attachment, AttachmentUploader
42 43  
43 44 # Scopes
44   - scope :for_commits, ->{ where(noteable_type: "Commit") }
45   - scope :common, ->{ where(noteable_id: nil, commit_id: nil) }
46   - scope :today, ->{ where("created_at >= :date", date: Date.today) }
47   - scope :last_week, ->{ where("created_at >= :date", date: (Date.today - 7.days)) }
48   - scope :since, ->(day) { where("created_at >= :date", date: (day)) }
  45 + scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
  46 + scope :inline, where("line_code IS NOT NULL")
  47 + scope :not_inline, where("line_code IS NULL")
  48 +
  49 + scope :common, ->{ where(noteable_type: ["", nil]) }
49 50 scope :fresh, ->{ order("created_at ASC, id ASC") }
50 51 scope :inc_author_project, ->{ includes(:project, :author) }
51 52 scope :inc_author, ->{ includes(:author) }
... ... @@ -126,7 +127,7 @@ class Note &lt; ActiveRecord::Base
126 127 # override to return commits, which are not active record
127 128 def noteable
128 129 if for_commit?
129   - project.commit(commit_id)
  130 + project.repository.commit(commit_id)
130 131 else
131 132 super
132 133 end
... ...
app/models/project.rb
... ... @@ -9,7 +9,7 @@
9 9 # created_at :datetime not null
10 10 # updated_at :datetime not null
11 11 # private_flag :boolean default(TRUE), not null
12   -# owner_id :integer
  12 +# creator_id :integer
13 13 # default_branch :string(255)
14 14 # issues_enabled :boolean default(TRUE), not null
15 15 # wall_enabled :boolean default(TRUE), not null
... ... @@ -21,18 +21,14 @@
21 21 require "grit"
22 22  
23 23 class Project < ActiveRecord::Base
24   - include Repository
25   - include PushObserver
26   - include Authority
27   - include Team
28   - include NamespacedProject
  24 + include Gitolited
29 25  
30 26 class TransferError < StandardError; end
31 27  
32 28 attr_accessible :name, :path, :description, :default_branch, :issues_enabled,
33 29 :wall_enabled, :merge_requests_enabled, :wiki_enabled, as: [:default, :admin]
34 30  
35   - attr_accessible :namespace_id, :owner_id, as: :admin
  31 + attr_accessible :namespace_id, :creator_id, as: :admin
36 32  
37 33 attr_accessor :error_code
38 34  
... ... @@ -40,10 +36,10 @@ class Project &lt; ActiveRecord::Base
40 36 belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'"
41 37 belongs_to :namespace
42 38  
43   - # TODO: replace owner with creator.
44   - # With namespaces a project owner will be a namespace owner
45   - # so this field makes sense only for global projects
46   - belongs_to :owner, class_name: "User"
  39 + belongs_to :creator,
  40 + class_name: "User",
  41 + foreign_key: "creator_id"
  42 +
47 43 has_many :users, through: :users_projects
48 44 has_many :events, dependent: :destroy
49 45 has_many :merge_requests, dependent: :destroy
... ... @@ -62,9 +58,11 @@ class Project &lt; ActiveRecord::Base
62 58 delegate :name, to: :owner, allow_nil: true, prefix: true
63 59  
64 60 # Validations
65   - validates :owner, presence: true
  61 + validates :creator, presence: true
66 62 validates :description, length: { within: 0..2000 }
67   - validates :name, presence: true, length: { within: 0..255 }
  63 + validates :name, presence: true, length: { within: 0..255 },
  64 + format: { with: Gitlab::Regex.project_name_regex,
  65 + message: "only letters, digits, spaces & '_' '-' '.' allowed. Letter should be first" }
68 66 validates :path, presence: true, length: { within: 0..255 },
69 67 format: { with: Gitlab::Regex.path_regex,
70 68 message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
... ... @@ -77,19 +75,14 @@ class Project &lt; ActiveRecord::Base
77 75 validate :check_limit, :repo_name
78 76  
79 77 # Scopes
80   - scope :public_only, where(private_flag: false)
81   - scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.projects.map(&:id) ) }
  78 + scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) }
82 79 scope :not_in_group, ->(group) { where("id NOT IN (:ids)", ids: group.project_ids ) }
  80 + scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) }
83 81 scope :sorted_by_activity, ->() { order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC") }
84 82 scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
85 83 scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) }
86 84  
87 85 class << self
88   - def authorized_for user
89   - projects = includes(:users_projects, :namespace)
90   - projects = projects.where("users_projects.user_id = :user_id or projects.owner_id = :user_id or namespaces.owner_id = :user_id", user_id: user.id)
91   - end
92   -
93 86 def active
94 87 joins(:issues, :notes, :merge_requests).order("issues.created_at, notes.created_at, merge_requests.created_at DESC")
95 88 end
... ... @@ -101,8 +94,10 @@ class Project &lt; ActiveRecord::Base
101 94 def find_with_namespace(id)
102 95 if id.include?("/")
103 96 id = id.split("/")
104   - namespace_id = Namespace.find_by_path(id.first).id
105   - where(namespace_id: namespace_id).find_by_path(id.last)
  97 + namespace = Namespace.find_by_path(id.first)
  98 + return nil unless namespace
  99 +
  100 + where(namespace_id: namespace.id).find_by_path(id.second)
106 101 else
107 102 where(path: id, namespace_id: nil).last
108 103 end
... ... @@ -122,7 +117,7 @@ class Project &lt; ActiveRecord::Base
122 117 #
123 118 project.path = project.name.dup.parameterize
124 119  
125   - project.owner = user
  120 + project.creator = user
126 121  
127 122 # Apply namespace if user has access to it
128 123 # else fallback to user namespace
... ... @@ -162,6 +157,20 @@ class Project &lt; ActiveRecord::Base
162 157 end
163 158 end
164 159  
  160 + def team
  161 + @team ||= Team.new(self)
  162 + end
  163 +
  164 + def repository
  165 + if path
  166 + @repository ||= Repository.new(path_with_namespace, default_branch)
  167 + else
  168 + nil
  169 + end
  170 + rescue Grit::NoSuchPathError
  171 + nil
  172 + end
  173 +
165 174 def git_error?
166 175 error_code == :gitolite
167 176 end
... ... @@ -171,8 +180,8 @@ class Project &lt; ActiveRecord::Base
171 180 end
172 181  
173 182 def check_limit
174   - unless owner.can_create_project?
175   - errors[:base] << ("Your own projects limit is #{owner.projects_limit}! Please contact administrator to increase it")
  183 + unless creator.can_create_project?
  184 + errors[:base] << ("Your own projects limit is #{creator.projects_limit}! Please contact administrator to increase it")
176 185 end
177 186 rescue
178 187 errors[:base] << ("Can't check your ability to create project")
... ... @@ -198,30 +207,10 @@ class Project &lt; ActiveRecord::Base
198 207 [Gitlab.config.gitlab.url, path_with_namespace].join("/")
199 208 end
200 209  
201   - def common_notes
202   - notes.where(noteable_type: ["", nil]).inc_author_project
203   - end
204   -
205 210 def build_commit_note(commit)
206 211 notes.new(commit_id: commit.id, noteable_type: "Commit")
207 212 end
208 213  
209   - def commit_notes(commit)
210   - notes.where(commit_id: commit.id, noteable_type: "Commit").where('line_code IS NULL OR line_code = ""')
211   - end
212   -
213   - def commit_line_notes(commit)
214   - notes.where(commit_id: commit.id, noteable_type: "Commit").where("line_code IS NOT NULL")
215   - end
216   -
217   - def public?
218   - !private_flag
219   - end
220   -
221   - def private?
222   - private_flag
223   - end
224   -
225 214 def last_activity
226 215 last_event
227 216 end
... ... @@ -262,7 +251,282 @@ class Project &lt; ActiveRecord::Base
262 251  
263 252 def send_move_instructions
264 253 self.users_projects.each do |member|
265   - Notify.project_was_moved_email(member.id).deliver
  254 + Notify.delay.project_was_moved_email(member.id)
  255 + end
  256 + end
  257 +
  258 + def owner
  259 + if namespace
  260 + namespace_owner
  261 + else
  262 + creator
  263 + end
  264 + end
  265 +
  266 + def team_member_by_name_or_email(name = nil, email = nil)
  267 + user = users.where("name like ? or email like ?", name, email).first
  268 + users_projects.where(user: user) if user
  269 + end
  270 +
  271 + # Get Team Member record by user id
  272 + def team_member_by_id(user_id)
  273 + users_projects.find_by_user_id(user_id)
  274 + end
  275 +
  276 + def transfer(new_namespace)
  277 + Project.transaction do
  278 + old_namespace = namespace
  279 + self.namespace = new_namespace
  280 +
  281 + old_dir = old_namespace.try(:path) || ''
  282 + new_dir = new_namespace.try(:path) || ''
  283 +
  284 + old_repo = if old_dir.present?
  285 + File.join(old_dir, self.path)
  286 + else
  287 + self.path
  288 + end
  289 +
  290 + if Project.where(path: self.path, namespace_id: new_namespace.try(:id)).present?
  291 + raise TransferError.new("Project with same path in target namespace already exists")
  292 + end
  293 +
  294 + Gitlab::ProjectMover.new(self, old_dir, new_dir).execute
  295 +
  296 + gitolite.move_repository(old_repo, self)
  297 +
  298 + save!
  299 + end
  300 + rescue Gitlab::ProjectMover::ProjectMoveError => ex
  301 + raise Project::TransferError.new(ex.message)
  302 + end
  303 +
  304 + def name_with_namespace
  305 + @name_with_namespace ||= begin
  306 + if namespace
  307 + namespace.human_name + " / " + name
  308 + else
  309 + name
  310 + end
  311 + end
  312 + end
  313 +
  314 + def namespace_owner
  315 + namespace.try(:owner)
  316 + end
  317 +
  318 + def path_with_namespace
  319 + if namespace
  320 + namespace.path + '/' + path
  321 + else
  322 + path
  323 + end
  324 + end
  325 +
  326 + # This method will be called after each post receive and only if the provided
  327 + # user is present in GitLab.
  328 + #
  329 + # All callbacks for post receive should be placed here.
  330 + def trigger_post_receive(oldrev, newrev, ref, user)
  331 + data = post_receive_data(oldrev, newrev, ref, user)
  332 +
  333 + # Create push event
  334 + self.observe_push(data)
  335 +
  336 + if push_to_branch? ref, oldrev
  337 + # Close merged MR
  338 + self.update_merge_requests(oldrev, newrev, ref, user)
  339 +
  340 + # Execute web hooks
  341 + self.execute_hooks(data.dup)
  342 +
  343 + # Execute project services
  344 + self.execute_services(data.dup)
  345 + end
  346 +
  347 + # Create satellite
  348 + self.satellite.create unless self.satellite.exists?
  349 +
  350 + # Discover the default branch, but only if it hasn't already been set to
  351 + # something else
  352 + if repository && default_branch.nil?
  353 + update_attributes(default_branch: self.repository.discover_default_branch)
  354 + end
  355 + end
  356 +
  357 + def push_to_branch? ref, oldrev
  358 + ref_parts = ref.split('/')
  359 +
  360 + # Return if this is not a push to a branch (e.g. new commits)
  361 + !(ref_parts[1] !~ /heads/ || oldrev == "00000000000000000000000000000000")
  362 + end
  363 +
  364 + def observe_push(data)
  365 + Event.create(
  366 + project: self,
  367 + action: Event::Pushed,
  368 + data: data,
  369 + author_id: data[:user_id]
  370 + )
  371 + end
  372 +
  373 + def execute_hooks(data)
  374 + hooks.each { |hook| hook.execute(data) }
  375 + end
  376 +
  377 + def execute_services(data)
  378 + services.each do |service|
  379 +
  380 + # Call service hook only if it is active
  381 + service.execute(data) if service.active
  382 + end
  383 + end
  384 +
  385 + # Produce a hash of post-receive data
  386 + #
  387 + # data = {
  388 + # before: String,
  389 + # after: String,
  390 + # ref: String,
  391 + # user_id: String,
  392 + # user_name: String,
  393 + # repository: {
  394 + # name: String,
  395 + # url: String,
  396 + # description: String,
  397 + # homepage: String,
  398 + # },
  399 + # commits: Array,
  400 + # total_commits_count: Fixnum
  401 + # }
  402 + #
  403 + def post_receive_data(oldrev, newrev, ref, user)
  404 +
  405 + push_commits = repository.commits_between(oldrev, newrev)
  406 +
  407 + # Total commits count
  408 + push_commits_count = push_commits.size
  409 +
  410 + # Get latest 20 commits ASC
  411 + push_commits_limited = push_commits.last(20)
  412 +
  413 + # Hash to be passed as post_receive_data
  414 + data = {
  415 + before: oldrev,
  416 + after: newrev,
  417 + ref: ref,
  418 + user_id: user.id,
  419 + user_name: user.name,
  420 + repository: {
  421 + name: name,
  422 + url: url_to_repo,
  423 + description: description,
  424 + homepage: web_url,
  425 + },
  426 + commits: [],
  427 + total_commits_count: push_commits_count
  428 + }
  429 +
  430 + # For perfomance purposes maximum 20 latest commits
  431 + # will be passed as post receive hook data.
  432 + #
  433 + push_commits_limited.each do |commit|
  434 + data[:commits] << {
  435 + id: commit.id,
  436 + message: commit.safe_message,
  437 + timestamp: commit.date.xmlschema,
  438 + url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{commit.id}",
  439 + author: {
  440 + name: commit.author_name,
  441 + email: commit.author_email
  442 + }
  443 + }
266 444 end
  445 +
  446 + data
  447 + end
  448 +
  449 + def update_merge_requests(oldrev, newrev, ref, user)
  450 + return true unless ref =~ /heads/
  451 + branch_name = ref.gsub("refs/heads/", "")
  452 + c_ids = self.repository.commits_between(oldrev, newrev).map(&:id)
  453 +
  454 + # Update code for merge requests
  455 + mrs = self.merge_requests.opened.find_all_by_branch(branch_name).all
  456 + mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked }
  457 +
  458 + # Close merge requests
  459 + mrs = self.merge_requests.opened.where(target_branch: branch_name).all
  460 + mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) }
  461 + mrs.each { |merge_request| merge_request.merge!(user.id) }
  462 +
  463 + true
  464 + end
  465 +
  466 + def valid_repo?
  467 + repo
  468 + rescue
  469 + errors.add(:path, "Invalid repository path")
  470 + false
  471 + end
  472 +
  473 + def empty_repo?
  474 + !repository || repository.empty?
  475 + end
  476 +
  477 + def satellite
  478 + @satellite ||= Gitlab::Satellite::Satellite.new(self)
  479 + end
  480 +
  481 + def repo
  482 + repository.raw
  483 + end
  484 +
  485 + def url_to_repo
  486 + gitolite.url_to_repo(path_with_namespace)
  487 + end
  488 +
  489 + def namespace_dir
  490 + namespace.try(:path) || ''
  491 + end
  492 +
  493 + def update_repository
  494 + gitolite.update_repository(self)
  495 + end
  496 +
  497 + def destroy_repository
  498 + gitolite.remove_repository(self)
  499 + end
  500 +
  501 + def repo_exists?
  502 + @repo_exists ||= (repository && repository.branches.present?)
  503 + rescue
  504 + @repo_exists = false
  505 + end
  506 +
  507 + def open_branches
  508 + if protected_branches.empty?
  509 + self.repo.heads
  510 + else
  511 + pnames = protected_branches.map(&:name)
  512 + self.repo.heads.reject { |h| pnames.include?(h.name) }
  513 + end.sort_by(&:name)
  514 + end
  515 +
  516 + def root_ref?(branch)
  517 + repository.root_ref == branch
  518 + end
  519 +
  520 + def ssh_url_to_repo
  521 + url_to_repo
  522 + end
  523 +
  524 + def http_url_to_repo
  525 + http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
  526 + end
  527 +
  528 + # Check if current branch name is marked as protected in the system
  529 + def protected_branch? branch_name
  530 + protected_branches.map(&:name).include?(branch_name)
267 531 end
268 532 end
... ...
app/models/protected_branch.rb
... ... @@ -10,7 +10,7 @@
10 10 #
11 11  
12 12 class ProtectedBranch < ActiveRecord::Base
13   - include GitHost
  13 + include Gitolited
14 14  
15 15 attr_accessible :name
16 16  
... ... @@ -22,10 +22,10 @@ class ProtectedBranch &lt; ActiveRecord::Base
22 22 after_destroy :update_repository
23 23  
24 24 def update_repository
25   - git_host.update_repository(project)
  25 + gitolite.update_repository(project)
26 26 end
27 27  
28 28 def commit
29   - project.commit(self.name)
  29 + project.repository.commit(self.name)
30 30 end
31 31 end
... ...
app/models/repository.rb 0 → 100644
... ... @@ -0,0 +1,169 @@
  1 +class Repository
  2 + # Repository directory name with namespace direcotry
  3 + # Examples:
  4 + # gitlab/gitolite
  5 + # diaspora
  6 + #
  7 + attr_accessor :path_with_namespace
  8 +
  9 + # Grit repo object
  10 + attr_accessor :repo
  11 +
  12 + # Default branch in the repository
  13 + attr_accessor :root_ref
  14 +
  15 + def initialize(path_with_namespace, root_ref = 'master')
  16 + @root_ref = root_ref || "master"
  17 + @path_with_namespace = path_with_namespace
  18 +
  19 + # Init grit repo object
  20 + repo
  21 + end
  22 +
  23 + def raw
  24 + repo
  25 + end
  26 +
  27 + def path_to_repo
  28 + @path_to_repo ||= File.join(Gitlab.config.gitolite.repos_path, "#{path_with_namespace}.git")
  29 + end
  30 +
  31 + def repo
  32 + @repo ||= Grit::Repo.new(path_to_repo)
  33 + end
  34 +
  35 + def commit(commit_id = nil)
  36 + Commit.find_or_first(repo, commit_id, root_ref)
  37 + end
  38 +
  39 + def fresh_commits(n = 10)
  40 + Commit.fresh_commits(repo, n)
  41 + end
  42 +
  43 + def commits_with_refs(n = 20)
  44 + Commit.commits_with_refs(repo, n)
  45 + end
  46 +
  47 + def commits_since(date)
  48 + Commit.commits_since(repo, date)
  49 + end
  50 +
  51 + def commits(ref, path = nil, limit = nil, offset = nil)
  52 + Commit.commits(repo, ref, path, limit, offset)
  53 + end
  54 +
  55 + def last_commit_for(ref, path = nil)
  56 + commits(ref, path, 1).first
  57 + end
  58 +
  59 + def commits_between(from, to)
  60 + Commit.commits_between(repo, from, to)
  61 + end
  62 +
  63 + def has_post_receive_file?
  64 + !!hook_file
  65 + end
  66 +
  67 + def valid_post_receive_file?
  68 + valid_hook_file == hook_file
  69 + end
  70 +
  71 + def valid_hook_file
  72 + @valid_hook_file ||= File.read(Rails.root.join('lib', 'hooks', 'post-receive'))
  73 + end
  74 +
  75 + def hook_file
  76 + @hook_file ||= begin
  77 + hook_path = File.join(path_to_repo, 'hooks', 'post-receive')
  78 + File.read(hook_path) if File.exists?(hook_path)
  79 + end
  80 + end
  81 +
  82 + # Returns an Array of branch names
  83 + def branch_names
  84 + repo.branches.collect(&:name).sort
  85 + end
  86 +
  87 + # Returns an Array of Branches
  88 + def branches
  89 + repo.branches.sort_by(&:name)
  90 + end
  91 +
  92 + # Returns an Array of tag names
  93 + def tag_names
  94 + repo.tags.collect(&:name).sort.reverse
  95 + end
  96 +
  97 + # Returns an Array of Tags
  98 + def tags
  99 + repo.tags.sort_by(&:name).reverse
  100 + end
  101 +
  102 + # Returns an Array of branch and tag names
  103 + def ref_names
  104 + [branch_names + tag_names].flatten
  105 + end
  106 +
  107 + def heads
  108 + @heads ||= repo.heads
  109 + end
  110 +
  111 + def tree(fcommit, path = nil)
  112 + fcommit = commit if fcommit == :head
  113 + tree = fcommit.tree
  114 + path ? (tree / path) : tree
  115 + end
  116 +
  117 + def has_commits?
  118 + !!commit
  119 + rescue Grit::NoSuchPathError
  120 + false
  121 + end
  122 +
  123 + def empty?
  124 + !has_commits?
  125 + end
  126 +
  127 + # Discovers the default branch based on the repository's available branches
  128 + #
  129 + # - If no branches are present, returns nil
  130 + # - If one branch is present, returns its name
  131 + # - If two or more branches are present, returns the one that has a name
  132 + # matching root_ref (default_branch or 'master' if default_branch is nil)
  133 + def discover_default_branch
  134 + if branch_names.length == 0
  135 + nil
  136 + elsif branch_names.length == 1
  137 + branch_names.first
  138 + else
  139 + branch_names.select { |v| v == root_ref }.first
  140 + end
  141 + end
  142 +
  143 + # Archive Project to .tar.gz
  144 + #
  145 + # Already packed repo archives stored at
  146 + # app_root/tmp/repositories/project_name/project_name-commit-id.tag.gz
  147 + #
  148 + def archive_repo(ref)
  149 + ref = ref || self.root_ref
  150 + commit = self.commit(ref)
  151 + return nil unless commit
  152 +
  153 + # Build file path
  154 + file_name = self.path + "-" + commit.id.to_s + ".tar.gz"
  155 + storage_path = Rails.root.join("tmp", "repositories", self.path_with_namespace)
  156 + file_path = File.join(storage_path, file_name)
  157 +
  158 + # Put files into a directory before archiving
  159 + prefix = self.path + "/"
  160 +
  161 + # Create file if not exists
  162 + unless File.exists?(file_path)
  163 + FileUtils.mkdir_p storage_path
  164 + file = self.repo.archive_to_file(ref, prefix, file_path)
  165 + end
  166 +
  167 + file_path
  168 + end
  169 +end
... ...
app/models/service.rb
... ... @@ -20,4 +20,8 @@ class Service &lt; ActiveRecord::Base
20 20 has_one :service_hook
21 21  
22 22 validates :project_id, presence: true
  23 +
  24 + def activated?
  25 + active
  26 + end
23 27 end
... ...
app/models/system_hook.rb
... ... @@ -19,6 +19,6 @@ class SystemHook &lt; WebHook
19 19 end
20 20  
21 21 def async_execute(data)
22   - Resque.enqueue(SystemHookWorker, id, data)
  22 + Sidekiq::Client.enqueue(SystemHookWorker, id, data)
23 23 end
24 24 end
... ...
app/models/team.rb 0 → 100644
... ... @@ -0,0 +1,118 @@
  1 +class Team
  2 + attr_accessor :project
  3 +
  4 + def initialize(project)
  5 + @project = project
  6 + end
  7 +
  8 + # Shortcut to add users
  9 + #
  10 + # Use:
  11 + # @team << [@user, :master]
  12 + # @team << [@users, :master]
  13 + #
  14 + def << args
  15 + users = args.first
  16 +
  17 + if users.respond_to?(:each)
  18 + add_users(users, args.second)
  19 + else
  20 + add_user(users, args.second)
  21 + end
  22 + end
  23 +
  24 + def add_user(user, access)
  25 + add_users_ids([user.id], access)
  26 + end
  27 +
  28 + def add_users(users, access)
  29 + add_users_ids(users.map(&:id), access)
  30 + end
  31 +
  32 + def add_users_ids(user_ids, access)
  33 + UsersProject.add_users_into_projects(
  34 + [project.id],
  35 + user_ids,
  36 + access
  37 + )
  38 + end
  39 +
  40 + # Remove all users from project team
  41 + def truncate
  42 + UsersProject.truncate_team(project)
  43 + end
  44 +
  45 + def members
  46 + project.users_projects
  47 + end
  48 +
  49 + def guests
  50 + members.guests.map(&:user)
  51 + end
  52 +
  53 + def reporters
  54 + members.reporters.map(&:user)
  55 + end
  56 +
  57 + def developers
  58 + members.developers.map(&:user)
  59 + end
  60 +
  61 + def masters
  62 + members.masters.map(&:user)
  63 + end
  64 +
  65 + def repository_readers
  66 + repository_members[UsersProject::REPORTER]
  67 + end
  68 +
  69 + def repository_writers
  70 + repository_members[UsersProject::DEVELOPER]
  71 + end
  72 +
  73 + def repository_masters
  74 + repository_members[UsersProject::MASTER]
  75 + end
  76 +
  77 + def repository_members
  78 + keys = Hash.new {|h,k| h[k] = [] }
  79 + UsersProject.select("keys.identifier, project_access").
  80 + joins(user: :keys).where(project_id: project.id).
  81 + each {|row| keys[row.project_access] << [row.identifier] }
  82 +
  83 + keys[UsersProject::REPORTER] += project.deploy_keys.pluck(:identifier)
  84 + keys
  85 + end
  86 +
  87 + def import(source_project)
  88 + target_project = project
  89 +
  90 + source_team = source_project.users_projects.all
  91 + target_team = target_project.users_projects.all
  92 + target_user_ids = target_team.map(&:user_id)
  93 +
  94 + source_team.reject! do |tm|
  95 + # Skip if user already present in team
  96 + target_user_ids.include?(tm.user_id)
  97 + end
  98 +
  99 + source_team.map! do |tm|
  100 + new_tm = tm.dup
  101 + new_tm.id = nil
  102 + new_tm.project_id = target_project.id
  103 + new_tm.skip_git = true
  104 + new_tm
  105 + end
  106 +
  107 + UsersProject.transaction do
  108 + source_team.each do |tm|
  109 + tm.save
  110 + end
  111 + target_project.update_repository
  112 + end
  113 +
  114 + true
  115 + rescue
  116 + false
  117 + end
  118 +end
... ...
app/models/tree.rb
1 1 class Tree
2 2 include Linguist::BlobHelper
3   - attr_accessor :path, :tree, :project, :ref
  3 +
  4 + attr_accessor :path, :tree, :ref
4 5  
5 6 delegate :contents, :basename, :name, :data, :mime_type,
6 7 :mode, :size, :text?, :colorize, to: :tree
7 8  
8   - def initialize(raw_tree, project, ref = nil, path = nil)
9   - @project, @ref, @path = project, ref, path
  9 + def initialize(raw_tree, ref = nil, path = nil)
  10 + @ref, @path = ref, path
10 11 @tree = if path.present?
11 12 raw_tree / path
12 13 else
... ...
app/models/user.rb
... ... @@ -34,8 +34,6 @@
34 34 #
35 35  
36 36 class User < ActiveRecord::Base
37   - include Account
38   -
39 37 devise :database_authenticatable, :token_authenticatable, :lockable,
40 38 :recoverable, :rememberable, :trackable, :validatable, :omniauthable
41 39  
... ... @@ -51,7 +49,6 @@ class User &lt; ActiveRecord::Base
51 49 has_many :groups, class_name: "Group", foreign_key: :owner_id
52 50  
53 51 has_many :keys, dependent: :destroy
54   - has_many :projects, through: :users_projects
55 52 has_many :users_projects, dependent: :destroy
56 53 has_many :issues, foreign_key: :author_id, dependent: :destroy
57 54 has_many :notes, foreign_key: :author_id, dependent: :destroy
... ... @@ -70,6 +67,8 @@ class User &lt; ActiveRecord::Base
70 67 message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
71 68  
72 69  
  70 + validate :namespace_uniq, if: ->(user) { user.username_changed? }
  71 +
73 72 before_validation :generate_password, on: :create
74 73 before_save :ensure_authentication_token
75 74 alias_attribute :private_token, :authentication_token
... ... @@ -77,11 +76,14 @@ class User &lt; ActiveRecord::Base
77 76 delegate :path, to: :namespace, allow_nil: true, prefix: true
78 77  
79 78 # Scopes
80   - scope :not_in_project, ->(project) { where("id not in (:ids)", ids: project.users.map(&:id) ) }
81 79 scope :admins, where(admin: true)
82 80 scope :blocked, where(blocked: true)
83 81 scope :active, where(blocked: false)
  82 + scope :alphabetically, order('name ASC')
84 83  
  84 + #
  85 + # Class methods
  86 + #
85 87 class << self
86 88 def filter filter_name
87 89 case filter_name
... ... @@ -93,6 +95,14 @@ class User &lt; ActiveRecord::Base
93 95 end
94 96 end
95 97  
  98 + def not_in_project(project)
  99 + if project.users.present?
  100 + where("id not in (:ids)", ids: project.users.map(&:id) )
  101 + else
  102 + scoped
  103 + end
  104 + end
  105 +
96 106 def without_projects
97 107 where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)')
98 108 end
... ... @@ -118,9 +128,158 @@ class User &lt; ActiveRecord::Base
118 128 end
119 129 end
120 130  
  131 + #
  132 + # Instance methods
  133 + #
121 134 def generate_password
122 135 if self.force_random_password
123 136 self.password = self.password_confirmation = Devise.friendly_token.first(8)
124 137 end
125 138 end
  139 +
  140 + def namespace_uniq
  141 + namespace_name = self.username
  142 + if Namespace.find_by_path(namespace_name)
  143 + self.errors.add :username, "already exist"
  144 + end
  145 + end
  146 +
  147 + # Namespaces user has access to
  148 + def namespaces
  149 + namespaces = []
  150 +
  151 + # Add user account namespace
  152 + namespaces << self.namespace if self.namespace
  153 +
  154 + # Add groups you can manage
  155 + namespaces += if admin
  156 + Group.all
  157 + else
  158 + groups.all
  159 + end
  160 + namespaces
  161 + end
  162 +
  163 + # Groups where user is an owner
  164 + def owned_groups
  165 + groups
  166 + end
  167 +
  168 + # Groups user has access to
  169 + def authorized_groups
  170 + @authorized_groups ||= begin
  171 + groups = Group.where(id: self.authorized_projects.pluck(:namespace_id)).all
  172 + groups = groups + self.groups
  173 + groups.uniq
  174 + end
  175 + end
  176 +
  177 +
  178 + # Projects user has access to
  179 + def authorized_projects
  180 + project_ids = users_projects.pluck(:project_id)
  181 + project_ids = project_ids | owned_projects.pluck(:id)
  182 + Project.where(id: project_ids)
  183 + end
  184 +
  185 + # Projects in user namespace
  186 + def personal_projects
  187 + Project.personal(self)
  188 + end
  189 +
  190 + # Projects where user is an owner
  191 + def owned_projects
  192 + Project.where("(projects.namespace_id IN (:namespaces)) OR
  193 + (projects.namespace_id IS NULL AND projects.creator_id = :user_id)",
  194 + namespaces: namespaces.map(&:id), user_id: self.id)
  195 + end
  196 +
  197 + # Team membership in personal projects
  198 + def tm_in_personal_projects
  199 + UsersProject.where(project_id: personal_projects.map(&:id), user_id: self.id)
  200 + end
  201 +
  202 + # Returns a string for use as a Gitolite user identifier
  203 + #
  204 + # Note that Gitolite 2.x requires the following pattern for users:
  205 + #
  206 + # ^@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$
  207 + def identifier
  208 + # Replace non-word chars with underscores, then make sure it starts with
  209 + # valid chars
  210 + email.gsub(/\W/, '_').gsub(/\A([\W\_])+/, '')
  211 + end
  212 +
  213 + def is_admin?
  214 + admin
  215 + end
  216 +
  217 + def require_ssh_key?
  218 + keys.count == 0
  219 + end
  220 +
  221 + def can_create_project?
  222 + projects_limit > personal_projects.count
  223 + end
  224 +
  225 + def can_create_group?
  226 + is_admin?
  227 + end
  228 +
  229 + def abilities
  230 + @abilities ||= begin
  231 + abilities = Six.new
  232 + abilities << Ability
  233 + abilities
  234 + end
  235 + end
  236 +
  237 + def can? action, subject
  238 + abilities.allowed?(self, action, subject)
  239 + end
  240 +
  241 + def first_name
  242 + name.split.first unless name.blank?
  243 + end
  244 +
  245 + def cared_merge_requests
  246 + MergeRequest.where("author_id = :id or assignee_id = :id", id: self.id)
  247 + end
  248 +
  249 + # Remove user from all projects and
  250 + # set blocked attribute to true
  251 + def block
  252 + users_projects.find_each do |membership|
  253 + return false unless membership.destroy
  254 + end
  255 +
  256 + self.blocked = true
  257 + save
  258 + end
  259 +
  260 + def projects_limit_percent
  261 + return 100 if projects_limit.zero?
  262 + (personal_projects.count.to_f / projects_limit) * 100
  263 + end
  264 +
  265 + def recent_push project_id = nil
  266 + # Get push events not earlier than 2 hours ago
  267 + events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours)
  268 + events = events.where(project_id: project_id) if project_id
  269 +
  270 + # Take only latest one
  271 + events = events.recent.limit(1).first
  272 + end
  273 +
  274 + def projects_sorted_by_activity
  275 + authorized_projects.sorted_by_activity
  276 + end
  277 +
  278 + def several_namespaces?
  279 + namespaces.size > 1
  280 + end
  281 +
  282 + def namespace_id
  283 + namespace.try :id
  284 + end
126 285 end
... ...
app/models/users_project.rb
... ... @@ -11,7 +11,7 @@
11 11 #
12 12  
13 13 class UsersProject < ActiveRecord::Base
14   - include GitHost
  14 + include Gitolited
15 15  
16 16 GUEST = 10
17 17 REPORTER = 20
... ... @@ -23,87 +23,96 @@ class UsersProject &lt; ActiveRecord::Base
23 23 belongs_to :user
24 24 belongs_to :project
25 25  
26   - after_save :update_repository
27   - after_destroy :update_repository
  26 + attr_accessor :skip_git
  27 +
  28 + after_save :update_repository, unless: :skip_git?
  29 + after_destroy :update_repository, unless: :skip_git?
28 30  
29 31 validates :user, presence: true
30   - validates :user_id, uniqueness: { :scope => [:project_id], message: "already exists in project" }
  32 + validates :user_id, uniqueness: { scope: [:project_id], message: "already exists in project" }
31 33 validates :project_access, inclusion: { in: [GUEST, REPORTER, DEVELOPER, MASTER] }, presence: true
32 34 validates :project, presence: true
33 35  
34 36 delegate :name, :email, to: :user, prefix: true
35 37  
  38 + scope :guests, where(project_access: GUEST)
  39 + scope :reporters, where(project_access: REPORTER)
  40 + scope :developers, where(project_access: DEVELOPER)
  41 + scope :masters, where(project_access: MASTER)
  42 + scope :in_project, ->(project) { where(project_id: project.id) }
  43 +
36 44 class << self
37   - def import_team(source_project, target_project)
38   - UsersProject.without_repository_callback do
39   - UsersProject.transaction do
40   - team = source_project.users_projects.all
41   -
42   - team.each do |tm|
43   - # Skip if user already present in team
44   - next if target_project.users.include?(tm.user)
45   -
46   - new_tm = tm.dup
47   - new_tm.id = nil
48   - new_tm.project_id = target_project.id
49   - new_tm.save
  45 +
  46 + # Add users to project teams with passed access option
  47 + #
  48 + # access can be an integer representing a access code
  49 + # or symbol like :master representing role
  50 + #
  51 + # Ex.
  52 + # add_users_into_projects(
  53 + # project_ids,
  54 + # user_ids,
  55 + # UsersProject::MASTER
  56 + # )
  57 + #
  58 + # add_users_into_projects(
  59 + # project_ids,
  60 + # user_ids,
  61 + # :master
  62 + # )
  63 + #
  64 + def add_users_into_projects(project_ids, user_ids, access)
  65 + project_access = if roles_hash.has_key?(access)
  66 + roles_hash[access]
  67 + elsif roles_hash.values.include?(access.to_i)
  68 + access
  69 + else
  70 + raise "Non valid access"
  71 + end
  72 +
  73 + UsersProject.transaction do
  74 + project_ids.each do |project_id|
  75 + user_ids.each do |user_id|
  76 + users_project = UsersProject.new(project_access: project_access, user_id: user_id)
  77 + users_project.project_id = project_id
  78 + users_project.skip_git = true
  79 + users_project.save
50 80 end
51 81 end
  82 + Gitlab::Gitolite.new.update_repositories(Project.where(id: project_ids))
52 83 end
53 84  
54   - target_project.update_repository
55 85 true
56 86 rescue
57 87 false
58 88 end
59 89  
60   - def without_repository_callback
61   - UsersProject.skip_callback(:destroy, :after, :update_repository)
62   - yield
63   - UsersProject.set_callback(:destroy, :after, :update_repository)
64   - end
65   -
66   - def bulk_delete(project, user_ids)
  90 + def truncate_teams(project_ids)
67 91 UsersProject.transaction do
68   - UsersProject.where(:user_id => user_ids, :project_id => project.id).each do |users_project|
  92 + users_projects = UsersProject.where(project_id: project_ids)
  93 + users_projects.each do |users_project|
  94 + users_project.skip_git = true
69 95 users_project.destroy
70 96 end
  97 + Gitlab::Gitolite.new.update_repositories(Project.where(id: project_ids))
71 98 end
72   - end
73 99  
74   - def bulk_update(project, user_ids, project_access)
75   - UsersProject.transaction do
76   - UsersProject.where(:user_id => user_ids, :project_id => project.id).each do |users_project|
77   - users_project.project_access = project_access
78   - users_project.save
79   - end
80   - end
  100 + true
  101 + rescue
  102 + false
81 103 end
82 104  
83   - def bulk_import(project, user_ids, project_access)
84   - UsersProject.transaction do
85   - user_ids.each do |user_id|
86   - users_project = UsersProject.new(
87   - project_access: project_access,
88   - user_id: user_id
89   - )
90   - users_project.project = project
91   - users_project.save
92   - end
93   - end
  105 + def truncate_team project
  106 + truncate_teams [project.id]
94 107 end
95 108  
96   - def user_bulk_import(user, project_ids, project_access)
97   - UsersProject.transaction do
98   - project_ids.each do |project_id|
99   - users_project = UsersProject.new(
100   - project_access: project_access,
101   - )
102   - users_project.project_id = project_id
103   - users_project.user_id = user.id
104   - users_project.save
105   - end
106   - end
  109 + def roles_hash
  110 + {
  111 + guest: GUEST,
  112 + reporter: REPORTER,
  113 + developer: DEVELOPER,
  114 + master: MASTER
  115 + }
107 116 end
108 117  
109 118 def access_roles
... ... @@ -116,12 +125,8 @@ class UsersProject &lt; ActiveRecord::Base
116 125 end
117 126 end
118 127  
119   - def role_access
120   - project_access
121   - end
122   -
123 128 def update_repository
124   - git_host.update_repository(project)
  129 + gitolite.update_repository(project)
125 130 end
126 131  
127 132 def project_access_human
... ... @@ -131,4 +136,8 @@ class UsersProject &lt; ActiveRecord::Base
131 136 def repo_access_human
132 137 self.class.access_roles.invert[self.project_access]
133 138 end
  139 +
  140 + def skip_git?
  141 + !!@skip_git
  142 + end
134 143 end
... ...
app/models/wiki.rb
... ... @@ -50,5 +50,4 @@ class Wiki &lt; ActiveRecord::Base
50 50 def set_slug
51 51 self.slug = self.title.parameterize
52 52 end
53   -
54 53 end
... ...
app/observers/issue_observer.rb
... ... @@ -3,7 +3,7 @@ class IssueObserver &lt; ActiveRecord::Observer
3 3  
4 4 def after_create(issue)
5 5 if issue.assignee && issue.assignee != current_user
6   - Notify.new_issue_email(issue.id).deliver
  6 + Notify.delay.new_issue_email(issue.id)
7 7 end
8 8 end
9 9  
... ... @@ -16,7 +16,7 @@ class IssueObserver &lt; ActiveRecord::Observer
16 16 if status
17 17 Note.create_status_change_note(issue, current_user, status)
18 18 [issue.author, issue.assignee].compact.each do |recipient|
19   - Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user.id).deliver
  19 + Notify.delay.issue_status_changed_email(recipient.id, issue.id, status, current_user.id)
20 20 end
21 21 end
22 22 end
... ... @@ -27,7 +27,7 @@ class IssueObserver &lt; ActiveRecord::Observer
27 27 recipient_ids = [issue.assignee_id, issue.assignee_id_was].keep_if {|id| id && id != current_user.id }
28 28  
29 29 recipient_ids.each do |recipient_id|
30   - Notify.reassigned_issue_email(recipient_id, issue.id, issue.assignee_id_was).deliver
  30 + Notify.delay.reassigned_issue_email(recipient_id, issue.id, issue.assignee_id_was)
31 31 end
32 32 end
33 33 end
... ...
app/observers/key_observer.rb
1 1 class KeyObserver < ActiveRecord::Observer
2   - include GitHost
  2 + include Gitolited
3 3  
4 4 def after_save(key)
5   - git_host.set_key(key.identifier, key.key, key.projects)
  5 + gitolite.set_key(key.identifier, key.key, key.projects)
6 6 end
7 7  
8 8 def after_destroy(key)
9 9 return if key.is_deploy_key && !key.last_deploy?
10   - git_host.remove_key(key.identifier, key.projects)
  10 + gitolite.remove_key(key.identifier, key.projects)
11 11 end
12 12 end
... ...
app/observers/merge_request_observer.rb
... ... @@ -3,7 +3,7 @@ class MergeRequestObserver &lt; ActiveRecord::Observer
3 3  
4 4 def after_create(merge_request)
5 5 if merge_request.assignee && merge_request.assignee != current_user
6   - Notify.new_merge_request_email(merge_request.id).deliver
  6 + Notify.delay.new_merge_request_email(merge_request.id)
7 7 end
8 8 end
9 9  
... ... @@ -25,7 +25,7 @@ class MergeRequestObserver &lt; ActiveRecord::Observer
25 25 recipients_ids.delete current_user.id
26 26  
27 27 recipients_ids.each do |recipient_id|
28   - Notify.reassigned_merge_request_email(recipient_id, merge_request.id, merge_request.assignee_id_was).deliver
  28 + Notify.delay.reassigned_merge_request_email(recipient_id, merge_request.id, merge_request.assignee_id_was)
29 29 end
30 30 end
31 31 end
... ...
app/observers/note_observer.rb
... ... @@ -11,7 +11,7 @@ class NoteObserver &lt; ActiveRecord::Observer
11 11 notify_team(note)
12 12 elsif note.notify_author
13 13 # Notify only author of resource
14   - Notify.note_commit_email(note.noteable.author_email, note.id).deliver
  14 + Notify.delay.note_commit_email(note.noteable.author_email, note.id)
15 15 else
16 16 # Otherwise ignore it
17 17 nil
... ... @@ -26,7 +26,7 @@ class NoteObserver &lt; ActiveRecord::Observer
26 26  
27 27 if Notify.respond_to? notify_method
28 28 team_without_note_author(note).map do |u|
29   - Notify.send(notify_method, u.id, note.id).deliver
  29 + Notify.delay.send(notify_method, u.id, note.id)
30 30 end
31 31 end
32 32 end
... ...
app/observers/user_observer.rb
... ... @@ -2,7 +2,7 @@ class UserObserver &lt; ActiveRecord::Observer
2 2 def after_create(user)
3 3 log_info("User \"#{user.name}\" (#{user.email}) was created")
4 4  
5   - Notify.new_user_email(user.id, user.password).deliver
  5 + Notify.delay.new_user_email(user.id, user.password)
6 6 end
7 7  
8 8 def after_destroy user
... ... @@ -14,7 +14,7 @@ class UserObserver &lt; ActiveRecord::Observer
14 14 if user.namespace
15 15 user.namespace.update_attributes(path: user.username)
16 16 else
17   - user.create_namespace!(path: user.username, name: user.name)
  17 + user.create_namespace!(path: user.username, name: user.username)
18 18 end
19 19 end
20 20 end
... ...
app/observers/users_project_observer.rb
1 1 class UsersProjectObserver < ActiveRecord::Observer
2 2 def after_commit(users_project)
3 3 return if users_project.destroyed?
4   - Notify.project_access_granted_email(users_project.id).deliver
  4 + Notify.delay.project_access_granted_email(users_project.id)
5 5 end
6 6  
7 7 def after_create(users_project)
... ...
app/roles/account.rb
... ... @@ -1,124 +0,0 @@
1   -module Account
2   - # Returns a string for use as a Gitolite user identifier
3   - #
4   - # Note that Gitolite 2.x requires the following pattern for users:
5   - #
6   - # ^@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$
7   - def identifier
8   - # Replace non-word chars with underscores, then make sure it starts with
9   - # valid chars
10   - email.gsub(/\W/, '_').gsub(/\A([\W\_])+/, '')
11   - end
12   -
13   - def is_admin?
14   - admin
15   - end
16   -
17   - def require_ssh_key?
18   - keys.count == 0
19   - end
20   -
21   - def can_create_project?
22   - projects_limit > my_own_projects.count
23   - end
24   -
25   - def can_create_group?
26   - is_admin?
27   - end
28   -
29   - def abilities
30   - @abilities ||= begin
31   - abilities = Six.new
32   - abilities << Ability
33   - abilities
34   - end
35   - end
36   -
37   - def can? action, subject
38   - abilities.allowed?(self, action, subject)
39   - end
40   -
41   - def last_activity_project
42   - projects.first
43   - end
44   -
45   - def first_name
46   - name.split.first unless name.blank?
47   - end
48   -
49   - def cared_merge_requests
50   - MergeRequest.where("author_id = :id or assignee_id = :id", id: self.id)
51   - end
52   -
53   - def project_ids
54   - projects.map(&:id)
55   - end
56   -
57   - # Remove user from all projects and
58   - # set blocked attribute to true
59   - def block
60   - users_projects.find_each do |membership|
61   - return false unless membership.destroy
62   - end
63   -
64   - self.blocked = true
65   - save
66   - end
67   -
68   - def projects_limit_percent
69   - return 100 if projects_limit.zero?
70   - (my_own_projects.count.to_f / projects_limit) * 100
71   - end
72   -
73   - def recent_push project_id = nil
74   - # Get push events not earlier than 2 hours ago
75   - events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours)
76   - events = events.where(project_id: project_id) if project_id
77   -
78   - # Take only latest one
79   - events = events.recent.limit(1).first
80   - end
81   -
82   - def projects_sorted_by_activity
83   - projects.sorted_by_activity
84   - end
85   -
86   - def namespaces
87   - namespaces = []
88   -
89   - # Add user account namespace
90   - namespaces << self.namespace if self.namespace
91   -
92   - # Add groups you can manage
93   - namespaces += if admin
94   - Group.all
95   - else
96   - groups.all
97   - end
98   - namespaces
99   - end
100   -
101   - def several_namespaces?
102   - namespaces.size > 1
103   - end
104   -
105   - def namespace_id
106   - namespace.try :id
107   - end
108   -
109   - def authorized_groups
110   - @authorized_groups ||= begin
111   - groups = Group.where(id: self.projects.pluck(:namespace_id)).all
112   - groups = groups + self.groups
113   - groups.uniq
114   - end
115   - end
116   -
117   - def authorized_projects
118   - Project.authorized_for(self)
119   - end
120   -
121   - def my_own_projects
122   - Project.personal(self)
123   - end
124   -end
app/roles/authority.rb
... ... @@ -1,58 +0,0 @@
1   -module Authority
2   - # Compatible with all access rights
3   - # Should be rewrited for new access rights
4   - def add_access(user, *access)
5   - access = if access.include?(:admin)
6   - { project_access: UsersProject::MASTER }
7   - elsif access.include?(:write)
8   - { project_access: UsersProject::DEVELOPER }
9   - else
10   - { project_access: UsersProject::REPORTER }
11   - end
12   - opts = { user: user }
13   - opts.merge!(access)
14   - users_projects.create(opts)
15   - end
16   -
17   - def reset_access(user)
18   - users_projects.where(project_id: self.id, user_id: user.id).destroy if self.id
19   - end
20   -
21   - def repository_readers
22   - keys = Key.joins({user: :users_projects}).
23   - where("users_projects.project_id = ? AND users_projects.project_access = ?", id, UsersProject::REPORTER)
24   - keys.map(&:identifier) + deploy_keys.map(&:identifier)
25   - end
26   -
27   - def repository_writers
28   - keys = Key.joins({user: :users_projects}).
29   - where("users_projects.project_id = ? AND users_projects.project_access = ?", id, UsersProject::DEVELOPER)
30   - keys.map(&:identifier)
31   - end
32   -
33   - def repository_masters
34   - keys = Key.joins({user: :users_projects}).
35   - where("users_projects.project_id = ? AND users_projects.project_access = ?", id, UsersProject::MASTER)
36   - keys.map(&:identifier)
37   - end
38   -
39   - def allow_read_for?(user)
40   - !users_projects.where(user_id: user.id).empty?
41   - end
42   -
43   - def guest_access_for?(user)
44   - !users_projects.where(user_id: user.id).empty?
45   - end
46   -
47   - def report_access_for?(user)
48   - !users_projects.where(user_id: user.id, project_access: [UsersProject::REPORTER, UsersProject::DEVELOPER, UsersProject::MASTER]).empty?
49   - end
50   -
51   - def dev_access_for?(user)
52   - !users_projects.where(user_id: user.id, project_access: [UsersProject::DEVELOPER, UsersProject::MASTER]).empty?
53   - end
54   -
55   - def master_access_for?(user)
56   - !users_projects.where(user_id: user.id, project_access: [UsersProject::MASTER]).empty?
57   - end
58   -end