Commit 30227869482bbbdbfea153f2b45ef3bb9a9fd218
Exists in
master
and in
4 other branches
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
.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
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
README.md
| ... | ... | @@ -2,6 +2,7 @@ |
| 2 | 2 | |
| 3 | 3 | GitLab is a free project and repository management application |
| 4 | 4 | |
| 5 | + | |
| 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
| ... | ... | @@ -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. | ... | ... |
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
| ... | ... | @@ -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 | -} |
| ... | ... | @@ -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
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
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
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
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
app/assets/stylesheets/themes/ui_gray.scss
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
app/contexts/commit_load_context.rb
| ... | ... | @@ -9,16 +9,16 @@ class CommitLoadContext < 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] | ... | ... |
| ... | ... | @@ -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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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
app/controllers/commits_controller.rb
| ... | ... | @@ -9,10 +9,10 @@ class CommitsController < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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
app/controllers/projects_controller.rb
| ... | ... | @@ -2,6 +2,7 @@ require Rails.root.join('lib', 'gitlab', 'graph', 'json_builder') |
| 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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
app/controllers/team_members_controller.rb
| ... | ... | @@ -16,10 +16,9 @@ class TeamMembersController < 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 < 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
app/decorators/tree_decorator.rb
| ... | ... | @@ -6,16 +6,14 @@ class TreeDecorator < 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 < 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
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 | ... | ... |
| ... | ... | @@ -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
app/helpers/namespaces_helper.rb
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 < 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 < 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. | ... | ... |
| ... | ... | @@ -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 < 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 < 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 < 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 < 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
app/models/key.rb
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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 | ... | ... |
| ... | ... | @@ -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
app/models/system_hook.rb
| ... | ... | @@ -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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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
app/observers/issue_observer.rb
| ... | ... | @@ -3,7 +3,7 @@ class IssueObserver < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 |