diff --git a/.gitignore b/.gitignore index 760487ca..d22760e7 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,4 @@ config/unicorn.rb db/data.yml .idea .DS_Store - +.chef diff --git a/.travis.yml b/.travis.yml index da67e37d..bb4c4a79 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,8 @@ branches: - 'master' rvm: - 1.9.3 +services: + - mysql before_script: - "cp config/database.yml.$DB config/database.yml" - "cp config/gitlab.yml.example config/gitlab.yml" diff --git a/CHANGELOG b/CHANGELOG index 6868c07b..99641ad3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,19 @@ +v 2.9.0 + - fixed inline notes bugs + - refactored rspecs + - refactored gitolite backend + - added factory_girl + - restyled projects list on dashboard + - ssh keys validation to prevent gitolite crash + - send notifications if changed premission in project + - scss refactoring. gitlab_bootstrap/ dir + - fix git push http body bigger than 112k problem + - list of labels page under issues tab + - API for milestones, keys + - restyled buttons + - OAuth + - Comment order changed + v 2.8.1 - ability to disable gravatars - improved MR diff logic diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..9041530d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,30 @@ +## Contribute to GitLab + +If you want to contribute to GitLab, follow this process: + +1. Fork the project +2. Create a feature branch +3. Code +4. Create a pull request + +We only accept pull requests if: + +* Your code has proper tests and all tests pass +* Your code can be merged w/o problems +* It wont broke existing functionality +* Its a quality code +* We like it :) + +## [You may need a developer VM](https://github.com/gitlabhq/developer-vm) + +## Running tests + +To run the specs for GitLab, you need to run seeds for test db. + + cd gitlabhq + rake db:seed_fu RAILS_ENV=test + +Then you can run the test suite with rake: + + rake gitlab:test + diff --git a/Gemfile b/Gemfile index 045baa36..8e569c5b 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,13 @@ source "http://rubygems.org" +def darwin_only(require_as) + RUBY_PLATFORM.include?('darwin') && require_as +end + +def linux_only(require_as) + RUBY_PLATFORM.include?('linux') && require_as +end + gem "rails", "3.2.8" # Supported DBs @@ -8,6 +16,10 @@ gem "mysql2" # Auth gem "devise", "~> 2.1.0" +gem 'omniauth' +gem 'omniauth-google-oauth2' +gem 'omniauth-twitter' +gem 'omniauth-github' # GITLAB patched libs gem "grit", :git => "https://github.com/gitlabhq/grit.git", :ref => "7f35cb98ff17d534a07e3ce6ec3d580f67402837" @@ -44,7 +56,8 @@ gem "ffaker" gem "seed-fu" # Markdown to HTML -gem "redcarpet", "~> 2.1.1" +gem "redcarpet", "~> 2.1.1" +gem "github-markup", "~> 0.7.4" # Servers gem "thin" @@ -97,20 +110,28 @@ group :development do end group :development, :test do + gem 'spinach-rails' gem "rspec-rails" gem "capybara" gem "capybara-webkit" gem "headless" - gem "autotest" - gem "autotest-rails" gem "pry" gem "awesome_print" gem "database_cleaner" gem "launchy" + gem 'factory_girl_rails' + + # Guard + gem 'guard-rspec' + gem 'guard-spinach' + + # Notification + gem 'rb-fsevent', :require => darwin_only('rb-fsevent') + gem 'growl', :require => darwin_only('growl') + gem 'rb-inotify', :require => linux_only('rb-inotify') end group :test do - gem 'cucumber-rails', :require => false gem "simplecov", :require => false gem "shoulda-matchers" gem 'email_spec' @@ -119,5 +140,5 @@ group :test do end group :production do - gem "gitlab_meta", '2.8' + gem "gitlab_meta", '2.9' end diff --git a/Gemfile.lock b/Gemfile.lock index 656bede4..3d27d3fb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -68,7 +68,6 @@ GIT GEM remote: http://rubygems.org/ specs: - ZenTest (4.8.1) actionmailer (3.2.8) actionpack (= 3.2.8) mail (~> 2.4.4) @@ -100,15 +99,11 @@ GEM rails (~> 3.0) addressable (2.2.8) arel (3.0.2) - autotest (4.4.6) - ZenTest (>= 4.4.1) - autotest-rails (4.1.2) - ZenTest (~> 4.5) awesome_print (1.0.2) bcrypt-ruby (3.0.1) blankslate (2.1.2.4) bootstrap-sass (2.0.4.0) - builder (3.0.0) + builder (3.0.2) capybara (1.1.2) mime-types (>= 1.16) nokogiri (>= 1.3.3) @@ -125,7 +120,7 @@ GEM charlock_holmes (0.6.8) childprocess (0.3.2) ffi (~> 1.0.6) - chosen-rails (0.9.8) + chosen-rails (0.9.8.3) railties (~> 3.0) thor (~> 0.14) coderay (1.0.6) @@ -137,16 +132,8 @@ GEM execjs coffee-script-source (1.3.3) colored (1.2) + colorize (0.5.8) crack (0.3.1) - cucumber (1.2.1) - builder (>= 2.1.2) - diff-lcs (>= 1.1.3) - gherkin (~> 2.11.0) - json (>= 1.4.6) - cucumber-rails (1.3.0) - capybara (>= 1.1.2) - cucumber (>= 1.1.8) - nokogiri (>= 1.5.0) daemons (1.1.8) database_cleaner (0.8.0) devise (2.1.2) @@ -166,20 +153,36 @@ GEM eventmachine (0.12.10) execjs (1.4.0) multi_json (~> 1.0) + factory_girl (4.0.0) + activesupport (>= 3.0.0) + factory_girl_rails (4.0.0) + factory_girl (~> 4.0.0) + railties (>= 3.0.0) + faraday (0.8.4) + multipart-post (~> 1.1) ffaker (1.14.0) ffi (1.0.11) foreman (0.47.0) thor (>= 0.13.6) - gherkin (2.11.0) - json (>= 1.4.6) + gherkin-ruby (0.2.1) git (1.2.5) - gitlab_meta (2.8) + github-markup (0.7.4) + gitlab_meta (2.9) grape (0.2.1) hashie (~> 1.2) multi_json multi_xml rack rack-mount + growl (1.0.3) + guard (1.3.2) + listen (>= 0.4.2) + thor (>= 0.14.6) + guard-rspec (1.2.1) + guard (>= 1.1) + guard-spinach (0.0.2) + guard (>= 1.1) + spinach haml (3.1.6) haml-rails (0.3.4) actionpack (~> 3.0) @@ -193,7 +196,8 @@ GEM httparty (0.8.3) multi_json (~> 1.0) multi_xml - i18n (0.6.0) + httpauth (0.1) + i18n (0.6.1) journey (1.0.4) jquery-rails (2.0.2) railties (>= 3.2.0, < 5.0) @@ -201,11 +205,12 @@ GEM jquery-ui-rails (0.5.0) jquery-rails railties (>= 3.1.0) - json (1.7.4) - kaminari (0.13.0) + json (1.7.5) + jwt (0.1.5) + multi_json (>= 1.0) + kaminari (0.14.0) actionpack (>= 3.0.0) activesupport (>= 3.0.0) - railties (>= 3.0.0) kgio (2.7.4) launchy (2.1.0) addressable (~> 2.2.6) @@ -214,6 +219,7 @@ GEM libv8 (3.3.10.4) libwebsocket (0.1.3) addressable + listen (0.5.0) mail (2.4.4) i18n (>= 0.4.0) mime-types (~> 1.16) @@ -224,12 +230,35 @@ GEM sprockets (~> 2.0) multi_json (1.3.6) multi_xml (0.5.1) + multipart-post (1.1.5) mysql2 (0.3.11) net-ldap (0.2.2) nokogiri (1.5.3) + oauth (0.4.7) + oauth2 (0.8.0) + faraday (~> 0.8) + httpauth (~> 0.1) + jwt (~> 0.1.4) + multi_json (~> 1.0) + rack (~> 1.2) omniauth (1.1.0) hashie (~> 1.2) rack + omniauth-github (1.0.3) + omniauth (~> 1.0) + omniauth-oauth2 (~> 1.1) + omniauth-google-oauth2 (0.1.13) + omniauth (~> 1.0) + omniauth-oauth2 + omniauth-oauth (1.0.1) + oauth + omniauth (~> 1.0) + omniauth-oauth2 (1.1.0) + oauth2 (~> 0.8.0) + omniauth (~> 1.0) + omniauth-twitter (0.0.13) + multi_json (~> 1.3) + omniauth-oauth (~> 1.0) orm_adapter (0.3.0) polyglot (0.3.3) posix-spawn (0.3.6) @@ -269,6 +298,9 @@ GEM raindrops (0.9.0) rake (0.9.2.2) raphael-rails (1.5.2) + rb-fsevent (0.9.1) + rb-inotify (0.8.8) + ffi (>= 0.5.0) rdoc (3.12) json (~> 1.4) redcarpet (2.1.1) @@ -319,7 +351,7 @@ GEM multi_json (~> 1.0) rubyzip settingslogic (2.0.8) - shoulda-matchers (1.1.0) + shoulda-matchers (1.3.0) activesupport (>= 3.0.0) simplecov (0.6.4) multi_json (~> 1.0) @@ -331,6 +363,13 @@ GEM tilt (~> 1.3, >= 1.3.3) six (0.2.0) slop (2.4.4) + spinach (0.5.2) + colorize + gherkin-ruby (~> 0.2.0) + spinach-rails (0.1.8) + capybara (~> 1) + railties (>= 3) + spinach (>= 0.4) sprockets (2.1.3) hike (~> 1.2) rack (~> 1.0) @@ -343,7 +382,7 @@ GEM daemons (>= 1.0.9) eventmachine (>= 0.12.6) rack (>= 1.0.0) - thor (0.15.4) + thor (0.16.0) tilt (1.3.3) treetop (1.4.10) polyglot @@ -372,8 +411,6 @@ PLATFORMS DEPENDENCIES acts-as-taggable-on (= 2.3.1) annotate! - autotest - autotest-rails awesome_print bootstrap-sass (= 2.0.4) capybara @@ -383,19 +420,23 @@ DEPENDENCIES chosen-rails coffee-rails (= 3.2.2) colored - cucumber-rails database_cleaner devise (~> 2.1.0) draper email_spec + factory_girl_rails ffaker foreman git - gitlab_meta (= 2.8) + github-markup (~> 0.7.4) + gitlab_meta (= 2.9) gitolite! grack! grape (~> 0.2.1) grit! + growl + guard-rspec + guard-spinach haml-rails headless httparty @@ -407,12 +448,18 @@ DEPENDENCIES linguist (~> 1.0.0)! modernizr (= 2.5.3) mysql2 + omniauth + omniauth-github + omniauth-google-oauth2 omniauth-ldap! + omniauth-twitter pry pygments.rb! rack-mini-profiler rails (= 3.2.8) raphael-rails (= 1.5.2) + rb-fsevent + rb-inotify redcarpet (~> 2.1.1) resque (~> 1.20.0) resque_mailer @@ -424,6 +471,7 @@ DEPENDENCIES shoulda-matchers simplecov six + spinach-rails sqlite3 stamp therubyracer diff --git a/Guardfile b/Guardfile new file mode 100644 index 00000000..e682f0b6 --- /dev/null +++ b/Guardfile @@ -0,0 +1,27 @@ +# A sample Guardfile +# More info at https://github.com/guard/guard#readme + +guard 'rspec', :version => 2, :all_on_start => false, :all_after_pass => false do + watch(%r{^spec/.+_spec\.rb$}) + watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } + watch(%r{^lib/api/(.+)\.rb$}) { |m| "spec/requests/api/#{m[1]}_spec.rb" } + watch('spec/spec_helper.rb') { "spec" } + + # Rails example + watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } + watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } + watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] } + watch(%r{^spec/support/(.+)\.rb$}) { "spec" } + watch('config/routes.rb') { "spec/routing" } + watch('app/controllers/application_controller.rb') { "spec/controllers" } + + # Capybara request specs + watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" } +end + +guard 'spinach' do + watch(%r|^features/(.*)\.feature|) + watch(%r|^features/steps/(.*)([^/]+)\.rb|) do |m| + "features/#{m[1]}#{m[2]}.feature" + end +end diff --git a/README.md b/README.md index 26ed209e..122cd984 100644 --- a/README.md +++ b/README.md @@ -39,5 +39,6 @@ Email ## Contribute +[Development Tips](https://github.com/gitlabhq/gitlabhq/blob/master/doc/development.md) Want to help - send a pull request. We'll accept good pull requests. diff --git a/VERSION b/VERSION index 1817afea..c8e38b61 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.8.2 +2.9.0 diff --git a/app/assets/images/emoji/+1.png b/app/assets/images/emoji/+1.png new file mode 100755 index 00000000..3a43ecae Binary files /dev/null and b/app/assets/images/emoji/+1.png differ diff --git a/app/assets/images/emoji/-1.png b/app/assets/images/emoji/-1.png new file mode 100755 index 00000000..41c6b825 Binary files /dev/null and b/app/assets/images/emoji/-1.png differ diff --git a/app/assets/images/emoji/100.png b/app/assets/images/emoji/100.png new file mode 100755 index 00000000..bce9ab14 Binary files /dev/null and b/app/assets/images/emoji/100.png differ diff --git a/app/assets/images/emoji/109.png b/app/assets/images/emoji/109.png new file mode 100755 index 00000000..74b9d5d3 Binary files /dev/null and b/app/assets/images/emoji/109.png differ diff --git a/app/assets/images/emoji/1234.png b/app/assets/images/emoji/1234.png new file mode 100755 index 00000000..c47c2e1f Binary files /dev/null and b/app/assets/images/emoji/1234.png differ diff --git a/app/assets/images/emoji/8ball.png b/app/assets/images/emoji/8ball.png new file mode 100755 index 00000000..c2c710d4 Binary files /dev/null and b/app/assets/images/emoji/8ball.png differ diff --git a/app/assets/images/emoji/a.png b/app/assets/images/emoji/a.png new file mode 100755 index 00000000..09ff6d6f Binary files /dev/null and b/app/assets/images/emoji/a.png differ diff --git a/app/assets/images/emoji/ab.png b/app/assets/images/emoji/ab.png new file mode 100755 index 00000000..2a522204 Binary files /dev/null and b/app/assets/images/emoji/ab.png differ diff --git a/app/assets/images/emoji/abc.png b/app/assets/images/emoji/abc.png new file mode 100755 index 00000000..505d40a1 Binary files /dev/null and b/app/assets/images/emoji/abc.png differ diff --git a/app/assets/images/emoji/abcd.png b/app/assets/images/emoji/abcd.png new file mode 100755 index 00000000..5218470b Binary files /dev/null and b/app/assets/images/emoji/abcd.png differ diff --git a/app/assets/images/emoji/accept.png b/app/assets/images/emoji/accept.png new file mode 100755 index 00000000..2d200903 Binary files /dev/null and b/app/assets/images/emoji/accept.png differ diff --git a/app/assets/images/emoji/aerial_tramway.png b/app/assets/images/emoji/aerial_tramway.png new file mode 100755 index 00000000..38f6dfe2 Binary files /dev/null and b/app/assets/images/emoji/aerial_tramway.png differ diff --git a/app/assets/images/emoji/airplane.png b/app/assets/images/emoji/airplane.png new file mode 100755 index 00000000..8407cb67 Binary files /dev/null and b/app/assets/images/emoji/airplane.png differ diff --git a/app/assets/images/emoji/alarm_clock.png b/app/assets/images/emoji/alarm_clock.png new file mode 100755 index 00000000..86ca8c8e Binary files /dev/null and b/app/assets/images/emoji/alarm_clock.png differ diff --git a/app/assets/images/emoji/alien.png b/app/assets/images/emoji/alien.png new file mode 100755 index 00000000..416de47b Binary files /dev/null and b/app/assets/images/emoji/alien.png differ diff --git a/app/assets/images/emoji/ambulance.png b/app/assets/images/emoji/ambulance.png new file mode 100755 index 00000000..b740f45d Binary files /dev/null and b/app/assets/images/emoji/ambulance.png differ diff --git a/app/assets/images/emoji/anchor.png b/app/assets/images/emoji/anchor.png new file mode 100755 index 00000000..0c5192e6 Binary files /dev/null and b/app/assets/images/emoji/anchor.png differ diff --git a/app/assets/images/emoji/angel.png b/app/assets/images/emoji/angel.png new file mode 100755 index 00000000..da52c310 Binary files /dev/null and b/app/assets/images/emoji/angel.png differ diff --git a/app/assets/images/emoji/anger.png b/app/assets/images/emoji/anger.png new file mode 100755 index 00000000..6fb4dca1 Binary files /dev/null and b/app/assets/images/emoji/anger.png differ diff --git a/app/assets/images/emoji/angry.png b/app/assets/images/emoji/angry.png new file mode 100755 index 00000000..f95bfa89 Binary files /dev/null and b/app/assets/images/emoji/angry.png differ diff --git a/app/assets/images/emoji/ant.png b/app/assets/images/emoji/ant.png new file mode 100755 index 00000000..b92d1cc1 Binary files /dev/null and b/app/assets/images/emoji/ant.png differ diff --git a/app/assets/images/emoji/apple.png b/app/assets/images/emoji/apple.png new file mode 100755 index 00000000..08aa17b9 Binary files /dev/null and b/app/assets/images/emoji/apple.png differ diff --git a/app/assets/images/emoji/aquarius.png b/app/assets/images/emoji/aquarius.png new file mode 100755 index 00000000..cbff66ed Binary files /dev/null and b/app/assets/images/emoji/aquarius.png differ diff --git a/app/assets/images/emoji/aries.png b/app/assets/images/emoji/aries.png new file mode 100755 index 00000000..89990223 Binary files /dev/null and b/app/assets/images/emoji/aries.png differ diff --git a/app/assets/images/emoji/arrow_backward.png b/app/assets/images/emoji/arrow_backward.png new file mode 100755 index 00000000..08862183 Binary files /dev/null and b/app/assets/images/emoji/arrow_backward.png differ diff --git a/app/assets/images/emoji/arrow_double_down.png b/app/assets/images/emoji/arrow_double_down.png new file mode 100755 index 00000000..2ecbebcd Binary files /dev/null and b/app/assets/images/emoji/arrow_double_down.png differ diff --git a/app/assets/images/emoji/arrow_double_up.png b/app/assets/images/emoji/arrow_double_up.png new file mode 100755 index 00000000..d42979d4 Binary files /dev/null and b/app/assets/images/emoji/arrow_double_up.png differ diff --git a/app/assets/images/emoji/arrow_down.png b/app/assets/images/emoji/arrow_down.png new file mode 100755 index 00000000..e6702f02 Binary files /dev/null and b/app/assets/images/emoji/arrow_down.png differ diff --git a/app/assets/images/emoji/arrow_down_small.png b/app/assets/images/emoji/arrow_down_small.png new file mode 100755 index 00000000..f7f2d510 Binary files /dev/null and b/app/assets/images/emoji/arrow_down_small.png differ diff --git a/app/assets/images/emoji/arrow_forward.png b/app/assets/images/emoji/arrow_forward.png new file mode 100755 index 00000000..fbfe711b Binary files /dev/null and b/app/assets/images/emoji/arrow_forward.png differ diff --git a/app/assets/images/emoji/arrow_heading_down.png b/app/assets/images/emoji/arrow_heading_down.png new file mode 100755 index 00000000..56dd3b9d Binary files /dev/null and b/app/assets/images/emoji/arrow_heading_down.png differ diff --git a/app/assets/images/emoji/arrow_heading_up.png b/app/assets/images/emoji/arrow_heading_up.png new file mode 100755 index 00000000..c8f670a1 Binary files /dev/null and b/app/assets/images/emoji/arrow_heading_up.png differ diff --git a/app/assets/images/emoji/arrow_left.png b/app/assets/images/emoji/arrow_left.png new file mode 100755 index 00000000..d64ac619 Binary files /dev/null and b/app/assets/images/emoji/arrow_left.png differ diff --git a/app/assets/images/emoji/arrow_lower_left.png b/app/assets/images/emoji/arrow_lower_left.png new file mode 100755 index 00000000..55fb03c4 Binary files /dev/null and b/app/assets/images/emoji/arrow_lower_left.png differ diff --git a/app/assets/images/emoji/arrow_lower_right.png b/app/assets/images/emoji/arrow_lower_right.png new file mode 100755 index 00000000..da8fb829 Binary files /dev/null and b/app/assets/images/emoji/arrow_lower_right.png differ diff --git a/app/assets/images/emoji/arrow_right.png b/app/assets/images/emoji/arrow_right.png new file mode 100755 index 00000000..6d483b51 Binary files /dev/null and b/app/assets/images/emoji/arrow_right.png differ diff --git a/app/assets/images/emoji/arrow_right_hook.png b/app/assets/images/emoji/arrow_right_hook.png new file mode 100755 index 00000000..8b4ea6e1 Binary files /dev/null and b/app/assets/images/emoji/arrow_right_hook.png differ diff --git a/app/assets/images/emoji/arrow_up.png b/app/assets/images/emoji/arrow_up.png new file mode 100755 index 00000000..b5b0688d Binary files /dev/null and b/app/assets/images/emoji/arrow_up.png differ diff --git a/app/assets/images/emoji/arrow_up_down.png b/app/assets/images/emoji/arrow_up_down.png new file mode 100755 index 00000000..b718c214 Binary files /dev/null and b/app/assets/images/emoji/arrow_up_down.png differ diff --git a/app/assets/images/emoji/arrow_up_small.png b/app/assets/images/emoji/arrow_up_small.png new file mode 100755 index 00000000..12173319 Binary files /dev/null and b/app/assets/images/emoji/arrow_up_small.png differ diff --git a/app/assets/images/emoji/arrow_upper_left.png b/app/assets/images/emoji/arrow_upper_left.png new file mode 100755 index 00000000..e895fd7b Binary files /dev/null and b/app/assets/images/emoji/arrow_upper_left.png differ diff --git a/app/assets/images/emoji/arrow_upper_right.png b/app/assets/images/emoji/arrow_upper_right.png new file mode 100755 index 00000000..e23790ba Binary files /dev/null and b/app/assets/images/emoji/arrow_upper_right.png differ diff --git a/app/assets/images/emoji/arrows_clockwise.png b/app/assets/images/emoji/arrows_clockwise.png new file mode 100755 index 00000000..5f84d7e7 Binary files /dev/null and b/app/assets/images/emoji/arrows_clockwise.png differ diff --git a/app/assets/images/emoji/arrows_counterclockwise.png b/app/assets/images/emoji/arrows_counterclockwise.png new file mode 100755 index 00000000..1933ae18 Binary files /dev/null and b/app/assets/images/emoji/arrows_counterclockwise.png differ diff --git a/app/assets/images/emoji/art.png b/app/assets/images/emoji/art.png new file mode 100755 index 00000000..d45212b0 Binary files /dev/null and b/app/assets/images/emoji/art.png differ diff --git a/app/assets/images/emoji/articulated_lorry.png b/app/assets/images/emoji/articulated_lorry.png new file mode 100755 index 00000000..81ec1f91 Binary files /dev/null and b/app/assets/images/emoji/articulated_lorry.png differ diff --git a/app/assets/images/emoji/astonished.png b/app/assets/images/emoji/astonished.png new file mode 100755 index 00000000..858a8348 Binary files /dev/null and b/app/assets/images/emoji/astonished.png differ diff --git a/app/assets/images/emoji/atm.png b/app/assets/images/emoji/atm.png new file mode 100755 index 00000000..c2846e79 Binary files /dev/null and b/app/assets/images/emoji/atm.png differ diff --git a/app/assets/images/emoji/b.png b/app/assets/images/emoji/b.png new file mode 100755 index 00000000..8742b3d2 Binary files /dev/null and b/app/assets/images/emoji/b.png differ diff --git a/app/assets/images/emoji/baby.png b/app/assets/images/emoji/baby.png new file mode 100755 index 00000000..3b29da40 Binary files /dev/null and b/app/assets/images/emoji/baby.png differ diff --git a/app/assets/images/emoji/baby_bottle.png b/app/assets/images/emoji/baby_bottle.png new file mode 100755 index 00000000..1b2cfe5e Binary files /dev/null and b/app/assets/images/emoji/baby_bottle.png differ diff --git a/app/assets/images/emoji/baby_chick.png b/app/assets/images/emoji/baby_chick.png new file mode 100755 index 00000000..9be8d293 Binary files /dev/null and b/app/assets/images/emoji/baby_chick.png differ diff --git a/app/assets/images/emoji/baby_symbol.png b/app/assets/images/emoji/baby_symbol.png new file mode 100755 index 00000000..2e58725c Binary files /dev/null and b/app/assets/images/emoji/baby_symbol.png differ diff --git a/app/assets/images/emoji/baggage_claim.png b/app/assets/images/emoji/baggage_claim.png new file mode 100755 index 00000000..59ae044a Binary files /dev/null and b/app/assets/images/emoji/baggage_claim.png differ diff --git a/app/assets/images/emoji/balloon.png b/app/assets/images/emoji/balloon.png new file mode 100755 index 00000000..03448970 Binary files /dev/null and b/app/assets/images/emoji/balloon.png differ diff --git a/app/assets/images/emoji/ballot_box_with_check.png b/app/assets/images/emoji/ballot_box_with_check.png new file mode 100755 index 00000000..f07a466c Binary files /dev/null and b/app/assets/images/emoji/ballot_box_with_check.png differ diff --git a/app/assets/images/emoji/bamboo.png b/app/assets/images/emoji/bamboo.png new file mode 100755 index 00000000..fc858d0f Binary files /dev/null and b/app/assets/images/emoji/bamboo.png differ diff --git a/app/assets/images/emoji/banana.png b/app/assets/images/emoji/banana.png new file mode 100755 index 00000000..a0563afb Binary files /dev/null and b/app/assets/images/emoji/banana.png differ diff --git a/app/assets/images/emoji/bangbang.png b/app/assets/images/emoji/bangbang.png new file mode 100755 index 00000000..7270f0af Binary files /dev/null and b/app/assets/images/emoji/bangbang.png differ diff --git a/app/assets/images/emoji/bank.png b/app/assets/images/emoji/bank.png new file mode 100755 index 00000000..1faa8777 Binary files /dev/null and b/app/assets/images/emoji/bank.png differ diff --git a/app/assets/images/emoji/bar_chart.png b/app/assets/images/emoji/bar_chart.png new file mode 100755 index 00000000..7871cc60 Binary files /dev/null and b/app/assets/images/emoji/bar_chart.png differ diff --git a/app/assets/images/emoji/barber.png b/app/assets/images/emoji/barber.png new file mode 100755 index 00000000..a10cb232 Binary files /dev/null and b/app/assets/images/emoji/barber.png differ diff --git a/app/assets/images/emoji/baseball.png b/app/assets/images/emoji/baseball.png new file mode 100755 index 00000000..da004e2e Binary files /dev/null and b/app/assets/images/emoji/baseball.png differ diff --git a/app/assets/images/emoji/basketball.png b/app/assets/images/emoji/basketball.png new file mode 100755 index 00000000..ef694bec Binary files /dev/null and b/app/assets/images/emoji/basketball.png differ diff --git a/app/assets/images/emoji/bath.png b/app/assets/images/emoji/bath.png new file mode 100755 index 00000000..8f75d1d2 Binary files /dev/null and b/app/assets/images/emoji/bath.png differ diff --git a/app/assets/images/emoji/bathtub.png b/app/assets/images/emoji/bathtub.png new file mode 100755 index 00000000..1c3f844a Binary files /dev/null and b/app/assets/images/emoji/bathtub.png differ diff --git a/app/assets/images/emoji/battery.png b/app/assets/images/emoji/battery.png new file mode 100755 index 00000000..aa7eedce Binary files /dev/null and b/app/assets/images/emoji/battery.png differ diff --git a/app/assets/images/emoji/bear.png b/app/assets/images/emoji/bear.png new file mode 100755 index 00000000..f5afe920 Binary files /dev/null and b/app/assets/images/emoji/bear.png differ diff --git a/app/assets/images/emoji/beer.png b/app/assets/images/emoji/beer.png new file mode 100755 index 00000000..cd78bed7 Binary files /dev/null and b/app/assets/images/emoji/beer.png differ diff --git a/app/assets/images/emoji/beers.png b/app/assets/images/emoji/beers.png new file mode 100755 index 00000000..cc5e4ab5 Binary files /dev/null and b/app/assets/images/emoji/beers.png differ diff --git a/app/assets/images/emoji/beetle.png b/app/assets/images/emoji/beetle.png new file mode 100755 index 00000000..222577ca Binary files /dev/null and b/app/assets/images/emoji/beetle.png differ diff --git a/app/assets/images/emoji/beginner.png b/app/assets/images/emoji/beginner.png new file mode 100755 index 00000000..1f022d17 Binary files /dev/null and b/app/assets/images/emoji/beginner.png differ diff --git a/app/assets/images/emoji/bell.png b/app/assets/images/emoji/bell.png new file mode 100755 index 00000000..69acceb2 Binary files /dev/null and b/app/assets/images/emoji/bell.png differ diff --git a/app/assets/images/emoji/bento.png b/app/assets/images/emoji/bento.png new file mode 100755 index 00000000..c6d99e89 Binary files /dev/null and b/app/assets/images/emoji/bento.png differ diff --git a/app/assets/images/emoji/bicyclist.png b/app/assets/images/emoji/bicyclist.png new file mode 100755 index 00000000..4e3e0549 Binary files /dev/null and b/app/assets/images/emoji/bicyclist.png differ diff --git a/app/assets/images/emoji/bike.png b/app/assets/images/emoji/bike.png new file mode 100755 index 00000000..65738602 Binary files /dev/null and b/app/assets/images/emoji/bike.png differ diff --git a/app/assets/images/emoji/bikini.png b/app/assets/images/emoji/bikini.png new file mode 100755 index 00000000..4ff63b40 Binary files /dev/null and b/app/assets/images/emoji/bikini.png differ diff --git a/app/assets/images/emoji/bird.png b/app/assets/images/emoji/bird.png new file mode 100755 index 00000000..e6be8c02 Binary files /dev/null and b/app/assets/images/emoji/bird.png differ diff --git a/app/assets/images/emoji/birthday.png b/app/assets/images/emoji/birthday.png new file mode 100755 index 00000000..36e8edcb Binary files /dev/null and b/app/assets/images/emoji/birthday.png differ diff --git a/app/assets/images/emoji/black_circle.png b/app/assets/images/emoji/black_circle.png new file mode 100755 index 00000000..e46f9df6 Binary files /dev/null and b/app/assets/images/emoji/black_circle.png differ diff --git a/app/assets/images/emoji/black_joker.png b/app/assets/images/emoji/black_joker.png new file mode 100755 index 00000000..4c78f361 Binary files /dev/null and b/app/assets/images/emoji/black_joker.png differ diff --git a/app/assets/images/emoji/black_nib.png b/app/assets/images/emoji/black_nib.png new file mode 100755 index 00000000..29f6994c Binary files /dev/null and b/app/assets/images/emoji/black_nib.png differ diff --git a/app/assets/images/emoji/black_square.png b/app/assets/images/emoji/black_square.png new file mode 100755 index 00000000..71da10de Binary files /dev/null and b/app/assets/images/emoji/black_square.png differ diff --git a/app/assets/images/emoji/blossom.png b/app/assets/images/emoji/blossom.png new file mode 100755 index 00000000..55a97353 Binary files /dev/null and b/app/assets/images/emoji/blossom.png differ diff --git a/app/assets/images/emoji/blowfish.png b/app/assets/images/emoji/blowfish.png new file mode 100755 index 00000000..a1d47cb7 Binary files /dev/null and b/app/assets/images/emoji/blowfish.png differ diff --git a/app/assets/images/emoji/blue_book.png b/app/assets/images/emoji/blue_book.png new file mode 100755 index 00000000..e2b9e8c7 Binary files /dev/null and b/app/assets/images/emoji/blue_book.png differ diff --git a/app/assets/images/emoji/blue_car.png b/app/assets/images/emoji/blue_car.png new file mode 100755 index 00000000..978291e0 Binary files /dev/null and b/app/assets/images/emoji/blue_car.png differ diff --git a/app/assets/images/emoji/blue_heart.png b/app/assets/images/emoji/blue_heart.png new file mode 100755 index 00000000..baa29b31 Binary files /dev/null and b/app/assets/images/emoji/blue_heart.png differ diff --git a/app/assets/images/emoji/blush.png b/app/assets/images/emoji/blush.png new file mode 100755 index 00000000..3a95eb61 Binary files /dev/null and b/app/assets/images/emoji/blush.png differ diff --git a/app/assets/images/emoji/boar.png b/app/assets/images/emoji/boar.png new file mode 100755 index 00000000..8196ad4a Binary files /dev/null and b/app/assets/images/emoji/boar.png differ diff --git a/app/assets/images/emoji/boat.png b/app/assets/images/emoji/boat.png new file mode 100755 index 00000000..ff656dc6 Binary files /dev/null and b/app/assets/images/emoji/boat.png differ diff --git a/app/assets/images/emoji/bomb.png b/app/assets/images/emoji/bomb.png new file mode 100755 index 00000000..3289787d Binary files /dev/null and b/app/assets/images/emoji/bomb.png differ diff --git a/app/assets/images/emoji/book.png b/app/assets/images/emoji/book.png new file mode 100755 index 00000000..8b698415 Binary files /dev/null and b/app/assets/images/emoji/book.png differ diff --git a/app/assets/images/emoji/bookmark.png b/app/assets/images/emoji/bookmark.png new file mode 100755 index 00000000..dbee45c6 Binary files /dev/null and b/app/assets/images/emoji/bookmark.png differ diff --git a/app/assets/images/emoji/bookmark_tabs.png b/app/assets/images/emoji/bookmark_tabs.png new file mode 100755 index 00000000..0c4e3bf1 Binary files /dev/null and b/app/assets/images/emoji/bookmark_tabs.png differ diff --git a/app/assets/images/emoji/books.png b/app/assets/images/emoji/books.png new file mode 100755 index 00000000..dca06a1a Binary files /dev/null and b/app/assets/images/emoji/books.png differ diff --git a/app/assets/images/emoji/boot.png b/app/assets/images/emoji/boot.png new file mode 100755 index 00000000..58d0fdbc Binary files /dev/null and b/app/assets/images/emoji/boot.png differ diff --git a/app/assets/images/emoji/bouquet.png b/app/assets/images/emoji/bouquet.png new file mode 100755 index 00000000..ce637832 Binary files /dev/null and b/app/assets/images/emoji/bouquet.png differ diff --git a/app/assets/images/emoji/bow.png b/app/assets/images/emoji/bow.png new file mode 100755 index 00000000..024cb610 Binary files /dev/null and b/app/assets/images/emoji/bow.png differ diff --git a/app/assets/images/emoji/bowling.png b/app/assets/images/emoji/bowling.png new file mode 100755 index 00000000..13d8ece2 Binary files /dev/null and b/app/assets/images/emoji/bowling.png differ diff --git a/app/assets/images/emoji/bowtie.png b/app/assets/images/emoji/bowtie.png new file mode 100755 index 00000000..28ff0c78 Binary files /dev/null and b/app/assets/images/emoji/bowtie.png differ diff --git a/app/assets/images/emoji/boy.png b/app/assets/images/emoji/boy.png new file mode 100755 index 00000000..f79f1f29 Binary files /dev/null and b/app/assets/images/emoji/boy.png differ diff --git a/app/assets/images/emoji/bread.png b/app/assets/images/emoji/bread.png new file mode 100755 index 00000000..7e7c6375 Binary files /dev/null and b/app/assets/images/emoji/bread.png differ diff --git a/app/assets/images/emoji/bride_with_veil.png b/app/assets/images/emoji/bride_with_veil.png new file mode 100755 index 00000000..dd0b0cfd Binary files /dev/null and b/app/assets/images/emoji/bride_with_veil.png differ diff --git a/app/assets/images/emoji/bridge_at_night.png b/app/assets/images/emoji/bridge_at_night.png new file mode 100755 index 00000000..495b06c3 Binary files /dev/null and b/app/assets/images/emoji/bridge_at_night.png differ diff --git a/app/assets/images/emoji/briefcase.png b/app/assets/images/emoji/briefcase.png new file mode 100755 index 00000000..46e82b00 Binary files /dev/null and b/app/assets/images/emoji/briefcase.png differ diff --git a/app/assets/images/emoji/broken_heart.png b/app/assets/images/emoji/broken_heart.png new file mode 100755 index 00000000..a1bc850e Binary files /dev/null and b/app/assets/images/emoji/broken_heart.png differ diff --git a/app/assets/images/emoji/bug.png b/app/assets/images/emoji/bug.png new file mode 100755 index 00000000..c2eaf7a7 Binary files /dev/null and b/app/assets/images/emoji/bug.png differ diff --git a/app/assets/images/emoji/bulb.png b/app/assets/images/emoji/bulb.png new file mode 100755 index 00000000..23afca1c Binary files /dev/null and b/app/assets/images/emoji/bulb.png differ diff --git a/app/assets/images/emoji/bullettrain_front.png b/app/assets/images/emoji/bullettrain_front.png new file mode 100755 index 00000000..16651acf Binary files /dev/null and b/app/assets/images/emoji/bullettrain_front.png differ diff --git a/app/assets/images/emoji/bullettrain_side.png b/app/assets/images/emoji/bullettrain_side.png new file mode 100755 index 00000000..8eca3684 Binary files /dev/null and b/app/assets/images/emoji/bullettrain_side.png differ diff --git a/app/assets/images/emoji/bus.png b/app/assets/images/emoji/bus.png new file mode 100755 index 00000000..823aa39e Binary files /dev/null and b/app/assets/images/emoji/bus.png differ diff --git a/app/assets/images/emoji/busstop.png b/app/assets/images/emoji/busstop.png new file mode 100755 index 00000000..94894847 Binary files /dev/null and b/app/assets/images/emoji/busstop.png differ diff --git a/app/assets/images/emoji/bust_in_silhouette.png b/app/assets/images/emoji/bust_in_silhouette.png new file mode 100755 index 00000000..d1313986 Binary files /dev/null and b/app/assets/images/emoji/bust_in_silhouette.png differ diff --git a/app/assets/images/emoji/busts_in_silhouette.png b/app/assets/images/emoji/busts_in_silhouette.png new file mode 100755 index 00000000..1f3aabcf Binary files /dev/null and b/app/assets/images/emoji/busts_in_silhouette.png differ diff --git a/app/assets/images/emoji/cactus.png b/app/assets/images/emoji/cactus.png new file mode 100755 index 00000000..5a2c3cc7 Binary files /dev/null and b/app/assets/images/emoji/cactus.png differ diff --git a/app/assets/images/emoji/cake.png b/app/assets/images/emoji/cake.png new file mode 100755 index 00000000..efeb9b4b Binary files /dev/null and b/app/assets/images/emoji/cake.png differ diff --git a/app/assets/images/emoji/calendar.png b/app/assets/images/emoji/calendar.png new file mode 100755 index 00000000..900b868b Binary files /dev/null and b/app/assets/images/emoji/calendar.png differ diff --git a/app/assets/images/emoji/calling.png b/app/assets/images/emoji/calling.png new file mode 100755 index 00000000..837897f2 Binary files /dev/null and b/app/assets/images/emoji/calling.png differ diff --git a/app/assets/images/emoji/camel.png b/app/assets/images/emoji/camel.png new file mode 100755 index 00000000..496c186a Binary files /dev/null and b/app/assets/images/emoji/camel.png differ diff --git a/app/assets/images/emoji/camera.png b/app/assets/images/emoji/camera.png new file mode 100755 index 00000000..397d03b3 Binary files /dev/null and b/app/assets/images/emoji/camera.png differ diff --git a/app/assets/images/emoji/cancer.png b/app/assets/images/emoji/cancer.png new file mode 100755 index 00000000..ea43a4a2 Binary files /dev/null and b/app/assets/images/emoji/cancer.png differ diff --git a/app/assets/images/emoji/candy.png b/app/assets/images/emoji/candy.png new file mode 100755 index 00000000..33722f23 Binary files /dev/null and b/app/assets/images/emoji/candy.png differ diff --git a/app/assets/images/emoji/capital_abcd.png b/app/assets/images/emoji/capital_abcd.png new file mode 100755 index 00000000..ffc0cba4 Binary files /dev/null and b/app/assets/images/emoji/capital_abcd.png differ diff --git a/app/assets/images/emoji/capricorn.png b/app/assets/images/emoji/capricorn.png new file mode 100755 index 00000000..f2044e78 Binary files /dev/null and b/app/assets/images/emoji/capricorn.png differ diff --git a/app/assets/images/emoji/car.png b/app/assets/images/emoji/car.png new file mode 100755 index 00000000..d70a2f06 Binary files /dev/null and b/app/assets/images/emoji/car.png differ diff --git a/app/assets/images/emoji/card_index.png b/app/assets/images/emoji/card_index.png new file mode 100755 index 00000000..374e94e9 Binary files /dev/null and b/app/assets/images/emoji/card_index.png differ diff --git a/app/assets/images/emoji/carousel_horse.png b/app/assets/images/emoji/carousel_horse.png new file mode 100755 index 00000000..765d2c0a Binary files /dev/null and b/app/assets/images/emoji/carousel_horse.png differ diff --git a/app/assets/images/emoji/cat.png b/app/assets/images/emoji/cat.png new file mode 100755 index 00000000..09b9ef79 Binary files /dev/null and b/app/assets/images/emoji/cat.png differ diff --git a/app/assets/images/emoji/cat2.png b/app/assets/images/emoji/cat2.png new file mode 100755 index 00000000..977c992c Binary files /dev/null and b/app/assets/images/emoji/cat2.png differ diff --git a/app/assets/images/emoji/cd.png b/app/assets/images/emoji/cd.png new file mode 100755 index 00000000..baff835c Binary files /dev/null and b/app/assets/images/emoji/cd.png differ diff --git a/app/assets/images/emoji/chart.png b/app/assets/images/emoji/chart.png new file mode 100755 index 00000000..ac2c4bb0 Binary files /dev/null and b/app/assets/images/emoji/chart.png differ diff --git a/app/assets/images/emoji/chart_with_downwards_trend.png b/app/assets/images/emoji/chart_with_downwards_trend.png new file mode 100755 index 00000000..65b82f04 Binary files /dev/null and b/app/assets/images/emoji/chart_with_downwards_trend.png differ diff --git a/app/assets/images/emoji/chart_with_upwards_trend.png b/app/assets/images/emoji/chart_with_upwards_trend.png new file mode 100755 index 00000000..de3e9ba7 Binary files /dev/null and b/app/assets/images/emoji/chart_with_upwards_trend.png differ diff --git a/app/assets/images/emoji/checkered_flag.png b/app/assets/images/emoji/checkered_flag.png new file mode 100755 index 00000000..ead4a68d Binary files /dev/null and b/app/assets/images/emoji/checkered_flag.png differ diff --git a/app/assets/images/emoji/cherries.png b/app/assets/images/emoji/cherries.png new file mode 100755 index 00000000..8d3e044f Binary files /dev/null and b/app/assets/images/emoji/cherries.png differ diff --git a/app/assets/images/emoji/cherry_blossom.png b/app/assets/images/emoji/cherry_blossom.png new file mode 100755 index 00000000..e0315549 Binary files /dev/null and b/app/assets/images/emoji/cherry_blossom.png differ diff --git a/app/assets/images/emoji/chestnut.png b/app/assets/images/emoji/chestnut.png new file mode 100755 index 00000000..066fb6bf Binary files /dev/null and b/app/assets/images/emoji/chestnut.png differ diff --git a/app/assets/images/emoji/chicken.png b/app/assets/images/emoji/chicken.png new file mode 100755 index 00000000..6d25c0ef Binary files /dev/null and b/app/assets/images/emoji/chicken.png differ diff --git a/app/assets/images/emoji/children_crossing.png b/app/assets/images/emoji/children_crossing.png new file mode 100755 index 00000000..b0302ae6 Binary files /dev/null and b/app/assets/images/emoji/children_crossing.png differ diff --git a/app/assets/images/emoji/chocolate_bar.png b/app/assets/images/emoji/chocolate_bar.png new file mode 100755 index 00000000..c7ec19d0 Binary files /dev/null and b/app/assets/images/emoji/chocolate_bar.png differ diff --git a/app/assets/images/emoji/christmas_tree.png b/app/assets/images/emoji/christmas_tree.png new file mode 100755 index 00000000..d813b959 Binary files /dev/null and b/app/assets/images/emoji/christmas_tree.png differ diff --git a/app/assets/images/emoji/church.png b/app/assets/images/emoji/church.png new file mode 100755 index 00000000..4c07c6b9 Binary files /dev/null and b/app/assets/images/emoji/church.png differ diff --git a/app/assets/images/emoji/cinema.png b/app/assets/images/emoji/cinema.png new file mode 100755 index 00000000..a990ccf9 Binary files /dev/null and b/app/assets/images/emoji/cinema.png differ diff --git a/app/assets/images/emoji/circus_tent.png b/app/assets/images/emoji/circus_tent.png new file mode 100755 index 00000000..4af8719a Binary files /dev/null and b/app/assets/images/emoji/circus_tent.png differ diff --git a/app/assets/images/emoji/city_sunrise.png b/app/assets/images/emoji/city_sunrise.png new file mode 100755 index 00000000..91ca2a40 Binary files /dev/null and b/app/assets/images/emoji/city_sunrise.png differ diff --git a/app/assets/images/emoji/city_sunset.png b/app/assets/images/emoji/city_sunset.png new file mode 100755 index 00000000..7cb178a2 Binary files /dev/null and b/app/assets/images/emoji/city_sunset.png differ diff --git a/app/assets/images/emoji/cl.png b/app/assets/images/emoji/cl.png new file mode 100755 index 00000000..15ac6752 Binary files /dev/null and b/app/assets/images/emoji/cl.png differ diff --git a/app/assets/images/emoji/clap.png b/app/assets/images/emoji/clap.png new file mode 100755 index 00000000..d01c982a Binary files /dev/null and b/app/assets/images/emoji/clap.png differ diff --git a/app/assets/images/emoji/clapper.png b/app/assets/images/emoji/clapper.png new file mode 100755 index 00000000..4e1dc111 Binary files /dev/null and b/app/assets/images/emoji/clapper.png differ diff --git a/app/assets/images/emoji/clipboard.png b/app/assets/images/emoji/clipboard.png new file mode 100755 index 00000000..e2c74e6d Binary files /dev/null and b/app/assets/images/emoji/clipboard.png differ diff --git a/app/assets/images/emoji/clock1.png b/app/assets/images/emoji/clock1.png new file mode 100755 index 00000000..9174d4e0 Binary files /dev/null and b/app/assets/images/emoji/clock1.png differ diff --git a/app/assets/images/emoji/clock10.png b/app/assets/images/emoji/clock10.png new file mode 100755 index 00000000..39f590d6 Binary files /dev/null and b/app/assets/images/emoji/clock10.png differ diff --git a/app/assets/images/emoji/clock1030.png b/app/assets/images/emoji/clock1030.png new file mode 100755 index 00000000..84a3bc8f Binary files /dev/null and b/app/assets/images/emoji/clock1030.png differ diff --git a/app/assets/images/emoji/clock11.png b/app/assets/images/emoji/clock11.png new file mode 100755 index 00000000..ddb53fad Binary files /dev/null and b/app/assets/images/emoji/clock11.png differ diff --git a/app/assets/images/emoji/clock1130.png b/app/assets/images/emoji/clock1130.png new file mode 100755 index 00000000..415999ec Binary files /dev/null and b/app/assets/images/emoji/clock1130.png differ diff --git a/app/assets/images/emoji/clock12.png b/app/assets/images/emoji/clock12.png new file mode 100755 index 00000000..87b13287 Binary files /dev/null and b/app/assets/images/emoji/clock12.png differ diff --git a/app/assets/images/emoji/clock1230.png b/app/assets/images/emoji/clock1230.png new file mode 100755 index 00000000..a6527154 Binary files /dev/null and b/app/assets/images/emoji/clock1230.png differ diff --git a/app/assets/images/emoji/clock130.png b/app/assets/images/emoji/clock130.png new file mode 100755 index 00000000..df939201 Binary files /dev/null and b/app/assets/images/emoji/clock130.png differ diff --git a/app/assets/images/emoji/clock2.png b/app/assets/images/emoji/clock2.png new file mode 100755 index 00000000..65b3b3af Binary files /dev/null and b/app/assets/images/emoji/clock2.png differ diff --git a/app/assets/images/emoji/clock230.png b/app/assets/images/emoji/clock230.png new file mode 100755 index 00000000..f12c6912 Binary files /dev/null and b/app/assets/images/emoji/clock230.png differ diff --git a/app/assets/images/emoji/clock3.png b/app/assets/images/emoji/clock3.png new file mode 100755 index 00000000..3e44d64e Binary files /dev/null and b/app/assets/images/emoji/clock3.png differ diff --git a/app/assets/images/emoji/clock330.png b/app/assets/images/emoji/clock330.png new file mode 100755 index 00000000..1dc9628e Binary files /dev/null and b/app/assets/images/emoji/clock330.png differ diff --git a/app/assets/images/emoji/clock4.png b/app/assets/images/emoji/clock4.png new file mode 100755 index 00000000..948ed1a3 Binary files /dev/null and b/app/assets/images/emoji/clock4.png differ diff --git a/app/assets/images/emoji/clock430.png b/app/assets/images/emoji/clock430.png new file mode 100755 index 00000000..7726aaea Binary files /dev/null and b/app/assets/images/emoji/clock430.png differ diff --git a/app/assets/images/emoji/clock5.png b/app/assets/images/emoji/clock5.png new file mode 100755 index 00000000..b010b4f8 Binary files /dev/null and b/app/assets/images/emoji/clock5.png differ diff --git a/app/assets/images/emoji/clock530.png b/app/assets/images/emoji/clock530.png new file mode 100755 index 00000000..e08d4ad2 Binary files /dev/null and b/app/assets/images/emoji/clock530.png differ diff --git a/app/assets/images/emoji/clock6.png b/app/assets/images/emoji/clock6.png new file mode 100755 index 00000000..76bf8cf1 Binary files /dev/null and b/app/assets/images/emoji/clock6.png differ diff --git a/app/assets/images/emoji/clock630.png b/app/assets/images/emoji/clock630.png new file mode 100755 index 00000000..46f0681f Binary files /dev/null and b/app/assets/images/emoji/clock630.png differ diff --git a/app/assets/images/emoji/clock7.png b/app/assets/images/emoji/clock7.png new file mode 100755 index 00000000..d48f645d Binary files /dev/null and b/app/assets/images/emoji/clock7.png differ diff --git a/app/assets/images/emoji/clock730.png b/app/assets/images/emoji/clock730.png new file mode 100755 index 00000000..18aab22f Binary files /dev/null and b/app/assets/images/emoji/clock730.png differ diff --git a/app/assets/images/emoji/clock8.png b/app/assets/images/emoji/clock8.png new file mode 100755 index 00000000..74c770d8 Binary files /dev/null and b/app/assets/images/emoji/clock8.png differ diff --git a/app/assets/images/emoji/clock830.png b/app/assets/images/emoji/clock830.png new file mode 100755 index 00000000..ec3e382d Binary files /dev/null and b/app/assets/images/emoji/clock830.png differ diff --git a/app/assets/images/emoji/clock9.png b/app/assets/images/emoji/clock9.png new file mode 100755 index 00000000..f009d14a Binary files /dev/null and b/app/assets/images/emoji/clock9.png differ diff --git a/app/assets/images/emoji/clock930.png b/app/assets/images/emoji/clock930.png new file mode 100755 index 00000000..fd352214 Binary files /dev/null and b/app/assets/images/emoji/clock930.png differ diff --git a/app/assets/images/emoji/closed_book.png b/app/assets/images/emoji/closed_book.png new file mode 100755 index 00000000..484029c5 Binary files /dev/null and b/app/assets/images/emoji/closed_book.png differ diff --git a/app/assets/images/emoji/closed_lock_with_key.png b/app/assets/images/emoji/closed_lock_with_key.png new file mode 100755 index 00000000..e6fdf6cb Binary files /dev/null and b/app/assets/images/emoji/closed_lock_with_key.png differ diff --git a/app/assets/images/emoji/closed_umbrella.png b/app/assets/images/emoji/closed_umbrella.png new file mode 100755 index 00000000..0b719f08 Binary files /dev/null and b/app/assets/images/emoji/closed_umbrella.png differ diff --git a/app/assets/images/emoji/cloud.png b/app/assets/images/emoji/cloud.png new file mode 100755 index 00000000..b31c08c0 Binary files /dev/null and b/app/assets/images/emoji/cloud.png differ diff --git a/app/assets/images/emoji/clubs.png b/app/assets/images/emoji/clubs.png new file mode 100755 index 00000000..bfab5365 Binary files /dev/null and b/app/assets/images/emoji/clubs.png differ diff --git a/app/assets/images/emoji/cn.png b/app/assets/images/emoji/cn.png new file mode 100755 index 00000000..b30dcc53 Binary files /dev/null and b/app/assets/images/emoji/cn.png differ diff --git a/app/assets/images/emoji/cocktail.png b/app/assets/images/emoji/cocktail.png new file mode 100755 index 00000000..28b45ea5 Binary files /dev/null and b/app/assets/images/emoji/cocktail.png differ diff --git a/app/assets/images/emoji/coffee.png b/app/assets/images/emoji/coffee.png new file mode 100755 index 00000000..57e1adcb Binary files /dev/null and b/app/assets/images/emoji/coffee.png differ diff --git a/app/assets/images/emoji/cold_sweat.png b/app/assets/images/emoji/cold_sweat.png new file mode 100755 index 00000000..b9e39bc6 Binary files /dev/null and b/app/assets/images/emoji/cold_sweat.png differ diff --git a/app/assets/images/emoji/collision.png b/app/assets/images/emoji/collision.png new file mode 100755 index 00000000..bddeb8f4 Binary files /dev/null and b/app/assets/images/emoji/collision.png differ diff --git a/app/assets/images/emoji/computer.png b/app/assets/images/emoji/computer.png new file mode 100755 index 00000000..d4d26876 Binary files /dev/null and b/app/assets/images/emoji/computer.png differ diff --git a/app/assets/images/emoji/confetti_ball.png b/app/assets/images/emoji/confetti_ball.png new file mode 100755 index 00000000..bd293e3d Binary files /dev/null and b/app/assets/images/emoji/confetti_ball.png differ diff --git a/app/assets/images/emoji/confounded.png b/app/assets/images/emoji/confounded.png new file mode 100755 index 00000000..762c3766 Binary files /dev/null and b/app/assets/images/emoji/confounded.png differ diff --git a/app/assets/images/emoji/congratulations.png b/app/assets/images/emoji/congratulations.png new file mode 100755 index 00000000..dcbb1d22 Binary files /dev/null and b/app/assets/images/emoji/congratulations.png differ diff --git a/app/assets/images/emoji/construction.png b/app/assets/images/emoji/construction.png new file mode 100755 index 00000000..523e9f10 Binary files /dev/null and b/app/assets/images/emoji/construction.png differ diff --git a/app/assets/images/emoji/construction_worker.png b/app/assets/images/emoji/construction_worker.png new file mode 100755 index 00000000..4d648604 Binary files /dev/null and b/app/assets/images/emoji/construction_worker.png differ diff --git a/app/assets/images/emoji/convenience_store.png b/app/assets/images/emoji/convenience_store.png new file mode 100755 index 00000000..671696c2 Binary files /dev/null and b/app/assets/images/emoji/convenience_store.png differ diff --git a/app/assets/images/emoji/cookie.png b/app/assets/images/emoji/cookie.png new file mode 100755 index 00000000..653edb25 Binary files /dev/null and b/app/assets/images/emoji/cookie.png differ diff --git a/app/assets/images/emoji/cool.png b/app/assets/images/emoji/cool.png new file mode 100755 index 00000000..937dcd79 Binary files /dev/null and b/app/assets/images/emoji/cool.png differ diff --git a/app/assets/images/emoji/cop.png b/app/assets/images/emoji/cop.png new file mode 100755 index 00000000..43a5a84f Binary files /dev/null and b/app/assets/images/emoji/cop.png differ diff --git a/app/assets/images/emoji/copyright.png b/app/assets/images/emoji/copyright.png new file mode 100755 index 00000000..38493c33 Binary files /dev/null and b/app/assets/images/emoji/copyright.png differ diff --git a/app/assets/images/emoji/corn.png b/app/assets/images/emoji/corn.png new file mode 100755 index 00000000..fe5d8b12 Binary files /dev/null and b/app/assets/images/emoji/corn.png differ diff --git a/app/assets/images/emoji/couple.png b/app/assets/images/emoji/couple.png new file mode 100755 index 00000000..9e51f40e Binary files /dev/null and b/app/assets/images/emoji/couple.png differ diff --git a/app/assets/images/emoji/couple_with_heart.png b/app/assets/images/emoji/couple_with_heart.png new file mode 100755 index 00000000..c503f40a Binary files /dev/null and b/app/assets/images/emoji/couple_with_heart.png differ diff --git a/app/assets/images/emoji/couplekiss.png b/app/assets/images/emoji/couplekiss.png new file mode 100755 index 00000000..d0279082 Binary files /dev/null and b/app/assets/images/emoji/couplekiss.png differ diff --git a/app/assets/images/emoji/cow.png b/app/assets/images/emoji/cow.png new file mode 100755 index 00000000..12e1ab6c Binary files /dev/null and b/app/assets/images/emoji/cow.png differ diff --git a/app/assets/images/emoji/cow2.png b/app/assets/images/emoji/cow2.png new file mode 100755 index 00000000..594c9215 Binary files /dev/null and b/app/assets/images/emoji/cow2.png differ diff --git a/app/assets/images/emoji/credit_card.png b/app/assets/images/emoji/credit_card.png new file mode 100755 index 00000000..be1c1dd3 Binary files /dev/null and b/app/assets/images/emoji/credit_card.png differ diff --git a/app/assets/images/emoji/crocodile.png b/app/assets/images/emoji/crocodile.png new file mode 100755 index 00000000..7435d5ab Binary files /dev/null and b/app/assets/images/emoji/crocodile.png differ diff --git a/app/assets/images/emoji/crossed_flags.png b/app/assets/images/emoji/crossed_flags.png new file mode 100755 index 00000000..2397bcd0 Binary files /dev/null and b/app/assets/images/emoji/crossed_flags.png differ diff --git a/app/assets/images/emoji/crown.png b/app/assets/images/emoji/crown.png new file mode 100755 index 00000000..39da1d52 Binary files /dev/null and b/app/assets/images/emoji/crown.png differ diff --git a/app/assets/images/emoji/cry.png b/app/assets/images/emoji/cry.png new file mode 100755 index 00000000..6d0d9afd Binary files /dev/null and b/app/assets/images/emoji/cry.png differ diff --git a/app/assets/images/emoji/crying_cat_face.png b/app/assets/images/emoji/crying_cat_face.png new file mode 100755 index 00000000..42d4c27c Binary files /dev/null and b/app/assets/images/emoji/crying_cat_face.png differ diff --git a/app/assets/images/emoji/crystal_ball.png b/app/assets/images/emoji/crystal_ball.png new file mode 100755 index 00000000..6d2c6c42 Binary files /dev/null and b/app/assets/images/emoji/crystal_ball.png differ diff --git a/app/assets/images/emoji/cupid.png b/app/assets/images/emoji/cupid.png new file mode 100755 index 00000000..49872847 Binary files /dev/null and b/app/assets/images/emoji/cupid.png differ diff --git a/app/assets/images/emoji/curly_loop.png b/app/assets/images/emoji/curly_loop.png new file mode 100755 index 00000000..8f051aca Binary files /dev/null and b/app/assets/images/emoji/curly_loop.png differ diff --git a/app/assets/images/emoji/currency_exchange.png b/app/assets/images/emoji/currency_exchange.png new file mode 100755 index 00000000..6ebebe70 Binary files /dev/null and b/app/assets/images/emoji/currency_exchange.png differ diff --git a/app/assets/images/emoji/curry.png b/app/assets/images/emoji/curry.png new file mode 100755 index 00000000..7983c706 Binary files /dev/null and b/app/assets/images/emoji/curry.png differ diff --git a/app/assets/images/emoji/custard.png b/app/assets/images/emoji/custard.png new file mode 100755 index 00000000..9f843b4c Binary files /dev/null and b/app/assets/images/emoji/custard.png differ diff --git a/app/assets/images/emoji/customs.png b/app/assets/images/emoji/customs.png new file mode 100755 index 00000000..92691e31 Binary files /dev/null and b/app/assets/images/emoji/customs.png differ diff --git a/app/assets/images/emoji/cyclone.png b/app/assets/images/emoji/cyclone.png new file mode 100755 index 00000000..5fd2e451 Binary files /dev/null and b/app/assets/images/emoji/cyclone.png differ diff --git a/app/assets/images/emoji/dancer.png b/app/assets/images/emoji/dancer.png new file mode 100755 index 00000000..6885a0bc Binary files /dev/null and b/app/assets/images/emoji/dancer.png differ diff --git a/app/assets/images/emoji/dancers.png b/app/assets/images/emoji/dancers.png new file mode 100755 index 00000000..2dfb451a Binary files /dev/null and b/app/assets/images/emoji/dancers.png differ diff --git a/app/assets/images/emoji/dango.png b/app/assets/images/emoji/dango.png new file mode 100755 index 00000000..2d042aeb Binary files /dev/null and b/app/assets/images/emoji/dango.png differ diff --git a/app/assets/images/emoji/dart.png b/app/assets/images/emoji/dart.png new file mode 100755 index 00000000..0438fe54 Binary files /dev/null and b/app/assets/images/emoji/dart.png differ diff --git a/app/assets/images/emoji/dash.png b/app/assets/images/emoji/dash.png new file mode 100755 index 00000000..dc2c0a8f Binary files /dev/null and b/app/assets/images/emoji/dash.png differ diff --git a/app/assets/images/emoji/date.png b/app/assets/images/emoji/date.png new file mode 100755 index 00000000..6ad2efa5 Binary files /dev/null and b/app/assets/images/emoji/date.png differ diff --git a/app/assets/images/emoji/de.png b/app/assets/images/emoji/de.png new file mode 100755 index 00000000..16a28548 Binary files /dev/null and b/app/assets/images/emoji/de.png differ diff --git a/app/assets/images/emoji/deciduous_tree.png b/app/assets/images/emoji/deciduous_tree.png new file mode 100755 index 00000000..9bb16bdf Binary files /dev/null and b/app/assets/images/emoji/deciduous_tree.png differ diff --git a/app/assets/images/emoji/department_store.png b/app/assets/images/emoji/department_store.png new file mode 100755 index 00000000..68d959c5 Binary files /dev/null and b/app/assets/images/emoji/department_store.png differ diff --git a/app/assets/images/emoji/diamond_shape_with_a_dot_inside.png b/app/assets/images/emoji/diamond_shape_with_a_dot_inside.png new file mode 100755 index 00000000..dfd1098b Binary files /dev/null and b/app/assets/images/emoji/diamond_shape_with_a_dot_inside.png differ diff --git a/app/assets/images/emoji/diamonds.png b/app/assets/images/emoji/diamonds.png new file mode 100755 index 00000000..fe082775 Binary files /dev/null and b/app/assets/images/emoji/diamonds.png differ diff --git a/app/assets/images/emoji/disappointed.png b/app/assets/images/emoji/disappointed.png new file mode 100755 index 00000000..82552008 Binary files /dev/null and b/app/assets/images/emoji/disappointed.png differ diff --git a/app/assets/images/emoji/dizzy.png b/app/assets/images/emoji/dizzy.png new file mode 100755 index 00000000..55213d2d Binary files /dev/null and b/app/assets/images/emoji/dizzy.png differ diff --git a/app/assets/images/emoji/dizzy_face.png b/app/assets/images/emoji/dizzy_face.png new file mode 100755 index 00000000..8001d6ff Binary files /dev/null and b/app/assets/images/emoji/dizzy_face.png differ diff --git a/app/assets/images/emoji/do_not_litter.png b/app/assets/images/emoji/do_not_litter.png new file mode 100755 index 00000000..38c7ae7a Binary files /dev/null and b/app/assets/images/emoji/do_not_litter.png differ diff --git a/app/assets/images/emoji/dog.png b/app/assets/images/emoji/dog.png new file mode 100755 index 00000000..389a02bf Binary files /dev/null and b/app/assets/images/emoji/dog.png differ diff --git a/app/assets/images/emoji/dog2.png b/app/assets/images/emoji/dog2.png new file mode 100755 index 00000000..c7f6a24a Binary files /dev/null and b/app/assets/images/emoji/dog2.png differ diff --git a/app/assets/images/emoji/dollar.png b/app/assets/images/emoji/dollar.png new file mode 100755 index 00000000..63de8849 Binary files /dev/null and b/app/assets/images/emoji/dollar.png differ diff --git a/app/assets/images/emoji/dolls.png b/app/assets/images/emoji/dolls.png new file mode 100755 index 00000000..47ce3390 Binary files /dev/null and b/app/assets/images/emoji/dolls.png differ diff --git a/app/assets/images/emoji/dolphin.png b/app/assets/images/emoji/dolphin.png new file mode 100755 index 00000000..9326077a Binary files /dev/null and b/app/assets/images/emoji/dolphin.png differ diff --git a/app/assets/images/emoji/door.png b/app/assets/images/emoji/door.png new file mode 100755 index 00000000..83c819ae Binary files /dev/null and b/app/assets/images/emoji/door.png differ diff --git a/app/assets/images/emoji/doughnut.png b/app/assets/images/emoji/doughnut.png new file mode 100755 index 00000000..ccf86912 Binary files /dev/null and b/app/assets/images/emoji/doughnut.png differ diff --git a/app/assets/images/emoji/dragon.png b/app/assets/images/emoji/dragon.png new file mode 100755 index 00000000..e399d60e Binary files /dev/null and b/app/assets/images/emoji/dragon.png differ diff --git a/app/assets/images/emoji/dragon_face.png b/app/assets/images/emoji/dragon_face.png new file mode 100755 index 00000000..e5e556bd Binary files /dev/null and b/app/assets/images/emoji/dragon_face.png differ diff --git a/app/assets/images/emoji/dress.png b/app/assets/images/emoji/dress.png new file mode 100755 index 00000000..6434e2e2 Binary files /dev/null and b/app/assets/images/emoji/dress.png differ diff --git a/app/assets/images/emoji/dromedary_camel.png b/app/assets/images/emoji/dromedary_camel.png new file mode 100755 index 00000000..c8c7b9ff Binary files /dev/null and b/app/assets/images/emoji/dromedary_camel.png differ diff --git a/app/assets/images/emoji/droplet.png b/app/assets/images/emoji/droplet.png new file mode 100755 index 00000000..9eff4633 Binary files /dev/null and b/app/assets/images/emoji/droplet.png differ diff --git a/app/assets/images/emoji/dvd.png b/app/assets/images/emoji/dvd.png new file mode 100755 index 00000000..363c83d0 Binary files /dev/null and b/app/assets/images/emoji/dvd.png differ diff --git a/app/assets/images/emoji/e-mail.png b/app/assets/images/emoji/e-mail.png new file mode 100755 index 00000000..176a8e1e Binary files /dev/null and b/app/assets/images/emoji/e-mail.png differ diff --git a/app/assets/images/emoji/ear.png b/app/assets/images/emoji/ear.png new file mode 100755 index 00000000..2bbbf10c Binary files /dev/null and b/app/assets/images/emoji/ear.png differ diff --git a/app/assets/images/emoji/ear_of_rice.png b/app/assets/images/emoji/ear_of_rice.png new file mode 100755 index 00000000..a9bba5c2 Binary files /dev/null and b/app/assets/images/emoji/ear_of_rice.png differ diff --git a/app/assets/images/emoji/earth_africa.png b/app/assets/images/emoji/earth_africa.png new file mode 100755 index 00000000..44ce5ecb Binary files /dev/null and b/app/assets/images/emoji/earth_africa.png differ diff --git a/app/assets/images/emoji/earth_americas.png b/app/assets/images/emoji/earth_americas.png new file mode 100755 index 00000000..97d71767 Binary files /dev/null and b/app/assets/images/emoji/earth_americas.png differ diff --git a/app/assets/images/emoji/earth_asia.png b/app/assets/images/emoji/earth_asia.png new file mode 100755 index 00000000..95ec357c Binary files /dev/null and b/app/assets/images/emoji/earth_asia.png differ diff --git a/app/assets/images/emoji/egg.png b/app/assets/images/emoji/egg.png new file mode 100755 index 00000000..c3de6ae4 Binary files /dev/null and b/app/assets/images/emoji/egg.png differ diff --git a/app/assets/images/emoji/eggplant.png b/app/assets/images/emoji/eggplant.png new file mode 100755 index 00000000..66f25fce Binary files /dev/null and b/app/assets/images/emoji/eggplant.png differ diff --git a/app/assets/images/emoji/eight.png b/app/assets/images/emoji/eight.png new file mode 100755 index 00000000..7bdb4223 Binary files /dev/null and b/app/assets/images/emoji/eight.png differ diff --git a/app/assets/images/emoji/eight_pointed_black_star.png b/app/assets/images/emoji/eight_pointed_black_star.png new file mode 100755 index 00000000..6ddaa21f Binary files /dev/null and b/app/assets/images/emoji/eight_pointed_black_star.png differ diff --git a/app/assets/images/emoji/eight_spoked_asterisk.png b/app/assets/images/emoji/eight_spoked_asterisk.png new file mode 100755 index 00000000..946a2033 Binary files /dev/null and b/app/assets/images/emoji/eight_spoked_asterisk.png differ diff --git a/app/assets/images/emoji/electric_plug.png b/app/assets/images/emoji/electric_plug.png new file mode 100755 index 00000000..7a3d6cee Binary files /dev/null and b/app/assets/images/emoji/electric_plug.png differ diff --git a/app/assets/images/emoji/elephant.png b/app/assets/images/emoji/elephant.png new file mode 100755 index 00000000..5ca04570 Binary files /dev/null and b/app/assets/images/emoji/elephant.png differ diff --git a/app/assets/images/emoji/email.png b/app/assets/images/emoji/email.png new file mode 100755 index 00000000..0e01fd5f Binary files /dev/null and b/app/assets/images/emoji/email.png differ diff --git a/app/assets/images/emoji/end.png b/app/assets/images/emoji/end.png new file mode 100755 index 00000000..edb0bda2 Binary files /dev/null and b/app/assets/images/emoji/end.png differ diff --git a/app/assets/images/emoji/envelope.png b/app/assets/images/emoji/envelope.png new file mode 100755 index 00000000..3631861b Binary files /dev/null and b/app/assets/images/emoji/envelope.png differ diff --git a/app/assets/images/emoji/es.png b/app/assets/images/emoji/es.png new file mode 100755 index 00000000..71b30bff Binary files /dev/null and b/app/assets/images/emoji/es.png differ diff --git a/app/assets/images/emoji/euro.png b/app/assets/images/emoji/euro.png new file mode 100755 index 00000000..1c5904b7 Binary files /dev/null and b/app/assets/images/emoji/euro.png differ diff --git a/app/assets/images/emoji/european_castle.png b/app/assets/images/emoji/european_castle.png new file mode 100755 index 00000000..8229b8a8 Binary files /dev/null and b/app/assets/images/emoji/european_castle.png differ diff --git a/app/assets/images/emoji/european_post_office.png b/app/assets/images/emoji/european_post_office.png new file mode 100755 index 00000000..0f65b145 Binary files /dev/null and b/app/assets/images/emoji/european_post_office.png differ diff --git a/app/assets/images/emoji/evergreen_tree.png b/app/assets/images/emoji/evergreen_tree.png new file mode 100755 index 00000000..ae8ad103 Binary files /dev/null and b/app/assets/images/emoji/evergreen_tree.png differ diff --git a/app/assets/images/emoji/exclamation.png b/app/assets/images/emoji/exclamation.png new file mode 100755 index 00000000..77bbdeab Binary files /dev/null and b/app/assets/images/emoji/exclamation.png differ diff --git a/app/assets/images/emoji/eyeglasses.png b/app/assets/images/emoji/eyeglasses.png new file mode 100755 index 00000000..a3cf75a2 Binary files /dev/null and b/app/assets/images/emoji/eyeglasses.png differ diff --git a/app/assets/images/emoji/eyes.png b/app/assets/images/emoji/eyes.png new file mode 100755 index 00000000..dc2216f6 Binary files /dev/null and b/app/assets/images/emoji/eyes.png differ diff --git a/app/assets/images/emoji/facepunch.png b/app/assets/images/emoji/facepunch.png new file mode 100755 index 00000000..2d41fd37 Binary files /dev/null and b/app/assets/images/emoji/facepunch.png differ diff --git a/app/assets/images/emoji/factory.png b/app/assets/images/emoji/factory.png new file mode 100755 index 00000000..64046347 Binary files /dev/null and b/app/assets/images/emoji/factory.png differ diff --git a/app/assets/images/emoji/fallen_leaf.png b/app/assets/images/emoji/fallen_leaf.png new file mode 100755 index 00000000..d49f9c17 Binary files /dev/null and b/app/assets/images/emoji/fallen_leaf.png differ diff --git a/app/assets/images/emoji/family.png b/app/assets/images/emoji/family.png new file mode 100755 index 00000000..b4b365f3 Binary files /dev/null and b/app/assets/images/emoji/family.png differ diff --git a/app/assets/images/emoji/fast_forward.png b/app/assets/images/emoji/fast_forward.png new file mode 100755 index 00000000..8830e146 Binary files /dev/null and b/app/assets/images/emoji/fast_forward.png differ diff --git a/app/assets/images/emoji/fax.png b/app/assets/images/emoji/fax.png new file mode 100755 index 00000000..62be2c95 Binary files /dev/null and b/app/assets/images/emoji/fax.png differ diff --git a/app/assets/images/emoji/fearful.png b/app/assets/images/emoji/fearful.png new file mode 100755 index 00000000..513fce47 Binary files /dev/null and b/app/assets/images/emoji/fearful.png differ diff --git a/app/assets/images/emoji/feelsgood.png b/app/assets/images/emoji/feelsgood.png new file mode 100755 index 00000000..361f969b Binary files /dev/null and b/app/assets/images/emoji/feelsgood.png differ diff --git a/app/assets/images/emoji/feet.png b/app/assets/images/emoji/feet.png new file mode 100755 index 00000000..1b0147b1 Binary files /dev/null and b/app/assets/images/emoji/feet.png differ diff --git a/app/assets/images/emoji/ferris_wheel.png b/app/assets/images/emoji/ferris_wheel.png new file mode 100755 index 00000000..54a1dcfa Binary files /dev/null and b/app/assets/images/emoji/ferris_wheel.png differ diff --git a/app/assets/images/emoji/file_folder.png b/app/assets/images/emoji/file_folder.png new file mode 100755 index 00000000..4d8bebf8 Binary files /dev/null and b/app/assets/images/emoji/file_folder.png differ diff --git a/app/assets/images/emoji/finnadie.png b/app/assets/images/emoji/finnadie.png new file mode 100755 index 00000000..bfc5a0d9 Binary files /dev/null and b/app/assets/images/emoji/finnadie.png differ diff --git a/app/assets/images/emoji/fire.png b/app/assets/images/emoji/fire.png new file mode 100755 index 00000000..f2a3149b Binary files /dev/null and b/app/assets/images/emoji/fire.png differ diff --git a/app/assets/images/emoji/fire_engine.png b/app/assets/images/emoji/fire_engine.png new file mode 100755 index 00000000..9e6c59c9 Binary files /dev/null and b/app/assets/images/emoji/fire_engine.png differ diff --git a/app/assets/images/emoji/fireworks.png b/app/assets/images/emoji/fireworks.png new file mode 100755 index 00000000..b4eccd57 Binary files /dev/null and b/app/assets/images/emoji/fireworks.png differ diff --git a/app/assets/images/emoji/first_quarter_moon.png b/app/assets/images/emoji/first_quarter_moon.png new file mode 100755 index 00000000..f38c2369 Binary files /dev/null and b/app/assets/images/emoji/first_quarter_moon.png differ diff --git a/app/assets/images/emoji/first_quarter_moon_with_face.png b/app/assets/images/emoji/first_quarter_moon_with_face.png new file mode 100755 index 00000000..85ae2ce7 Binary files /dev/null and b/app/assets/images/emoji/first_quarter_moon_with_face.png differ diff --git a/app/assets/images/emoji/fish.png b/app/assets/images/emoji/fish.png new file mode 100755 index 00000000..90bdda2c Binary files /dev/null and b/app/assets/images/emoji/fish.png differ diff --git a/app/assets/images/emoji/fish_cake.png b/app/assets/images/emoji/fish_cake.png new file mode 100755 index 00000000..a8f22614 Binary files /dev/null and b/app/assets/images/emoji/fish_cake.png differ diff --git a/app/assets/images/emoji/fishing_pole_and_fish.png b/app/assets/images/emoji/fishing_pole_and_fish.png new file mode 100755 index 00000000..d84609c3 Binary files /dev/null and b/app/assets/images/emoji/fishing_pole_and_fish.png differ diff --git a/app/assets/images/emoji/fist.png b/app/assets/images/emoji/fist.png new file mode 100755 index 00000000..ecc8874c Binary files /dev/null and b/app/assets/images/emoji/fist.png differ diff --git a/app/assets/images/emoji/five.png b/app/assets/images/emoji/five.png new file mode 100755 index 00000000..794321aa Binary files /dev/null and b/app/assets/images/emoji/five.png differ diff --git a/app/assets/images/emoji/flags.png b/app/assets/images/emoji/flags.png new file mode 100755 index 00000000..540164e8 Binary files /dev/null and b/app/assets/images/emoji/flags.png differ diff --git a/app/assets/images/emoji/flashlight.png b/app/assets/images/emoji/flashlight.png new file mode 100755 index 00000000..215940aa Binary files /dev/null and b/app/assets/images/emoji/flashlight.png differ diff --git a/app/assets/images/emoji/floppy_disk.png b/app/assets/images/emoji/floppy_disk.png new file mode 100755 index 00000000..4ad56315 Binary files /dev/null and b/app/assets/images/emoji/floppy_disk.png differ diff --git a/app/assets/images/emoji/flower_playing_cards.png b/app/assets/images/emoji/flower_playing_cards.png new file mode 100755 index 00000000..cc46a6a1 Binary files /dev/null and b/app/assets/images/emoji/flower_playing_cards.png differ diff --git a/app/assets/images/emoji/flushed.png b/app/assets/images/emoji/flushed.png new file mode 100755 index 00000000..74b78c9c Binary files /dev/null and b/app/assets/images/emoji/flushed.png differ diff --git a/app/assets/images/emoji/foggy.png b/app/assets/images/emoji/foggy.png new file mode 100755 index 00000000..3c7b8b04 Binary files /dev/null and b/app/assets/images/emoji/foggy.png differ diff --git a/app/assets/images/emoji/football.png b/app/assets/images/emoji/football.png new file mode 100755 index 00000000..0e4e168f Binary files /dev/null and b/app/assets/images/emoji/football.png differ diff --git a/app/assets/images/emoji/fork_and_knife.png b/app/assets/images/emoji/fork_and_knife.png new file mode 100755 index 00000000..8ba4bc65 Binary files /dev/null and b/app/assets/images/emoji/fork_and_knife.png differ diff --git a/app/assets/images/emoji/fountain.png b/app/assets/images/emoji/fountain.png new file mode 100755 index 00000000..da126e64 Binary files /dev/null and b/app/assets/images/emoji/fountain.png differ diff --git a/app/assets/images/emoji/four.png b/app/assets/images/emoji/four.png new file mode 100755 index 00000000..14782ba2 Binary files /dev/null and b/app/assets/images/emoji/four.png differ diff --git a/app/assets/images/emoji/four_leaf_clover.png b/app/assets/images/emoji/four_leaf_clover.png new file mode 100755 index 00000000..f2014bea Binary files /dev/null and b/app/assets/images/emoji/four_leaf_clover.png differ diff --git a/app/assets/images/emoji/fr.png b/app/assets/images/emoji/fr.png new file mode 100755 index 00000000..6311c911 Binary files /dev/null and b/app/assets/images/emoji/fr.png differ diff --git a/app/assets/images/emoji/free.png b/app/assets/images/emoji/free.png new file mode 100755 index 00000000..c886cf24 Binary files /dev/null and b/app/assets/images/emoji/free.png differ diff --git a/app/assets/images/emoji/fried_shrimp.png b/app/assets/images/emoji/fried_shrimp.png new file mode 100755 index 00000000..c8c284bf Binary files /dev/null and b/app/assets/images/emoji/fried_shrimp.png differ diff --git a/app/assets/images/emoji/fries.png b/app/assets/images/emoji/fries.png new file mode 100755 index 00000000..cfef6696 Binary files /dev/null and b/app/assets/images/emoji/fries.png differ diff --git a/app/assets/images/emoji/frog.png b/app/assets/images/emoji/frog.png new file mode 100755 index 00000000..cfe11b18 Binary files /dev/null and b/app/assets/images/emoji/frog.png differ diff --git a/app/assets/images/emoji/fuelpump.png b/app/assets/images/emoji/fuelpump.png new file mode 100755 index 00000000..54c29aeb Binary files /dev/null and b/app/assets/images/emoji/fuelpump.png differ diff --git a/app/assets/images/emoji/full_moon.png b/app/assets/images/emoji/full_moon.png new file mode 100755 index 00000000..8ff657a2 Binary files /dev/null and b/app/assets/images/emoji/full_moon.png differ diff --git a/app/assets/images/emoji/full_moon_with_face.png b/app/assets/images/emoji/full_moon_with_face.png new file mode 100755 index 00000000..94395a40 Binary files /dev/null and b/app/assets/images/emoji/full_moon_with_face.png differ diff --git a/app/assets/images/emoji/game_die.png b/app/assets/images/emoji/game_die.png new file mode 100755 index 00000000..4136e78e Binary files /dev/null and b/app/assets/images/emoji/game_die.png differ diff --git a/app/assets/images/emoji/gb.png b/app/assets/images/emoji/gb.png new file mode 100755 index 00000000..2a62c7a0 Binary files /dev/null and b/app/assets/images/emoji/gb.png differ diff --git a/app/assets/images/emoji/gem.png b/app/assets/images/emoji/gem.png new file mode 100755 index 00000000..8a5d8dad Binary files /dev/null and b/app/assets/images/emoji/gem.png differ diff --git a/app/assets/images/emoji/gemini.png b/app/assets/images/emoji/gemini.png new file mode 100755 index 00000000..d926f6e8 Binary files /dev/null and b/app/assets/images/emoji/gemini.png differ diff --git a/app/assets/images/emoji/ghost.png b/app/assets/images/emoji/ghost.png new file mode 100755 index 00000000..671dd0c9 Binary files /dev/null and b/app/assets/images/emoji/ghost.png differ diff --git a/app/assets/images/emoji/gift.png b/app/assets/images/emoji/gift.png new file mode 100755 index 00000000..552cfdc2 Binary files /dev/null and b/app/assets/images/emoji/gift.png differ diff --git a/app/assets/images/emoji/gift_heart.png b/app/assets/images/emoji/gift_heart.png new file mode 100755 index 00000000..f31c26a3 Binary files /dev/null and b/app/assets/images/emoji/gift_heart.png differ diff --git a/app/assets/images/emoji/girl.png b/app/assets/images/emoji/girl.png new file mode 100755 index 00000000..ea412694 Binary files /dev/null and b/app/assets/images/emoji/girl.png differ diff --git a/app/assets/images/emoji/globe_with_meridians.png b/app/assets/images/emoji/globe_with_meridians.png new file mode 100755 index 00000000..b1986466 Binary files /dev/null and b/app/assets/images/emoji/globe_with_meridians.png differ diff --git a/app/assets/images/emoji/goat.png b/app/assets/images/emoji/goat.png new file mode 100755 index 00000000..4be9cf30 Binary files /dev/null and b/app/assets/images/emoji/goat.png differ diff --git a/app/assets/images/emoji/goberserk.png b/app/assets/images/emoji/goberserk.png new file mode 100755 index 00000000..59a742aa Binary files /dev/null and b/app/assets/images/emoji/goberserk.png differ diff --git a/app/assets/images/emoji/godmode.png b/app/assets/images/emoji/godmode.png new file mode 100755 index 00000000..7e75ab20 Binary files /dev/null and b/app/assets/images/emoji/godmode.png differ diff --git a/app/assets/images/emoji/golf.png b/app/assets/images/emoji/golf.png new file mode 100755 index 00000000..cba2116a Binary files /dev/null and b/app/assets/images/emoji/golf.png differ diff --git a/app/assets/images/emoji/grapes.png b/app/assets/images/emoji/grapes.png new file mode 100755 index 00000000..0f9f007a Binary files /dev/null and b/app/assets/images/emoji/grapes.png differ diff --git a/app/assets/images/emoji/green_apple.png b/app/assets/images/emoji/green_apple.png new file mode 100755 index 00000000..337205cd Binary files /dev/null and b/app/assets/images/emoji/green_apple.png differ diff --git a/app/assets/images/emoji/green_book.png b/app/assets/images/emoji/green_book.png new file mode 100755 index 00000000..e86651e5 Binary files /dev/null and b/app/assets/images/emoji/green_book.png differ diff --git a/app/assets/images/emoji/green_heart.png b/app/assets/images/emoji/green_heart.png new file mode 100755 index 00000000..7289cb81 Binary files /dev/null and b/app/assets/images/emoji/green_heart.png differ diff --git a/app/assets/images/emoji/grey_exclamation.png b/app/assets/images/emoji/grey_exclamation.png new file mode 100755 index 00000000..a50d265e Binary files /dev/null and b/app/assets/images/emoji/grey_exclamation.png differ diff --git a/app/assets/images/emoji/grey_question.png b/app/assets/images/emoji/grey_question.png new file mode 100755 index 00000000..fb97ba75 Binary files /dev/null and b/app/assets/images/emoji/grey_question.png differ diff --git a/app/assets/images/emoji/grin.png b/app/assets/images/emoji/grin.png new file mode 100755 index 00000000..591cfcef Binary files /dev/null and b/app/assets/images/emoji/grin.png differ diff --git a/app/assets/images/emoji/guardsman.png b/app/assets/images/emoji/guardsman.png new file mode 100755 index 00000000..b67b335d Binary files /dev/null and b/app/assets/images/emoji/guardsman.png differ diff --git a/app/assets/images/emoji/guitar.png b/app/assets/images/emoji/guitar.png new file mode 100755 index 00000000..2b7fa43c Binary files /dev/null and b/app/assets/images/emoji/guitar.png differ diff --git a/app/assets/images/emoji/gun.png b/app/assets/images/emoji/gun.png new file mode 100755 index 00000000..c49dc52c Binary files /dev/null and b/app/assets/images/emoji/gun.png differ diff --git a/app/assets/images/emoji/haircut.png b/app/assets/images/emoji/haircut.png new file mode 100755 index 00000000..902d273f Binary files /dev/null and b/app/assets/images/emoji/haircut.png differ diff --git a/app/assets/images/emoji/hamburger.png b/app/assets/images/emoji/hamburger.png new file mode 100755 index 00000000..9f1a3fdf Binary files /dev/null and b/app/assets/images/emoji/hamburger.png differ diff --git a/app/assets/images/emoji/hammer.png b/app/assets/images/emoji/hammer.png new file mode 100755 index 00000000..482b1c74 Binary files /dev/null and b/app/assets/images/emoji/hammer.png differ diff --git a/app/assets/images/emoji/hamster.png b/app/assets/images/emoji/hamster.png new file mode 100755 index 00000000..addfd2e6 Binary files /dev/null and b/app/assets/images/emoji/hamster.png differ diff --git a/app/assets/images/emoji/hand.png b/app/assets/images/emoji/hand.png new file mode 100755 index 00000000..5e45c25a Binary files /dev/null and b/app/assets/images/emoji/hand.png differ diff --git a/app/assets/images/emoji/handbag.png b/app/assets/images/emoji/handbag.png new file mode 100755 index 00000000..d7adf04d Binary files /dev/null and b/app/assets/images/emoji/handbag.png differ diff --git a/app/assets/images/emoji/hankey.png b/app/assets/images/emoji/hankey.png new file mode 100755 index 00000000..73a4dc84 Binary files /dev/null and b/app/assets/images/emoji/hankey.png differ diff --git a/app/assets/images/emoji/hash.png b/app/assets/images/emoji/hash.png new file mode 100755 index 00000000..6765d7d3 Binary files /dev/null and b/app/assets/images/emoji/hash.png differ diff --git a/app/assets/images/emoji/hatched_chick.png b/app/assets/images/emoji/hatched_chick.png new file mode 100755 index 00000000..39c25bc7 Binary files /dev/null and b/app/assets/images/emoji/hatched_chick.png differ diff --git a/app/assets/images/emoji/hatching_chick.png b/app/assets/images/emoji/hatching_chick.png new file mode 100755 index 00000000..005a5551 Binary files /dev/null and b/app/assets/images/emoji/hatching_chick.png differ diff --git a/app/assets/images/emoji/headphones.png b/app/assets/images/emoji/headphones.png new file mode 100755 index 00000000..ad83000e Binary files /dev/null and b/app/assets/images/emoji/headphones.png differ diff --git a/app/assets/images/emoji/hear_no_evil.png b/app/assets/images/emoji/hear_no_evil.png new file mode 100755 index 00000000..f97a1f9a Binary files /dev/null and b/app/assets/images/emoji/hear_no_evil.png differ diff --git a/app/assets/images/emoji/heart.png b/app/assets/images/emoji/heart.png new file mode 100755 index 00000000..7d7790ce Binary files /dev/null and b/app/assets/images/emoji/heart.png differ diff --git a/app/assets/images/emoji/heart_decoration.png b/app/assets/images/emoji/heart_decoration.png new file mode 100755 index 00000000..b8be44db Binary files /dev/null and b/app/assets/images/emoji/heart_decoration.png differ diff --git a/app/assets/images/emoji/heart_eyes.png b/app/assets/images/emoji/heart_eyes.png new file mode 100755 index 00000000..0e579427 Binary files /dev/null and b/app/assets/images/emoji/heart_eyes.png differ diff --git a/app/assets/images/emoji/heart_eyes_cat.png b/app/assets/images/emoji/heart_eyes_cat.png new file mode 100755 index 00000000..eeba240e Binary files /dev/null and b/app/assets/images/emoji/heart_eyes_cat.png differ diff --git a/app/assets/images/emoji/heartbeat.png b/app/assets/images/emoji/heartbeat.png new file mode 100755 index 00000000..b6628f6f Binary files /dev/null and b/app/assets/images/emoji/heartbeat.png differ diff --git a/app/assets/images/emoji/heartpulse.png b/app/assets/images/emoji/heartpulse.png new file mode 100755 index 00000000..a7491cbe Binary files /dev/null and b/app/assets/images/emoji/heartpulse.png differ diff --git a/app/assets/images/emoji/hearts.png b/app/assets/images/emoji/hearts.png new file mode 100755 index 00000000..e8947153 Binary files /dev/null and b/app/assets/images/emoji/hearts.png differ diff --git a/app/assets/images/emoji/heavy_check_mark.png b/app/assets/images/emoji/heavy_check_mark.png new file mode 100755 index 00000000..336d2626 Binary files /dev/null and b/app/assets/images/emoji/heavy_check_mark.png differ diff --git a/app/assets/images/emoji/heavy_division_sign.png b/app/assets/images/emoji/heavy_division_sign.png new file mode 100755 index 00000000..ac757a23 Binary files /dev/null and b/app/assets/images/emoji/heavy_division_sign.png differ diff --git a/app/assets/images/emoji/heavy_dollar_sign.png b/app/assets/images/emoji/heavy_dollar_sign.png new file mode 100755 index 00000000..361e26ae Binary files /dev/null and b/app/assets/images/emoji/heavy_dollar_sign.png differ diff --git a/app/assets/images/emoji/heavy_exclamation_mark.png b/app/assets/images/emoji/heavy_exclamation_mark.png new file mode 100755 index 00000000..4c560f5e Binary files /dev/null and b/app/assets/images/emoji/heavy_exclamation_mark.png differ diff --git a/app/assets/images/emoji/heavy_minus_sign.png b/app/assets/images/emoji/heavy_minus_sign.png new file mode 100755 index 00000000..b8d3d82f Binary files /dev/null and b/app/assets/images/emoji/heavy_minus_sign.png differ diff --git a/app/assets/images/emoji/heavy_multiplication_x.png b/app/assets/images/emoji/heavy_multiplication_x.png new file mode 100755 index 00000000..13d66607 Binary files /dev/null and b/app/assets/images/emoji/heavy_multiplication_x.png differ diff --git a/app/assets/images/emoji/heavy_plus_sign.png b/app/assets/images/emoji/heavy_plus_sign.png new file mode 100755 index 00000000..61595387 Binary files /dev/null and b/app/assets/images/emoji/heavy_plus_sign.png differ diff --git a/app/assets/images/emoji/helicopter.png b/app/assets/images/emoji/helicopter.png new file mode 100755 index 00000000..8e82a0d5 Binary files /dev/null and b/app/assets/images/emoji/helicopter.png differ diff --git a/app/assets/images/emoji/herb.png b/app/assets/images/emoji/herb.png new file mode 100755 index 00000000..de1ff1b7 Binary files /dev/null and b/app/assets/images/emoji/herb.png differ diff --git a/app/assets/images/emoji/hibiscus.png b/app/assets/images/emoji/hibiscus.png new file mode 100755 index 00000000..9365ae21 Binary files /dev/null and b/app/assets/images/emoji/hibiscus.png differ diff --git a/app/assets/images/emoji/high_brightness.png b/app/assets/images/emoji/high_brightness.png new file mode 100755 index 00000000..ba9de7d4 Binary files /dev/null and b/app/assets/images/emoji/high_brightness.png differ diff --git a/app/assets/images/emoji/high_heel.png b/app/assets/images/emoji/high_heel.png new file mode 100755 index 00000000..525b6a0d Binary files /dev/null and b/app/assets/images/emoji/high_heel.png differ diff --git a/app/assets/images/emoji/hocho.png b/app/assets/images/emoji/hocho.png new file mode 100755 index 00000000..18eade0a Binary files /dev/null and b/app/assets/images/emoji/hocho.png differ diff --git a/app/assets/images/emoji/honey_pot.png b/app/assets/images/emoji/honey_pot.png new file mode 100755 index 00000000..73278898 Binary files /dev/null and b/app/assets/images/emoji/honey_pot.png differ diff --git a/app/assets/images/emoji/honeybee.png b/app/assets/images/emoji/honeybee.png new file mode 100755 index 00000000..f5373395 Binary files /dev/null and b/app/assets/images/emoji/honeybee.png differ diff --git a/app/assets/images/emoji/horse.png b/app/assets/images/emoji/horse.png new file mode 100755 index 00000000..78d580ad Binary files /dev/null and b/app/assets/images/emoji/horse.png differ diff --git a/app/assets/images/emoji/horse_racing.png b/app/assets/images/emoji/horse_racing.png new file mode 100755 index 00000000..e3bbaec1 Binary files /dev/null and b/app/assets/images/emoji/horse_racing.png differ diff --git a/app/assets/images/emoji/hospital.png b/app/assets/images/emoji/hospital.png new file mode 100755 index 00000000..c05c4937 Binary files /dev/null and b/app/assets/images/emoji/hospital.png differ diff --git a/app/assets/images/emoji/hotel.png b/app/assets/images/emoji/hotel.png new file mode 100755 index 00000000..d29f276a Binary files /dev/null and b/app/assets/images/emoji/hotel.png differ diff --git a/app/assets/images/emoji/hotsprings.png b/app/assets/images/emoji/hotsprings.png new file mode 100755 index 00000000..a0bc9d75 Binary files /dev/null and b/app/assets/images/emoji/hotsprings.png differ diff --git a/app/assets/images/emoji/hourglass.png b/app/assets/images/emoji/hourglass.png new file mode 100755 index 00000000..405aab41 Binary files /dev/null and b/app/assets/images/emoji/hourglass.png differ diff --git a/app/assets/images/emoji/house.png b/app/assets/images/emoji/house.png new file mode 100755 index 00000000..95b9ee09 Binary files /dev/null and b/app/assets/images/emoji/house.png differ diff --git a/app/assets/images/emoji/hurtrealbad.png b/app/assets/images/emoji/hurtrealbad.png new file mode 100755 index 00000000..146ef1a6 Binary files /dev/null and b/app/assets/images/emoji/hurtrealbad.png differ diff --git a/app/assets/images/emoji/ice_cream.png b/app/assets/images/emoji/ice_cream.png new file mode 100755 index 00000000..190be016 Binary files /dev/null and b/app/assets/images/emoji/ice_cream.png differ diff --git a/app/assets/images/emoji/icecream.png b/app/assets/images/emoji/icecream.png new file mode 100755 index 00000000..871ce097 Binary files /dev/null and b/app/assets/images/emoji/icecream.png differ diff --git a/app/assets/images/emoji/id.png b/app/assets/images/emoji/id.png new file mode 100755 index 00000000..47437a76 Binary files /dev/null and b/app/assets/images/emoji/id.png differ diff --git a/app/assets/images/emoji/ideograph_advantage.png b/app/assets/images/emoji/ideograph_advantage.png new file mode 100755 index 00000000..e79af784 Binary files /dev/null and b/app/assets/images/emoji/ideograph_advantage.png differ diff --git a/app/assets/images/emoji/imp.png b/app/assets/images/emoji/imp.png new file mode 100755 index 00000000..5acca337 Binary files /dev/null and b/app/assets/images/emoji/imp.png differ diff --git a/app/assets/images/emoji/inbox_tray.png b/app/assets/images/emoji/inbox_tray.png new file mode 100755 index 00000000..e2df0f89 Binary files /dev/null and b/app/assets/images/emoji/inbox_tray.png differ diff --git a/app/assets/images/emoji/incoming_envelope.png b/app/assets/images/emoji/incoming_envelope.png new file mode 100755 index 00000000..afc82712 Binary files /dev/null and b/app/assets/images/emoji/incoming_envelope.png differ diff --git a/app/assets/images/emoji/information_desk_person.png b/app/assets/images/emoji/information_desk_person.png new file mode 100755 index 00000000..52c0a50a Binary files /dev/null and b/app/assets/images/emoji/information_desk_person.png differ diff --git a/app/assets/images/emoji/information_source.png b/app/assets/images/emoji/information_source.png new file mode 100755 index 00000000..9cb8b09b Binary files /dev/null and b/app/assets/images/emoji/information_source.png differ diff --git a/app/assets/images/emoji/innocent.png b/app/assets/images/emoji/innocent.png new file mode 100755 index 00000000..503b614f Binary files /dev/null and b/app/assets/images/emoji/innocent.png differ diff --git a/app/assets/images/emoji/interrobang.png b/app/assets/images/emoji/interrobang.png new file mode 100755 index 00000000..64304b9f Binary files /dev/null and b/app/assets/images/emoji/interrobang.png differ diff --git a/app/assets/images/emoji/iphone.png b/app/assets/images/emoji/iphone.png new file mode 100755 index 00000000..df007103 Binary files /dev/null and b/app/assets/images/emoji/iphone.png differ diff --git a/app/assets/images/emoji/it.png b/app/assets/images/emoji/it.png new file mode 100755 index 00000000..70bc9f32 Binary files /dev/null and b/app/assets/images/emoji/it.png differ diff --git a/app/assets/images/emoji/izakaya_lantern.png b/app/assets/images/emoji/izakaya_lantern.png new file mode 100755 index 00000000..18730ad5 Binary files /dev/null and b/app/assets/images/emoji/izakaya_lantern.png differ diff --git a/app/assets/images/emoji/jack_o_lantern.png b/app/assets/images/emoji/jack_o_lantern.png new file mode 100755 index 00000000..1f7667ea Binary files /dev/null and b/app/assets/images/emoji/jack_o_lantern.png differ diff --git a/app/assets/images/emoji/japan.png b/app/assets/images/emoji/japan.png new file mode 100755 index 00000000..45932803 Binary files /dev/null and b/app/assets/images/emoji/japan.png differ diff --git a/app/assets/images/emoji/japanese_castle.png b/app/assets/images/emoji/japanese_castle.png new file mode 100755 index 00000000..f225ab21 Binary files /dev/null and b/app/assets/images/emoji/japanese_castle.png differ diff --git a/app/assets/images/emoji/japanese_goblin.png b/app/assets/images/emoji/japanese_goblin.png new file mode 100755 index 00000000..bd21b187 Binary files /dev/null and b/app/assets/images/emoji/japanese_goblin.png differ diff --git a/app/assets/images/emoji/japanese_ogre.png b/app/assets/images/emoji/japanese_ogre.png new file mode 100755 index 00000000..e9f5471c Binary files /dev/null and b/app/assets/images/emoji/japanese_ogre.png differ diff --git a/app/assets/images/emoji/jeans.png b/app/assets/images/emoji/jeans.png new file mode 100755 index 00000000..d721cea5 Binary files /dev/null and b/app/assets/images/emoji/jeans.png differ diff --git a/app/assets/images/emoji/joy.png b/app/assets/images/emoji/joy.png new file mode 100755 index 00000000..47df693d Binary files /dev/null and b/app/assets/images/emoji/joy.png differ diff --git a/app/assets/images/emoji/joy_cat.png b/app/assets/images/emoji/joy_cat.png new file mode 100755 index 00000000..6c60cb0e Binary files /dev/null and b/app/assets/images/emoji/joy_cat.png differ diff --git a/app/assets/images/emoji/jp.png b/app/assets/images/emoji/jp.png new file mode 100755 index 00000000..b786efbb Binary files /dev/null and b/app/assets/images/emoji/jp.png differ diff --git a/app/assets/images/emoji/key.png b/app/assets/images/emoji/key.png new file mode 100755 index 00000000..34673213 Binary files /dev/null and b/app/assets/images/emoji/key.png differ diff --git a/app/assets/images/emoji/keycap_ten.png b/app/assets/images/emoji/keycap_ten.png new file mode 100755 index 00000000..71dac1c1 Binary files /dev/null and b/app/assets/images/emoji/keycap_ten.png differ diff --git a/app/assets/images/emoji/kimono.png b/app/assets/images/emoji/kimono.png new file mode 100755 index 00000000..34ffe137 Binary files /dev/null and b/app/assets/images/emoji/kimono.png differ diff --git a/app/assets/images/emoji/kiss.png b/app/assets/images/emoji/kiss.png new file mode 100755 index 00000000..14fd9918 Binary files /dev/null and b/app/assets/images/emoji/kiss.png differ diff --git a/app/assets/images/emoji/kissing_cat.png b/app/assets/images/emoji/kissing_cat.png new file mode 100755 index 00000000..adc62fbe Binary files /dev/null and b/app/assets/images/emoji/kissing_cat.png differ diff --git a/app/assets/images/emoji/kissing_face.png b/app/assets/images/emoji/kissing_face.png new file mode 100755 index 00000000..449de197 Binary files /dev/null and b/app/assets/images/emoji/kissing_face.png differ diff --git a/app/assets/images/emoji/kissing_heart.png b/app/assets/images/emoji/kissing_heart.png new file mode 100755 index 00000000..af9a80b7 Binary files /dev/null and b/app/assets/images/emoji/kissing_heart.png differ diff --git a/app/assets/images/emoji/koala.png b/app/assets/images/emoji/koala.png new file mode 100755 index 00000000..e17bd3cf Binary files /dev/null and b/app/assets/images/emoji/koala.png differ diff --git a/app/assets/images/emoji/koko.png b/app/assets/images/emoji/koko.png new file mode 100755 index 00000000..3bef28c9 Binary files /dev/null and b/app/assets/images/emoji/koko.png differ diff --git a/app/assets/images/emoji/kr.png b/app/assets/images/emoji/kr.png new file mode 100755 index 00000000..b4c0c1b6 Binary files /dev/null and b/app/assets/images/emoji/kr.png differ diff --git a/app/assets/images/emoji/large_blue_circle.png b/app/assets/images/emoji/large_blue_circle.png new file mode 100755 index 00000000..a5b4ad4a Binary files /dev/null and b/app/assets/images/emoji/large_blue_circle.png differ diff --git a/app/assets/images/emoji/large_blue_diamond.png b/app/assets/images/emoji/large_blue_diamond.png new file mode 100755 index 00000000..f4598ec0 Binary files /dev/null and b/app/assets/images/emoji/large_blue_diamond.png differ diff --git a/app/assets/images/emoji/large_orange_diamond.png b/app/assets/images/emoji/large_orange_diamond.png new file mode 100755 index 00000000..46d52e5c Binary files /dev/null and b/app/assets/images/emoji/large_orange_diamond.png differ diff --git a/app/assets/images/emoji/last_quarter_moon.png b/app/assets/images/emoji/last_quarter_moon.png new file mode 100755 index 00000000..355e3c3f Binary files /dev/null and b/app/assets/images/emoji/last_quarter_moon.png differ diff --git a/app/assets/images/emoji/last_quarter_moon_with_face.png b/app/assets/images/emoji/last_quarter_moon_with_face.png new file mode 100755 index 00000000..9ece82df Binary files /dev/null and b/app/assets/images/emoji/last_quarter_moon_with_face.png differ diff --git a/app/assets/images/emoji/laughing.png b/app/assets/images/emoji/laughing.png new file mode 100755 index 00000000..11c91eb2 Binary files /dev/null and b/app/assets/images/emoji/laughing.png differ diff --git a/app/assets/images/emoji/leaves.png b/app/assets/images/emoji/leaves.png new file mode 100755 index 00000000..5229e06b Binary files /dev/null and b/app/assets/images/emoji/leaves.png differ diff --git a/app/assets/images/emoji/ledger.png b/app/assets/images/emoji/ledger.png new file mode 100755 index 00000000..e4f72ace Binary files /dev/null and b/app/assets/images/emoji/ledger.png differ diff --git a/app/assets/images/emoji/left_luggage.png b/app/assets/images/emoji/left_luggage.png new file mode 100755 index 00000000..1c08b464 Binary files /dev/null and b/app/assets/images/emoji/left_luggage.png differ diff --git a/app/assets/images/emoji/left_right_arrow.png b/app/assets/images/emoji/left_right_arrow.png new file mode 100755 index 00000000..b9fd11c5 Binary files /dev/null and b/app/assets/images/emoji/left_right_arrow.png differ diff --git a/app/assets/images/emoji/leftwards_arrow_with_hook.png b/app/assets/images/emoji/leftwards_arrow_with_hook.png new file mode 100755 index 00000000..bc45dfef Binary files /dev/null and b/app/assets/images/emoji/leftwards_arrow_with_hook.png differ diff --git a/app/assets/images/emoji/lemon.png b/app/assets/images/emoji/lemon.png new file mode 100755 index 00000000..9814dc95 Binary files /dev/null and b/app/assets/images/emoji/lemon.png differ diff --git a/app/assets/images/emoji/leo.png b/app/assets/images/emoji/leo.png new file mode 100755 index 00000000..e025933b Binary files /dev/null and b/app/assets/images/emoji/leo.png differ diff --git a/app/assets/images/emoji/leopard.png b/app/assets/images/emoji/leopard.png new file mode 100755 index 00000000..8abfc4a2 Binary files /dev/null and b/app/assets/images/emoji/leopard.png differ diff --git a/app/assets/images/emoji/libra.png b/app/assets/images/emoji/libra.png new file mode 100755 index 00000000..c9062dd2 Binary files /dev/null and b/app/assets/images/emoji/libra.png differ diff --git a/app/assets/images/emoji/light_rail.png b/app/assets/images/emoji/light_rail.png new file mode 100755 index 00000000..bcfe801e Binary files /dev/null and b/app/assets/images/emoji/light_rail.png differ diff --git a/app/assets/images/emoji/link.png b/app/assets/images/emoji/link.png new file mode 100755 index 00000000..ffb8f62c Binary files /dev/null and b/app/assets/images/emoji/link.png differ diff --git a/app/assets/images/emoji/lips.png b/app/assets/images/emoji/lips.png new file mode 100755 index 00000000..826ed110 Binary files /dev/null and b/app/assets/images/emoji/lips.png differ diff --git a/app/assets/images/emoji/lipstick.png b/app/assets/images/emoji/lipstick.png new file mode 100755 index 00000000..82f990c5 Binary files /dev/null and b/app/assets/images/emoji/lipstick.png differ diff --git a/app/assets/images/emoji/lock.png b/app/assets/images/emoji/lock.png new file mode 100755 index 00000000..4892b023 Binary files /dev/null and b/app/assets/images/emoji/lock.png differ diff --git a/app/assets/images/emoji/lock_with_ink_pen.png b/app/assets/images/emoji/lock_with_ink_pen.png new file mode 100755 index 00000000..375e67e8 Binary files /dev/null and b/app/assets/images/emoji/lock_with_ink_pen.png differ diff --git a/app/assets/images/emoji/lollipop.png b/app/assets/images/emoji/lollipop.png new file mode 100755 index 00000000..ba55e709 Binary files /dev/null and b/app/assets/images/emoji/lollipop.png differ diff --git a/app/assets/images/emoji/loop.png b/app/assets/images/emoji/loop.png new file mode 100755 index 00000000..ef34df3a Binary files /dev/null and b/app/assets/images/emoji/loop.png differ diff --git a/app/assets/images/emoji/loudspeaker.png b/app/assets/images/emoji/loudspeaker.png new file mode 100755 index 00000000..752385e5 Binary files /dev/null and b/app/assets/images/emoji/loudspeaker.png differ diff --git a/app/assets/images/emoji/love_hotel.png b/app/assets/images/emoji/love_hotel.png new file mode 100755 index 00000000..44d7db82 Binary files /dev/null and b/app/assets/images/emoji/love_hotel.png differ diff --git a/app/assets/images/emoji/love_letter.png b/app/assets/images/emoji/love_letter.png new file mode 100755 index 00000000..e29981f4 Binary files /dev/null and b/app/assets/images/emoji/love_letter.png differ diff --git a/app/assets/images/emoji/low_brightness.png b/app/assets/images/emoji/low_brightness.png new file mode 100755 index 00000000..ea15bde4 Binary files /dev/null and b/app/assets/images/emoji/low_brightness.png differ diff --git a/app/assets/images/emoji/m.png b/app/assets/images/emoji/m.png new file mode 100755 index 00000000..7424665e Binary files /dev/null and b/app/assets/images/emoji/m.png differ diff --git a/app/assets/images/emoji/mag.png b/app/assets/images/emoji/mag.png new file mode 100755 index 00000000..aa5b1d7c Binary files /dev/null and b/app/assets/images/emoji/mag.png differ diff --git a/app/assets/images/emoji/mag_right.png b/app/assets/images/emoji/mag_right.png new file mode 100755 index 00000000..6e6cf11e Binary files /dev/null and b/app/assets/images/emoji/mag_right.png differ diff --git a/app/assets/images/emoji/mahjong.png b/app/assets/images/emoji/mahjong.png new file mode 100755 index 00000000..f51ce65f Binary files /dev/null and b/app/assets/images/emoji/mahjong.png differ diff --git a/app/assets/images/emoji/mailbox.png b/app/assets/images/emoji/mailbox.png new file mode 100755 index 00000000..8351e707 Binary files /dev/null and b/app/assets/images/emoji/mailbox.png differ diff --git a/app/assets/images/emoji/mailbox_closed.png b/app/assets/images/emoji/mailbox_closed.png new file mode 100755 index 00000000..a5982b69 Binary files /dev/null and b/app/assets/images/emoji/mailbox_closed.png differ diff --git a/app/assets/images/emoji/mailbox_with_mail.png b/app/assets/images/emoji/mailbox_with_mail.png new file mode 100755 index 00000000..dae34594 Binary files /dev/null and b/app/assets/images/emoji/mailbox_with_mail.png differ diff --git a/app/assets/images/emoji/mailbox_with_no_mail.png b/app/assets/images/emoji/mailbox_with_no_mail.png new file mode 100755 index 00000000..59f15c5d Binary files /dev/null and b/app/assets/images/emoji/mailbox_with_no_mail.png differ diff --git a/app/assets/images/emoji/man.png b/app/assets/images/emoji/man.png new file mode 100755 index 00000000..d9bfa26a Binary files /dev/null and b/app/assets/images/emoji/man.png differ diff --git a/app/assets/images/emoji/man_with_gua_pi_mao.png b/app/assets/images/emoji/man_with_gua_pi_mao.png new file mode 100755 index 00000000..7aad74b5 Binary files /dev/null and b/app/assets/images/emoji/man_with_gua_pi_mao.png differ diff --git a/app/assets/images/emoji/man_with_turban.png b/app/assets/images/emoji/man_with_turban.png new file mode 100755 index 00000000..036604ca Binary files /dev/null and b/app/assets/images/emoji/man_with_turban.png differ diff --git a/app/assets/images/emoji/mans_shoe.png b/app/assets/images/emoji/mans_shoe.png new file mode 100755 index 00000000..ecba9ba7 Binary files /dev/null and b/app/assets/images/emoji/mans_shoe.png differ diff --git a/app/assets/images/emoji/maple_leaf.png b/app/assets/images/emoji/maple_leaf.png new file mode 100755 index 00000000..4e9b4720 Binary files /dev/null and b/app/assets/images/emoji/maple_leaf.png differ diff --git a/app/assets/images/emoji/mask.png b/app/assets/images/emoji/mask.png new file mode 100755 index 00000000..05887e99 Binary files /dev/null and b/app/assets/images/emoji/mask.png differ diff --git a/app/assets/images/emoji/massage.png b/app/assets/images/emoji/massage.png new file mode 100755 index 00000000..dd30d159 Binary files /dev/null and b/app/assets/images/emoji/massage.png differ diff --git a/app/assets/images/emoji/meat_on_bone.png b/app/assets/images/emoji/meat_on_bone.png new file mode 100755 index 00000000..d6b311b6 Binary files /dev/null and b/app/assets/images/emoji/meat_on_bone.png differ diff --git a/app/assets/images/emoji/mega.png b/app/assets/images/emoji/mega.png new file mode 100755 index 00000000..022df2f8 Binary files /dev/null and b/app/assets/images/emoji/mega.png differ diff --git a/app/assets/images/emoji/melon.png b/app/assets/images/emoji/melon.png new file mode 100755 index 00000000..11c13cbb Binary files /dev/null and b/app/assets/images/emoji/melon.png differ diff --git a/app/assets/images/emoji/memo.png b/app/assets/images/emoji/memo.png new file mode 100755 index 00000000..fc97ddbc Binary files /dev/null and b/app/assets/images/emoji/memo.png differ diff --git a/app/assets/images/emoji/mens.png b/app/assets/images/emoji/mens.png new file mode 100755 index 00000000..abccfc9f Binary files /dev/null and b/app/assets/images/emoji/mens.png differ diff --git a/app/assets/images/emoji/metal.png b/app/assets/images/emoji/metal.png new file mode 100755 index 00000000..94f1fda2 Binary files /dev/null and b/app/assets/images/emoji/metal.png differ diff --git a/app/assets/images/emoji/metro.png b/app/assets/images/emoji/metro.png new file mode 100755 index 00000000..4acf5ab3 Binary files /dev/null and b/app/assets/images/emoji/metro.png differ diff --git a/app/assets/images/emoji/microphone.png b/app/assets/images/emoji/microphone.png new file mode 100755 index 00000000..68c74ada Binary files /dev/null and b/app/assets/images/emoji/microphone.png differ diff --git a/app/assets/images/emoji/microscope.png b/app/assets/images/emoji/microscope.png new file mode 100755 index 00000000..f11d54c0 Binary files /dev/null and b/app/assets/images/emoji/microscope.png differ diff --git a/app/assets/images/emoji/milky_way.png b/app/assets/images/emoji/milky_way.png new file mode 100755 index 00000000..901090a1 Binary files /dev/null and b/app/assets/images/emoji/milky_way.png differ diff --git a/app/assets/images/emoji/minibus.png b/app/assets/images/emoji/minibus.png new file mode 100755 index 00000000..c52cef23 Binary files /dev/null and b/app/assets/images/emoji/minibus.png differ diff --git a/app/assets/images/emoji/minidisc.png b/app/assets/images/emoji/minidisc.png new file mode 100755 index 00000000..e19cc5d0 Binary files /dev/null and b/app/assets/images/emoji/minidisc.png differ diff --git a/app/assets/images/emoji/mobile_phone_off.png b/app/assets/images/emoji/mobile_phone_off.png new file mode 100755 index 00000000..fa16c763 Binary files /dev/null and b/app/assets/images/emoji/mobile_phone_off.png differ diff --git a/app/assets/images/emoji/money_with_wings.png b/app/assets/images/emoji/money_with_wings.png new file mode 100755 index 00000000..135e3981 Binary files /dev/null and b/app/assets/images/emoji/money_with_wings.png differ diff --git a/app/assets/images/emoji/moneybag.png b/app/assets/images/emoji/moneybag.png new file mode 100755 index 00000000..5546c04b Binary files /dev/null and b/app/assets/images/emoji/moneybag.png differ diff --git a/app/assets/images/emoji/monkey.png b/app/assets/images/emoji/monkey.png new file mode 100755 index 00000000..64070359 Binary files /dev/null and b/app/assets/images/emoji/monkey.png differ diff --git a/app/assets/images/emoji/monkey_face.png b/app/assets/images/emoji/monkey_face.png new file mode 100755 index 00000000..6964cf4d Binary files /dev/null and b/app/assets/images/emoji/monkey_face.png differ diff --git a/app/assets/images/emoji/monorail.png b/app/assets/images/emoji/monorail.png new file mode 100755 index 00000000..913d3002 Binary files /dev/null and b/app/assets/images/emoji/monorail.png differ diff --git a/app/assets/images/emoji/moon.png b/app/assets/images/emoji/moon.png new file mode 100755 index 00000000..afdb450d Binary files /dev/null and b/app/assets/images/emoji/moon.png differ diff --git a/app/assets/images/emoji/mortar_board.png b/app/assets/images/emoji/mortar_board.png new file mode 100755 index 00000000..2e811b09 Binary files /dev/null and b/app/assets/images/emoji/mortar_board.png differ diff --git a/app/assets/images/emoji/mount_fuji.png b/app/assets/images/emoji/mount_fuji.png new file mode 100755 index 00000000..4c313e58 Binary files /dev/null and b/app/assets/images/emoji/mount_fuji.png differ diff --git a/app/assets/images/emoji/mountain_bicyclist.png b/app/assets/images/emoji/mountain_bicyclist.png new file mode 100755 index 00000000..b6988975 Binary files /dev/null and b/app/assets/images/emoji/mountain_bicyclist.png differ diff --git a/app/assets/images/emoji/mountain_cableway.png b/app/assets/images/emoji/mountain_cableway.png new file mode 100755 index 00000000..5688bb23 Binary files /dev/null and b/app/assets/images/emoji/mountain_cableway.png differ diff --git a/app/assets/images/emoji/mountain_railway.png b/app/assets/images/emoji/mountain_railway.png new file mode 100755 index 00000000..1f3d1aab Binary files /dev/null and b/app/assets/images/emoji/mountain_railway.png differ diff --git a/app/assets/images/emoji/mouse.png b/app/assets/images/emoji/mouse.png new file mode 100755 index 00000000..8ff162e2 Binary files /dev/null and b/app/assets/images/emoji/mouse.png differ diff --git a/app/assets/images/emoji/mouse2.png b/app/assets/images/emoji/mouse2.png new file mode 100755 index 00000000..2d777e5e Binary files /dev/null and b/app/assets/images/emoji/mouse2.png differ diff --git a/app/assets/images/emoji/movie_camera.png b/app/assets/images/emoji/movie_camera.png new file mode 100755 index 00000000..9c143840 Binary files /dev/null and b/app/assets/images/emoji/movie_camera.png differ diff --git a/app/assets/images/emoji/moyai.png b/app/assets/images/emoji/moyai.png new file mode 100755 index 00000000..61a1a9c2 Binary files /dev/null and b/app/assets/images/emoji/moyai.png differ diff --git a/app/assets/images/emoji/muscle.png b/app/assets/images/emoji/muscle.png new file mode 100755 index 00000000..19f92efb Binary files /dev/null and b/app/assets/images/emoji/muscle.png differ diff --git a/app/assets/images/emoji/mushroom.png b/app/assets/images/emoji/mushroom.png new file mode 100755 index 00000000..5eeed8e7 Binary files /dev/null and b/app/assets/images/emoji/mushroom.png differ diff --git a/app/assets/images/emoji/musical_keyboard.png b/app/assets/images/emoji/musical_keyboard.png new file mode 100755 index 00000000..93647a4a Binary files /dev/null and b/app/assets/images/emoji/musical_keyboard.png differ diff --git a/app/assets/images/emoji/musical_note.png b/app/assets/images/emoji/musical_note.png new file mode 100755 index 00000000..68b261bc Binary files /dev/null and b/app/assets/images/emoji/musical_note.png differ diff --git a/app/assets/images/emoji/musical_score.png b/app/assets/images/emoji/musical_score.png new file mode 100755 index 00000000..0c927d32 Binary files /dev/null and b/app/assets/images/emoji/musical_score.png differ diff --git a/app/assets/images/emoji/mute.png b/app/assets/images/emoji/mute.png new file mode 100755 index 00000000..4cf67c36 Binary files /dev/null and b/app/assets/images/emoji/mute.png differ diff --git a/app/assets/images/emoji/nail_care.png b/app/assets/images/emoji/nail_care.png new file mode 100755 index 00000000..6a66e63d Binary files /dev/null and b/app/assets/images/emoji/nail_care.png differ diff --git a/app/assets/images/emoji/name_badge.png b/app/assets/images/emoji/name_badge.png new file mode 100755 index 00000000..2b712dcd Binary files /dev/null and b/app/assets/images/emoji/name_badge.png differ diff --git a/app/assets/images/emoji/neckbeard.png b/app/assets/images/emoji/neckbeard.png new file mode 100755 index 00000000..15108fc9 Binary files /dev/null and b/app/assets/images/emoji/neckbeard.png differ diff --git a/app/assets/images/emoji/necktie.png b/app/assets/images/emoji/necktie.png new file mode 100755 index 00000000..80461c66 Binary files /dev/null and b/app/assets/images/emoji/necktie.png differ diff --git a/app/assets/images/emoji/negative_squared_cross_mark.png b/app/assets/images/emoji/negative_squared_cross_mark.png new file mode 100755 index 00000000..b47a0cec Binary files /dev/null and b/app/assets/images/emoji/negative_squared_cross_mark.png differ diff --git a/app/assets/images/emoji/neutral_face.png b/app/assets/images/emoji/neutral_face.png new file mode 100755 index 00000000..682a1ba0 Binary files /dev/null and b/app/assets/images/emoji/neutral_face.png differ diff --git a/app/assets/images/emoji/new.png b/app/assets/images/emoji/new.png new file mode 100755 index 00000000..28d1570e Binary files /dev/null and b/app/assets/images/emoji/new.png differ diff --git a/app/assets/images/emoji/new_moon.png b/app/assets/images/emoji/new_moon.png new file mode 100755 index 00000000..540239b1 Binary files /dev/null and b/app/assets/images/emoji/new_moon.png differ diff --git a/app/assets/images/emoji/new_moon_with_face.png b/app/assets/images/emoji/new_moon_with_face.png new file mode 100755 index 00000000..b9aff7a0 Binary files /dev/null and b/app/assets/images/emoji/new_moon_with_face.png differ diff --git a/app/assets/images/emoji/newspaper.png b/app/assets/images/emoji/newspaper.png new file mode 100755 index 00000000..d171394e Binary files /dev/null and b/app/assets/images/emoji/newspaper.png differ diff --git a/app/assets/images/emoji/ng.png b/app/assets/images/emoji/ng.png new file mode 100755 index 00000000..2ca180ae Binary files /dev/null and b/app/assets/images/emoji/ng.png differ diff --git a/app/assets/images/emoji/nine.png b/app/assets/images/emoji/nine.png new file mode 100755 index 00000000..8006cc90 Binary files /dev/null and b/app/assets/images/emoji/nine.png differ diff --git a/app/assets/images/emoji/no_bell.png b/app/assets/images/emoji/no_bell.png new file mode 100755 index 00000000..613b81cd Binary files /dev/null and b/app/assets/images/emoji/no_bell.png differ diff --git a/app/assets/images/emoji/no_bicycles.png b/app/assets/images/emoji/no_bicycles.png new file mode 100755 index 00000000..4b262166 Binary files /dev/null and b/app/assets/images/emoji/no_bicycles.png differ diff --git a/app/assets/images/emoji/no_entry.png b/app/assets/images/emoji/no_entry.png new file mode 100755 index 00000000..cf2086a8 Binary files /dev/null and b/app/assets/images/emoji/no_entry.png differ diff --git a/app/assets/images/emoji/no_entry_sign.png b/app/assets/images/emoji/no_entry_sign.png new file mode 100755 index 00000000..a8444d18 Binary files /dev/null and b/app/assets/images/emoji/no_entry_sign.png differ diff --git a/app/assets/images/emoji/no_good.png b/app/assets/images/emoji/no_good.png new file mode 100755 index 00000000..d459a35b Binary files /dev/null and b/app/assets/images/emoji/no_good.png differ diff --git a/app/assets/images/emoji/no_mobile_phones.png b/app/assets/images/emoji/no_mobile_phones.png new file mode 100755 index 00000000..41df57cf Binary files /dev/null and b/app/assets/images/emoji/no_mobile_phones.png differ diff --git a/app/assets/images/emoji/no_mouth.png b/app/assets/images/emoji/no_mouth.png new file mode 100755 index 00000000..d9ec7ca7 Binary files /dev/null and b/app/assets/images/emoji/no_mouth.png differ diff --git a/app/assets/images/emoji/no_pedestrians.png b/app/assets/images/emoji/no_pedestrians.png new file mode 100755 index 00000000..c35f530b Binary files /dev/null and b/app/assets/images/emoji/no_pedestrians.png differ diff --git a/app/assets/images/emoji/no_smoking.png b/app/assets/images/emoji/no_smoking.png new file mode 100755 index 00000000..5880ddfd Binary files /dev/null and b/app/assets/images/emoji/no_smoking.png differ diff --git a/app/assets/images/emoji/non-potable_water.png b/app/assets/images/emoji/non-potable_water.png new file mode 100755 index 00000000..1b29d35b Binary files /dev/null and b/app/assets/images/emoji/non-potable_water.png differ diff --git a/app/assets/images/emoji/nose.png b/app/assets/images/emoji/nose.png new file mode 100755 index 00000000..ad17c16c Binary files /dev/null and b/app/assets/images/emoji/nose.png differ diff --git a/app/assets/images/emoji/notebook.png b/app/assets/images/emoji/notebook.png new file mode 100755 index 00000000..07ea6087 Binary files /dev/null and b/app/assets/images/emoji/notebook.png differ diff --git a/app/assets/images/emoji/notebook_with_decorative_cover.png b/app/assets/images/emoji/notebook_with_decorative_cover.png new file mode 100755 index 00000000..4f3b14c8 Binary files /dev/null and b/app/assets/images/emoji/notebook_with_decorative_cover.png differ diff --git a/app/assets/images/emoji/notes.png b/app/assets/images/emoji/notes.png new file mode 100755 index 00000000..0956d6ab Binary files /dev/null and b/app/assets/images/emoji/notes.png differ diff --git a/app/assets/images/emoji/nut_and_bolt.png b/app/assets/images/emoji/nut_and_bolt.png new file mode 100755 index 00000000..bddfa72a Binary files /dev/null and b/app/assets/images/emoji/nut_and_bolt.png differ diff --git a/app/assets/images/emoji/o.png b/app/assets/images/emoji/o.png new file mode 100755 index 00000000..0ededebe Binary files /dev/null and b/app/assets/images/emoji/o.png differ diff --git a/app/assets/images/emoji/o2.png b/app/assets/images/emoji/o2.png new file mode 100755 index 00000000..d85f9fb9 Binary files /dev/null and b/app/assets/images/emoji/o2.png differ diff --git a/app/assets/images/emoji/ocean.png b/app/assets/images/emoji/ocean.png new file mode 100755 index 00000000..f8d520cd Binary files /dev/null and b/app/assets/images/emoji/ocean.png differ diff --git a/app/assets/images/emoji/octocat.png b/app/assets/images/emoji/octocat.png new file mode 100755 index 00000000..3fc20503 Binary files /dev/null and b/app/assets/images/emoji/octocat.png differ diff --git a/app/assets/images/emoji/octopus.png b/app/assets/images/emoji/octopus.png new file mode 100755 index 00000000..52ce64b4 Binary files /dev/null and b/app/assets/images/emoji/octopus.png differ diff --git a/app/assets/images/emoji/oden.png b/app/assets/images/emoji/oden.png new file mode 100755 index 00000000..73add1c7 Binary files /dev/null and b/app/assets/images/emoji/oden.png differ diff --git a/app/assets/images/emoji/office.png b/app/assets/images/emoji/office.png new file mode 100755 index 00000000..ea9281a4 Binary files /dev/null and b/app/assets/images/emoji/office.png differ diff --git a/app/assets/images/emoji/ok.png b/app/assets/images/emoji/ok.png new file mode 100755 index 00000000..6433d1a9 Binary files /dev/null and b/app/assets/images/emoji/ok.png differ diff --git a/app/assets/images/emoji/ok_hand.png b/app/assets/images/emoji/ok_hand.png new file mode 100755 index 00000000..80c5aebb Binary files /dev/null and b/app/assets/images/emoji/ok_hand.png differ diff --git a/app/assets/images/emoji/ok_woman.png b/app/assets/images/emoji/ok_woman.png new file mode 100755 index 00000000..e8b98194 Binary files /dev/null and b/app/assets/images/emoji/ok_woman.png differ diff --git a/app/assets/images/emoji/older_man.png b/app/assets/images/emoji/older_man.png new file mode 100755 index 00000000..149f0cfb Binary files /dev/null and b/app/assets/images/emoji/older_man.png differ diff --git a/app/assets/images/emoji/older_woman.png b/app/assets/images/emoji/older_woman.png new file mode 100755 index 00000000..f839565f Binary files /dev/null and b/app/assets/images/emoji/older_woman.png differ diff --git a/app/assets/images/emoji/on.png b/app/assets/images/emoji/on.png new file mode 100755 index 00000000..3595387f Binary files /dev/null and b/app/assets/images/emoji/on.png differ diff --git a/app/assets/images/emoji/oncoming_automobile.png b/app/assets/images/emoji/oncoming_automobile.png new file mode 100755 index 00000000..cb46de22 Binary files /dev/null and b/app/assets/images/emoji/oncoming_automobile.png differ diff --git a/app/assets/images/emoji/oncoming_bus.png b/app/assets/images/emoji/oncoming_bus.png new file mode 100755 index 00000000..3695f762 Binary files /dev/null and b/app/assets/images/emoji/oncoming_bus.png differ diff --git a/app/assets/images/emoji/oncoming_police_car.png b/app/assets/images/emoji/oncoming_police_car.png new file mode 100755 index 00000000..af20e7ef Binary files /dev/null and b/app/assets/images/emoji/oncoming_police_car.png differ diff --git a/app/assets/images/emoji/oncoming_taxi.png b/app/assets/images/emoji/oncoming_taxi.png new file mode 100755 index 00000000..f78cf310 Binary files /dev/null and b/app/assets/images/emoji/oncoming_taxi.png differ diff --git a/app/assets/images/emoji/one.png b/app/assets/images/emoji/one.png new file mode 100755 index 00000000..2d1f9f8c Binary files /dev/null and b/app/assets/images/emoji/one.png differ diff --git a/app/assets/images/emoji/open_file_folder.png b/app/assets/images/emoji/open_file_folder.png new file mode 100755 index 00000000..2bbbbf5e Binary files /dev/null and b/app/assets/images/emoji/open_file_folder.png differ diff --git a/app/assets/images/emoji/open_hands.png b/app/assets/images/emoji/open_hands.png new file mode 100755 index 00000000..2cc25bd4 Binary files /dev/null and b/app/assets/images/emoji/open_hands.png differ diff --git a/app/assets/images/emoji/ophiuchus.png b/app/assets/images/emoji/ophiuchus.png new file mode 100755 index 00000000..4eef715b Binary files /dev/null and b/app/assets/images/emoji/ophiuchus.png differ diff --git a/app/assets/images/emoji/orange_book.png b/app/assets/images/emoji/orange_book.png new file mode 100755 index 00000000..49650d59 Binary files /dev/null and b/app/assets/images/emoji/orange_book.png differ diff --git a/app/assets/images/emoji/outbox_tray.png b/app/assets/images/emoji/outbox_tray.png new file mode 100755 index 00000000..7ad15e64 Binary files /dev/null and b/app/assets/images/emoji/outbox_tray.png differ diff --git a/app/assets/images/emoji/ox.png b/app/assets/images/emoji/ox.png new file mode 100755 index 00000000..f7669802 Binary files /dev/null and b/app/assets/images/emoji/ox.png differ diff --git a/app/assets/images/emoji/page_facing_up.png b/app/assets/images/emoji/page_facing_up.png new file mode 100755 index 00000000..64cd2e1b Binary files /dev/null and b/app/assets/images/emoji/page_facing_up.png differ diff --git a/app/assets/images/emoji/page_with_curl.png b/app/assets/images/emoji/page_with_curl.png new file mode 100755 index 00000000..bf8f979d Binary files /dev/null and b/app/assets/images/emoji/page_with_curl.png differ diff --git a/app/assets/images/emoji/pager.png b/app/assets/images/emoji/pager.png new file mode 100755 index 00000000..e3e1fc44 Binary files /dev/null and b/app/assets/images/emoji/pager.png differ diff --git a/app/assets/images/emoji/palm_tree.png b/app/assets/images/emoji/palm_tree.png new file mode 100755 index 00000000..d534785e Binary files /dev/null and b/app/assets/images/emoji/palm_tree.png differ diff --git a/app/assets/images/emoji/panda_face.png b/app/assets/images/emoji/panda_face.png new file mode 100755 index 00000000..a794fb17 Binary files /dev/null and b/app/assets/images/emoji/panda_face.png differ diff --git a/app/assets/images/emoji/paperclip.png b/app/assets/images/emoji/paperclip.png new file mode 100755 index 00000000..774412dc Binary files /dev/null and b/app/assets/images/emoji/paperclip.png differ diff --git a/app/assets/images/emoji/parking.png b/app/assets/images/emoji/parking.png new file mode 100755 index 00000000..c24af81c Binary files /dev/null and b/app/assets/images/emoji/parking.png differ diff --git a/app/assets/images/emoji/part_alternation_mark.png b/app/assets/images/emoji/part_alternation_mark.png new file mode 100755 index 00000000..45dc9b85 Binary files /dev/null and b/app/assets/images/emoji/part_alternation_mark.png differ diff --git a/app/assets/images/emoji/partly_sunny.png b/app/assets/images/emoji/partly_sunny.png new file mode 100755 index 00000000..020dd5ff Binary files /dev/null and b/app/assets/images/emoji/partly_sunny.png differ diff --git a/app/assets/images/emoji/passport_control.png b/app/assets/images/emoji/passport_control.png new file mode 100755 index 00000000..675b76d3 Binary files /dev/null and b/app/assets/images/emoji/passport_control.png differ diff --git a/app/assets/images/emoji/paw_prints.png b/app/assets/images/emoji/paw_prints.png new file mode 100755 index 00000000..89b9fec9 Binary files /dev/null and b/app/assets/images/emoji/paw_prints.png differ diff --git a/app/assets/images/emoji/peach.png b/app/assets/images/emoji/peach.png new file mode 100755 index 00000000..ee2139ec Binary files /dev/null and b/app/assets/images/emoji/peach.png differ diff --git a/app/assets/images/emoji/pear.png b/app/assets/images/emoji/pear.png new file mode 100755 index 00000000..f24aca8c Binary files /dev/null and b/app/assets/images/emoji/pear.png differ diff --git a/app/assets/images/emoji/pencil.png b/app/assets/images/emoji/pencil.png new file mode 100755 index 00000000..fc97ddbc Binary files /dev/null and b/app/assets/images/emoji/pencil.png differ diff --git a/app/assets/images/emoji/pencil2.png b/app/assets/images/emoji/pencil2.png new file mode 100755 index 00000000..e624373b Binary files /dev/null and b/app/assets/images/emoji/pencil2.png differ diff --git a/app/assets/images/emoji/penguin.png b/app/assets/images/emoji/penguin.png new file mode 100755 index 00000000..d8edbcb8 Binary files /dev/null and b/app/assets/images/emoji/penguin.png differ diff --git a/app/assets/images/emoji/pensive.png b/app/assets/images/emoji/pensive.png new file mode 100755 index 00000000..4159f3c4 Binary files /dev/null and b/app/assets/images/emoji/pensive.png differ diff --git a/app/assets/images/emoji/performing_arts.png b/app/assets/images/emoji/performing_arts.png new file mode 100755 index 00000000..899fbe5a Binary files /dev/null and b/app/assets/images/emoji/performing_arts.png differ diff --git a/app/assets/images/emoji/persevere.png b/app/assets/images/emoji/persevere.png new file mode 100755 index 00000000..f99f6da4 Binary files /dev/null and b/app/assets/images/emoji/persevere.png differ diff --git a/app/assets/images/emoji/person_frowning.png b/app/assets/images/emoji/person_frowning.png new file mode 100755 index 00000000..6f34d5e1 Binary files /dev/null and b/app/assets/images/emoji/person_frowning.png differ diff --git a/app/assets/images/emoji/person_with_blond_hair.png b/app/assets/images/emoji/person_with_blond_hair.png new file mode 100755 index 00000000..c144301c Binary files /dev/null and b/app/assets/images/emoji/person_with_blond_hair.png differ diff --git a/app/assets/images/emoji/person_with_pouting_face.png b/app/assets/images/emoji/person_with_pouting_face.png new file mode 100755 index 00000000..c4a95c3b Binary files /dev/null and b/app/assets/images/emoji/person_with_pouting_face.png differ diff --git a/app/assets/images/emoji/phone.png b/app/assets/images/emoji/phone.png new file mode 100755 index 00000000..87d2559b Binary files /dev/null and b/app/assets/images/emoji/phone.png differ diff --git a/app/assets/images/emoji/pig.png b/app/assets/images/emoji/pig.png new file mode 100755 index 00000000..f7f273c7 Binary files /dev/null and b/app/assets/images/emoji/pig.png differ diff --git a/app/assets/images/emoji/pig2.png b/app/assets/images/emoji/pig2.png new file mode 100755 index 00000000..fec3374d Binary files /dev/null and b/app/assets/images/emoji/pig2.png differ diff --git a/app/assets/images/emoji/pig_nose.png b/app/assets/images/emoji/pig_nose.png new file mode 100755 index 00000000..38d61244 Binary files /dev/null and b/app/assets/images/emoji/pig_nose.png differ diff --git a/app/assets/images/emoji/pill.png b/app/assets/images/emoji/pill.png new file mode 100755 index 00000000..cd84a78f Binary files /dev/null and b/app/assets/images/emoji/pill.png differ diff --git a/app/assets/images/emoji/pineapple.png b/app/assets/images/emoji/pineapple.png new file mode 100755 index 00000000..d6f8e287 Binary files /dev/null and b/app/assets/images/emoji/pineapple.png differ diff --git a/app/assets/images/emoji/pisces.png b/app/assets/images/emoji/pisces.png new file mode 100755 index 00000000..5a2da0a0 Binary files /dev/null and b/app/assets/images/emoji/pisces.png differ diff --git a/app/assets/images/emoji/pizza.png b/app/assets/images/emoji/pizza.png new file mode 100755 index 00000000..460367d0 Binary files /dev/null and b/app/assets/images/emoji/pizza.png differ diff --git a/app/assets/images/emoji/plus1.png b/app/assets/images/emoji/plus1.png new file mode 100755 index 00000000..81786c1d Binary files /dev/null and b/app/assets/images/emoji/plus1.png differ diff --git a/app/assets/images/emoji/point_down.png b/app/assets/images/emoji/point_down.png new file mode 100755 index 00000000..658c6d91 Binary files /dev/null and b/app/assets/images/emoji/point_down.png differ diff --git a/app/assets/images/emoji/point_left.png b/app/assets/images/emoji/point_left.png new file mode 100755 index 00000000..38a99b43 Binary files /dev/null and b/app/assets/images/emoji/point_left.png differ diff --git a/app/assets/images/emoji/point_right.png b/app/assets/images/emoji/point_right.png new file mode 100755 index 00000000..6f9f029a Binary files /dev/null and b/app/assets/images/emoji/point_right.png differ diff --git a/app/assets/images/emoji/point_up.png b/app/assets/images/emoji/point_up.png new file mode 100755 index 00000000..01896e21 Binary files /dev/null and b/app/assets/images/emoji/point_up.png differ diff --git a/app/assets/images/emoji/point_up_2.png b/app/assets/images/emoji/point_up_2.png new file mode 100755 index 00000000..1cfe7367 Binary files /dev/null and b/app/assets/images/emoji/point_up_2.png differ diff --git a/app/assets/images/emoji/police_car.png b/app/assets/images/emoji/police_car.png new file mode 100755 index 00000000..b8f17275 Binary files /dev/null and b/app/assets/images/emoji/police_car.png differ diff --git a/app/assets/images/emoji/poodle.png b/app/assets/images/emoji/poodle.png new file mode 100755 index 00000000..adac80bd Binary files /dev/null and b/app/assets/images/emoji/poodle.png differ diff --git a/app/assets/images/emoji/poop.png b/app/assets/images/emoji/poop.png new file mode 100755 index 00000000..73a4dc84 Binary files /dev/null and b/app/assets/images/emoji/poop.png differ diff --git a/app/assets/images/emoji/post_office.png b/app/assets/images/emoji/post_office.png new file mode 100755 index 00000000..43b59e30 Binary files /dev/null and b/app/assets/images/emoji/post_office.png differ diff --git a/app/assets/images/emoji/postal_horn.png b/app/assets/images/emoji/postal_horn.png new file mode 100755 index 00000000..e9b713bb Binary files /dev/null and b/app/assets/images/emoji/postal_horn.png differ diff --git a/app/assets/images/emoji/postbox.png b/app/assets/images/emoji/postbox.png new file mode 100755 index 00000000..ce04b700 Binary files /dev/null and b/app/assets/images/emoji/postbox.png differ diff --git a/app/assets/images/emoji/potable_water.png b/app/assets/images/emoji/potable_water.png new file mode 100755 index 00000000..e9fd5607 Binary files /dev/null and b/app/assets/images/emoji/potable_water.png differ diff --git a/app/assets/images/emoji/pouch.png b/app/assets/images/emoji/pouch.png new file mode 100755 index 00000000..0bc5879f Binary files /dev/null and b/app/assets/images/emoji/pouch.png differ diff --git a/app/assets/images/emoji/poultry_leg.png b/app/assets/images/emoji/poultry_leg.png new file mode 100755 index 00000000..43ad8596 Binary files /dev/null and b/app/assets/images/emoji/poultry_leg.png differ diff --git a/app/assets/images/emoji/pound.png b/app/assets/images/emoji/pound.png new file mode 100755 index 00000000..f8be91d7 Binary files /dev/null and b/app/assets/images/emoji/pound.png differ diff --git a/app/assets/images/emoji/pouting_cat.png b/app/assets/images/emoji/pouting_cat.png new file mode 100755 index 00000000..4325fd48 Binary files /dev/null and b/app/assets/images/emoji/pouting_cat.png differ diff --git a/app/assets/images/emoji/pray.png b/app/assets/images/emoji/pray.png new file mode 100755 index 00000000..f86c992d Binary files /dev/null and b/app/assets/images/emoji/pray.png differ diff --git a/app/assets/images/emoji/princess.png b/app/assets/images/emoji/princess.png new file mode 100755 index 00000000..1ebb2ce9 Binary files /dev/null and b/app/assets/images/emoji/princess.png differ diff --git a/app/assets/images/emoji/punch.png b/app/assets/images/emoji/punch.png new file mode 100755 index 00000000..277047b7 Binary files /dev/null and b/app/assets/images/emoji/punch.png differ diff --git a/app/assets/images/emoji/purple_heart.png b/app/assets/images/emoji/purple_heart.png new file mode 100755 index 00000000..d5f87504 Binary files /dev/null and b/app/assets/images/emoji/purple_heart.png differ diff --git a/app/assets/images/emoji/purse.png b/app/assets/images/emoji/purse.png new file mode 100755 index 00000000..8f06a2b9 Binary files /dev/null and b/app/assets/images/emoji/purse.png differ diff --git a/app/assets/images/emoji/pushpin.png b/app/assets/images/emoji/pushpin.png new file mode 100755 index 00000000..540c4ecb Binary files /dev/null and b/app/assets/images/emoji/pushpin.png differ diff --git a/app/assets/images/emoji/put_litter_in_its_place.png b/app/assets/images/emoji/put_litter_in_its_place.png new file mode 100755 index 00000000..c2e350c2 Binary files /dev/null and b/app/assets/images/emoji/put_litter_in_its_place.png differ diff --git a/app/assets/images/emoji/question.png b/app/assets/images/emoji/question.png new file mode 100755 index 00000000..38cedf56 Binary files /dev/null and b/app/assets/images/emoji/question.png differ diff --git a/app/assets/images/emoji/rabbit.png b/app/assets/images/emoji/rabbit.png new file mode 100755 index 00000000..5cb3ef6f Binary files /dev/null and b/app/assets/images/emoji/rabbit.png differ diff --git a/app/assets/images/emoji/rabbit2.png b/app/assets/images/emoji/rabbit2.png new file mode 100755 index 00000000..5bc993e7 Binary files /dev/null and b/app/assets/images/emoji/rabbit2.png differ diff --git a/app/assets/images/emoji/racehorse.png b/app/assets/images/emoji/racehorse.png new file mode 100755 index 00000000..4d09c64d Binary files /dev/null and b/app/assets/images/emoji/racehorse.png differ diff --git a/app/assets/images/emoji/radio.png b/app/assets/images/emoji/radio.png new file mode 100755 index 00000000..ea589efe Binary files /dev/null and b/app/assets/images/emoji/radio.png differ diff --git a/app/assets/images/emoji/radio_button.png b/app/assets/images/emoji/radio_button.png new file mode 100755 index 00000000..63755eec Binary files /dev/null and b/app/assets/images/emoji/radio_button.png differ diff --git a/app/assets/images/emoji/rage.png b/app/assets/images/emoji/rage.png new file mode 100755 index 00000000..c65ddff5 Binary files /dev/null and b/app/assets/images/emoji/rage.png differ diff --git a/app/assets/images/emoji/rage1.png b/app/assets/images/emoji/rage1.png new file mode 100755 index 00000000..1506ba40 Binary files /dev/null and b/app/assets/images/emoji/rage1.png differ diff --git a/app/assets/images/emoji/rage2.png b/app/assets/images/emoji/rage2.png new file mode 100755 index 00000000..f792e063 Binary files /dev/null and b/app/assets/images/emoji/rage2.png differ diff --git a/app/assets/images/emoji/rage3.png b/app/assets/images/emoji/rage3.png new file mode 100755 index 00000000..58764cbc Binary files /dev/null and b/app/assets/images/emoji/rage3.png differ diff --git a/app/assets/images/emoji/rage4.png b/app/assets/images/emoji/rage4.png new file mode 100755 index 00000000..c726c94a Binary files /dev/null and b/app/assets/images/emoji/rage4.png differ diff --git a/app/assets/images/emoji/railway_car.png b/app/assets/images/emoji/railway_car.png new file mode 100755 index 00000000..22361158 Binary files /dev/null and b/app/assets/images/emoji/railway_car.png differ diff --git a/app/assets/images/emoji/rainbow.png b/app/assets/images/emoji/rainbow.png new file mode 100755 index 00000000..6b1faa03 Binary files /dev/null and b/app/assets/images/emoji/rainbow.png differ diff --git a/app/assets/images/emoji/raised_hand.png b/app/assets/images/emoji/raised_hand.png new file mode 100755 index 00000000..e1741a40 Binary files /dev/null and b/app/assets/images/emoji/raised_hand.png differ diff --git a/app/assets/images/emoji/raised_hands.png b/app/assets/images/emoji/raised_hands.png new file mode 100755 index 00000000..e03142bd Binary files /dev/null and b/app/assets/images/emoji/raised_hands.png differ diff --git a/app/assets/images/emoji/ram.png b/app/assets/images/emoji/ram.png new file mode 100755 index 00000000..5ea7bfbc Binary files /dev/null and b/app/assets/images/emoji/ram.png differ diff --git a/app/assets/images/emoji/ramen.png b/app/assets/images/emoji/ramen.png new file mode 100755 index 00000000..78dc7d53 Binary files /dev/null and b/app/assets/images/emoji/ramen.png differ diff --git a/app/assets/images/emoji/rat.png b/app/assets/images/emoji/rat.png new file mode 100755 index 00000000..1c463dfd Binary files /dev/null and b/app/assets/images/emoji/rat.png differ diff --git a/app/assets/images/emoji/recycle.png b/app/assets/images/emoji/recycle.png new file mode 100755 index 00000000..99104c0e Binary files /dev/null and b/app/assets/images/emoji/recycle.png differ diff --git a/app/assets/images/emoji/red_car.png b/app/assets/images/emoji/red_car.png new file mode 100755 index 00000000..d70a2f06 Binary files /dev/null and b/app/assets/images/emoji/red_car.png differ diff --git a/app/assets/images/emoji/red_circle.png b/app/assets/images/emoji/red_circle.png new file mode 100755 index 00000000..b391289b Binary files /dev/null and b/app/assets/images/emoji/red_circle.png differ diff --git a/app/assets/images/emoji/registered.png b/app/assets/images/emoji/registered.png new file mode 100755 index 00000000..31c68a80 Binary files /dev/null and b/app/assets/images/emoji/registered.png differ diff --git a/app/assets/images/emoji/relaxed.png b/app/assets/images/emoji/relaxed.png new file mode 100755 index 00000000..bbab82d3 Binary files /dev/null and b/app/assets/images/emoji/relaxed.png differ diff --git a/app/assets/images/emoji/relieved.png b/app/assets/images/emoji/relieved.png new file mode 100755 index 00000000..fa5f9e7f Binary files /dev/null and b/app/assets/images/emoji/relieved.png differ diff --git a/app/assets/images/emoji/repeat.png b/app/assets/images/emoji/repeat.png new file mode 100755 index 00000000..80113b69 Binary files /dev/null and b/app/assets/images/emoji/repeat.png differ diff --git a/app/assets/images/emoji/repeat_one.png b/app/assets/images/emoji/repeat_one.png new file mode 100755 index 00000000..3c47bcc1 Binary files /dev/null and b/app/assets/images/emoji/repeat_one.png differ diff --git a/app/assets/images/emoji/restroom.png b/app/assets/images/emoji/restroom.png new file mode 100755 index 00000000..d6c111b2 Binary files /dev/null and b/app/assets/images/emoji/restroom.png differ diff --git a/app/assets/images/emoji/revolving_hearts.png b/app/assets/images/emoji/revolving_hearts.png new file mode 100755 index 00000000..ea3317c4 Binary files /dev/null and b/app/assets/images/emoji/revolving_hearts.png differ diff --git a/app/assets/images/emoji/rewind.png b/app/assets/images/emoji/rewind.png new file mode 100755 index 00000000..26289dc3 Binary files /dev/null and b/app/assets/images/emoji/rewind.png differ diff --git a/app/assets/images/emoji/ribbon.png b/app/assets/images/emoji/ribbon.png new file mode 100755 index 00000000..63ee5ba5 Binary files /dev/null and b/app/assets/images/emoji/ribbon.png differ diff --git a/app/assets/images/emoji/rice.png b/app/assets/images/emoji/rice.png new file mode 100755 index 00000000..1fd22027 Binary files /dev/null and b/app/assets/images/emoji/rice.png differ diff --git a/app/assets/images/emoji/rice_ball.png b/app/assets/images/emoji/rice_ball.png new file mode 100755 index 00000000..04f8a880 Binary files /dev/null and b/app/assets/images/emoji/rice_ball.png differ diff --git a/app/assets/images/emoji/rice_cracker.png b/app/assets/images/emoji/rice_cracker.png new file mode 100755 index 00000000..954c901e Binary files /dev/null and b/app/assets/images/emoji/rice_cracker.png differ diff --git a/app/assets/images/emoji/rice_scene.png b/app/assets/images/emoji/rice_scene.png new file mode 100755 index 00000000..14361988 Binary files /dev/null and b/app/assets/images/emoji/rice_scene.png differ diff --git a/app/assets/images/emoji/ring.png b/app/assets/images/emoji/ring.png new file mode 100755 index 00000000..8a57fd68 Binary files /dev/null and b/app/assets/images/emoji/ring.png differ diff --git a/app/assets/images/emoji/rocket.png b/app/assets/images/emoji/rocket.png new file mode 100755 index 00000000..783078d3 Binary files /dev/null and b/app/assets/images/emoji/rocket.png differ diff --git a/app/assets/images/emoji/roller_coaster.png b/app/assets/images/emoji/roller_coaster.png new file mode 100755 index 00000000..9180b986 Binary files /dev/null and b/app/assets/images/emoji/roller_coaster.png differ diff --git a/app/assets/images/emoji/rooster.png b/app/assets/images/emoji/rooster.png new file mode 100755 index 00000000..fab23ad3 Binary files /dev/null and b/app/assets/images/emoji/rooster.png differ diff --git a/app/assets/images/emoji/rose.png b/app/assets/images/emoji/rose.png new file mode 100755 index 00000000..3479fbcb Binary files /dev/null and b/app/assets/images/emoji/rose.png differ diff --git a/app/assets/images/emoji/rotating_light.png b/app/assets/images/emoji/rotating_light.png new file mode 100755 index 00000000..6cf4a775 Binary files /dev/null and b/app/assets/images/emoji/rotating_light.png differ diff --git a/app/assets/images/emoji/round_pushpin.png b/app/assets/images/emoji/round_pushpin.png new file mode 100755 index 00000000..e498e92c Binary files /dev/null and b/app/assets/images/emoji/round_pushpin.png differ diff --git a/app/assets/images/emoji/rowboat.png b/app/assets/images/emoji/rowboat.png new file mode 100755 index 00000000..fe8ae3ec Binary files /dev/null and b/app/assets/images/emoji/rowboat.png differ diff --git a/app/assets/images/emoji/ru.png b/app/assets/images/emoji/ru.png new file mode 100755 index 00000000..55fcf354 Binary files /dev/null and b/app/assets/images/emoji/ru.png differ diff --git a/app/assets/images/emoji/rugby_football.png b/app/assets/images/emoji/rugby_football.png new file mode 100755 index 00000000..f8db67d7 Binary files /dev/null and b/app/assets/images/emoji/rugby_football.png differ diff --git a/app/assets/images/emoji/runner.png b/app/assets/images/emoji/runner.png new file mode 100755 index 00000000..cb004296 Binary files /dev/null and b/app/assets/images/emoji/runner.png differ diff --git a/app/assets/images/emoji/running.png b/app/assets/images/emoji/running.png new file mode 100755 index 00000000..1ecfd905 Binary files /dev/null and b/app/assets/images/emoji/running.png differ diff --git a/app/assets/images/emoji/running_shirt_with_sash.png b/app/assets/images/emoji/running_shirt_with_sash.png new file mode 100755 index 00000000..0d68bba0 Binary files /dev/null and b/app/assets/images/emoji/running_shirt_with_sash.png differ diff --git a/app/assets/images/emoji/sa.png b/app/assets/images/emoji/sa.png new file mode 100755 index 00000000..387f098b Binary files /dev/null and b/app/assets/images/emoji/sa.png differ diff --git a/app/assets/images/emoji/sagittarius.png b/app/assets/images/emoji/sagittarius.png new file mode 100755 index 00000000..8b5435ba Binary files /dev/null and b/app/assets/images/emoji/sagittarius.png differ diff --git a/app/assets/images/emoji/sailboat.png b/app/assets/images/emoji/sailboat.png new file mode 100755 index 00000000..ff656dc6 Binary files /dev/null and b/app/assets/images/emoji/sailboat.png differ diff --git a/app/assets/images/emoji/sake.png b/app/assets/images/emoji/sake.png new file mode 100755 index 00000000..1f69907e Binary files /dev/null and b/app/assets/images/emoji/sake.png differ diff --git a/app/assets/images/emoji/sandal.png b/app/assets/images/emoji/sandal.png new file mode 100755 index 00000000..0bb3f663 Binary files /dev/null and b/app/assets/images/emoji/sandal.png differ diff --git a/app/assets/images/emoji/santa.png b/app/assets/images/emoji/santa.png new file mode 100755 index 00000000..a2240c07 Binary files /dev/null and b/app/assets/images/emoji/santa.png differ diff --git a/app/assets/images/emoji/satellite.png b/app/assets/images/emoji/satellite.png new file mode 100755 index 00000000..3481cc2e Binary files /dev/null and b/app/assets/images/emoji/satellite.png differ diff --git a/app/assets/images/emoji/satisfied.png b/app/assets/images/emoji/satisfied.png new file mode 100755 index 00000000..fe5629f4 Binary files /dev/null and b/app/assets/images/emoji/satisfied.png differ diff --git a/app/assets/images/emoji/saxophone.png b/app/assets/images/emoji/saxophone.png new file mode 100755 index 00000000..011559a7 Binary files /dev/null and b/app/assets/images/emoji/saxophone.png differ diff --git a/app/assets/images/emoji/school.png b/app/assets/images/emoji/school.png new file mode 100755 index 00000000..afd922bf Binary files /dev/null and b/app/assets/images/emoji/school.png differ diff --git a/app/assets/images/emoji/school_satchel.png b/app/assets/images/emoji/school_satchel.png new file mode 100755 index 00000000..edfb19ae Binary files /dev/null and b/app/assets/images/emoji/school_satchel.png differ diff --git a/app/assets/images/emoji/scissors.png b/app/assets/images/emoji/scissors.png new file mode 100755 index 00000000..be916043 Binary files /dev/null and b/app/assets/images/emoji/scissors.png differ diff --git a/app/assets/images/emoji/scorpius.png b/app/assets/images/emoji/scorpius.png new file mode 100755 index 00000000..67fcea16 Binary files /dev/null and b/app/assets/images/emoji/scorpius.png differ diff --git a/app/assets/images/emoji/scream.png b/app/assets/images/emoji/scream.png new file mode 100755 index 00000000..9e93c885 Binary files /dev/null and b/app/assets/images/emoji/scream.png differ diff --git a/app/assets/images/emoji/scream_cat.png b/app/assets/images/emoji/scream_cat.png new file mode 100755 index 00000000..d94cd34f Binary files /dev/null and b/app/assets/images/emoji/scream_cat.png differ diff --git a/app/assets/images/emoji/scroll.png b/app/assets/images/emoji/scroll.png new file mode 100755 index 00000000..c5a10e6b Binary files /dev/null and b/app/assets/images/emoji/scroll.png differ diff --git a/app/assets/images/emoji/seat.png b/app/assets/images/emoji/seat.png new file mode 100755 index 00000000..d1cb864b Binary files /dev/null and b/app/assets/images/emoji/seat.png differ diff --git a/app/assets/images/emoji/secret.png b/app/assets/images/emoji/secret.png new file mode 100755 index 00000000..82e383a6 Binary files /dev/null and b/app/assets/images/emoji/secret.png differ diff --git a/app/assets/images/emoji/see_no_evil.png b/app/assets/images/emoji/see_no_evil.png new file mode 100755 index 00000000..0890a622 Binary files /dev/null and b/app/assets/images/emoji/see_no_evil.png differ diff --git a/app/assets/images/emoji/seedling.png b/app/assets/images/emoji/seedling.png new file mode 100755 index 00000000..f0eb5a6b Binary files /dev/null and b/app/assets/images/emoji/seedling.png differ diff --git a/app/assets/images/emoji/seven.png b/app/assets/images/emoji/seven.png new file mode 100755 index 00000000..354e89ae Binary files /dev/null and b/app/assets/images/emoji/seven.png differ diff --git a/app/assets/images/emoji/shaved_ice.png b/app/assets/images/emoji/shaved_ice.png new file mode 100755 index 00000000..0d0b382c Binary files /dev/null and b/app/assets/images/emoji/shaved_ice.png differ diff --git a/app/assets/images/emoji/sheep.png b/app/assets/images/emoji/sheep.png new file mode 100755 index 00000000..c7277d28 Binary files /dev/null and b/app/assets/images/emoji/sheep.png differ diff --git a/app/assets/images/emoji/shell.png b/app/assets/images/emoji/shell.png new file mode 100755 index 00000000..3145b564 Binary files /dev/null and b/app/assets/images/emoji/shell.png differ diff --git a/app/assets/images/emoji/ship.png b/app/assets/images/emoji/ship.png new file mode 100755 index 00000000..5d2d8b60 Binary files /dev/null and b/app/assets/images/emoji/ship.png differ diff --git a/app/assets/images/emoji/shipit.png b/app/assets/images/emoji/shipit.png new file mode 100755 index 00000000..a58a47f6 Binary files /dev/null and b/app/assets/images/emoji/shipit.png differ diff --git a/app/assets/images/emoji/shirt.png b/app/assets/images/emoji/shirt.png new file mode 100755 index 00000000..297a6d63 Binary files /dev/null and b/app/assets/images/emoji/shirt.png differ diff --git a/app/assets/images/emoji/shit.png b/app/assets/images/emoji/shit.png new file mode 100755 index 00000000..73a4dc84 Binary files /dev/null and b/app/assets/images/emoji/shit.png differ diff --git a/app/assets/images/emoji/shoe.png b/app/assets/images/emoji/shoe.png new file mode 100755 index 00000000..45b82e61 Binary files /dev/null and b/app/assets/images/emoji/shoe.png differ diff --git a/app/assets/images/emoji/shower.png b/app/assets/images/emoji/shower.png new file mode 100755 index 00000000..94f82aac Binary files /dev/null and b/app/assets/images/emoji/shower.png differ diff --git a/app/assets/images/emoji/signal_strength.png b/app/assets/images/emoji/signal_strength.png new file mode 100755 index 00000000..a4bd23eb Binary files /dev/null and b/app/assets/images/emoji/signal_strength.png differ diff --git a/app/assets/images/emoji/six.png b/app/assets/images/emoji/six.png new file mode 100755 index 00000000..56880556 Binary files /dev/null and b/app/assets/images/emoji/six.png differ diff --git a/app/assets/images/emoji/six_pointed_star.png b/app/assets/images/emoji/six_pointed_star.png new file mode 100755 index 00000000..010f8f5f Binary files /dev/null and b/app/assets/images/emoji/six_pointed_star.png differ diff --git a/app/assets/images/emoji/ski.png b/app/assets/images/emoji/ski.png new file mode 100755 index 00000000..98f5cb0f Binary files /dev/null and b/app/assets/images/emoji/ski.png differ diff --git a/app/assets/images/emoji/skull.png b/app/assets/images/emoji/skull.png new file mode 100755 index 00000000..bd4ee382 Binary files /dev/null and b/app/assets/images/emoji/skull.png differ diff --git a/app/assets/images/emoji/sleepy.png b/app/assets/images/emoji/sleepy.png new file mode 100755 index 00000000..df4f55ef Binary files /dev/null and b/app/assets/images/emoji/sleepy.png differ diff --git a/app/assets/images/emoji/slot_machine.png b/app/assets/images/emoji/slot_machine.png new file mode 100755 index 00000000..26f11483 Binary files /dev/null and b/app/assets/images/emoji/slot_machine.png differ diff --git a/app/assets/images/emoji/small_blue_diamond.png b/app/assets/images/emoji/small_blue_diamond.png new file mode 100755 index 00000000..5a7b5d55 Binary files /dev/null and b/app/assets/images/emoji/small_blue_diamond.png differ diff --git a/app/assets/images/emoji/small_orange_diamond.png b/app/assets/images/emoji/small_orange_diamond.png new file mode 100755 index 00000000..04941d37 Binary files /dev/null and b/app/assets/images/emoji/small_orange_diamond.png differ diff --git a/app/assets/images/emoji/small_red_triangle.png b/app/assets/images/emoji/small_red_triangle.png new file mode 100755 index 00000000..8c4428da Binary files /dev/null and b/app/assets/images/emoji/small_red_triangle.png differ diff --git a/app/assets/images/emoji/small_red_triangle_down.png b/app/assets/images/emoji/small_red_triangle_down.png new file mode 100755 index 00000000..94832f06 Binary files /dev/null and b/app/assets/images/emoji/small_red_triangle_down.png differ diff --git a/app/assets/images/emoji/smile.png b/app/assets/images/emoji/smile.png new file mode 100755 index 00000000..81a83968 Binary files /dev/null and b/app/assets/images/emoji/smile.png differ diff --git a/app/assets/images/emoji/smile_cat.png b/app/assets/images/emoji/smile_cat.png new file mode 100755 index 00000000..ad333ba3 Binary files /dev/null and b/app/assets/images/emoji/smile_cat.png differ diff --git a/app/assets/images/emoji/smiley.png b/app/assets/images/emoji/smiley.png new file mode 100755 index 00000000..77b581d6 Binary files /dev/null and b/app/assets/images/emoji/smiley.png differ diff --git a/app/assets/images/emoji/smiley_cat.png b/app/assets/images/emoji/smiley_cat.png new file mode 100755 index 00000000..dbf1b027 Binary files /dev/null and b/app/assets/images/emoji/smiley_cat.png differ diff --git a/app/assets/images/emoji/smiling_imp.png b/app/assets/images/emoji/smiling_imp.png new file mode 100755 index 00000000..d9040493 Binary files /dev/null and b/app/assets/images/emoji/smiling_imp.png differ diff --git a/app/assets/images/emoji/smirk.png b/app/assets/images/emoji/smirk.png new file mode 100755 index 00000000..bc6e5082 Binary files /dev/null and b/app/assets/images/emoji/smirk.png differ diff --git a/app/assets/images/emoji/smirk_cat.png b/app/assets/images/emoji/smirk_cat.png new file mode 100755 index 00000000..351565e2 Binary files /dev/null and b/app/assets/images/emoji/smirk_cat.png differ diff --git a/app/assets/images/emoji/smoking.png b/app/assets/images/emoji/smoking.png new file mode 100755 index 00000000..4aad6cbd Binary files /dev/null and b/app/assets/images/emoji/smoking.png differ diff --git a/app/assets/images/emoji/snail.png b/app/assets/images/emoji/snail.png new file mode 100755 index 00000000..e75e69a8 Binary files /dev/null and b/app/assets/images/emoji/snail.png differ diff --git a/app/assets/images/emoji/snake.png b/app/assets/images/emoji/snake.png new file mode 100755 index 00000000..ef58933e Binary files /dev/null and b/app/assets/images/emoji/snake.png differ diff --git a/app/assets/images/emoji/snowboarder.png b/app/assets/images/emoji/snowboarder.png new file mode 100755 index 00000000..aeda5c8d Binary files /dev/null and b/app/assets/images/emoji/snowboarder.png differ diff --git a/app/assets/images/emoji/snowflake.png b/app/assets/images/emoji/snowflake.png new file mode 100755 index 00000000..54b68ff4 Binary files /dev/null and b/app/assets/images/emoji/snowflake.png differ diff --git a/app/assets/images/emoji/snowman.png b/app/assets/images/emoji/snowman.png new file mode 100755 index 00000000..a97902e5 Binary files /dev/null and b/app/assets/images/emoji/snowman.png differ diff --git a/app/assets/images/emoji/sob.png b/app/assets/images/emoji/sob.png new file mode 100755 index 00000000..1561df92 Binary files /dev/null and b/app/assets/images/emoji/sob.png differ diff --git a/app/assets/images/emoji/soccer.png b/app/assets/images/emoji/soccer.png new file mode 100755 index 00000000..1e118b5b Binary files /dev/null and b/app/assets/images/emoji/soccer.png differ diff --git a/app/assets/images/emoji/soon.png b/app/assets/images/emoji/soon.png new file mode 100755 index 00000000..9386615a Binary files /dev/null and b/app/assets/images/emoji/soon.png differ diff --git a/app/assets/images/emoji/sos.png b/app/assets/images/emoji/sos.png new file mode 100755 index 00000000..e3e16ef7 Binary files /dev/null and b/app/assets/images/emoji/sos.png differ diff --git a/app/assets/images/emoji/sound.png b/app/assets/images/emoji/sound.png new file mode 100755 index 00000000..6aa4dbff Binary files /dev/null and b/app/assets/images/emoji/sound.png differ diff --git a/app/assets/images/emoji/space_invader.png b/app/assets/images/emoji/space_invader.png new file mode 100755 index 00000000..38404916 Binary files /dev/null and b/app/assets/images/emoji/space_invader.png differ diff --git a/app/assets/images/emoji/spades.png b/app/assets/images/emoji/spades.png new file mode 100755 index 00000000..133a1aba Binary files /dev/null and b/app/assets/images/emoji/spades.png differ diff --git a/app/assets/images/emoji/spaghetti.png b/app/assets/images/emoji/spaghetti.png new file mode 100755 index 00000000..08de243f Binary files /dev/null and b/app/assets/images/emoji/spaghetti.png differ diff --git a/app/assets/images/emoji/sparkler.png b/app/assets/images/emoji/sparkler.png new file mode 100755 index 00000000..4aabd7e0 Binary files /dev/null and b/app/assets/images/emoji/sparkler.png differ diff --git a/app/assets/images/emoji/sparkles.png b/app/assets/images/emoji/sparkles.png new file mode 100755 index 00000000..92138828 Binary files /dev/null and b/app/assets/images/emoji/sparkles.png differ diff --git a/app/assets/images/emoji/speak_no_evil.png b/app/assets/images/emoji/speak_no_evil.png new file mode 100755 index 00000000..87944c4d Binary files /dev/null and b/app/assets/images/emoji/speak_no_evil.png differ diff --git a/app/assets/images/emoji/speaker.png b/app/assets/images/emoji/speaker.png new file mode 100755 index 00000000..470476e1 Binary files /dev/null and b/app/assets/images/emoji/speaker.png differ diff --git a/app/assets/images/emoji/speech_balloon.png b/app/assets/images/emoji/speech_balloon.png new file mode 100755 index 00000000..2896c278 Binary files /dev/null and b/app/assets/images/emoji/speech_balloon.png differ diff --git a/app/assets/images/emoji/speedboat.png b/app/assets/images/emoji/speedboat.png new file mode 100755 index 00000000..da6689b3 Binary files /dev/null and b/app/assets/images/emoji/speedboat.png differ diff --git a/app/assets/images/emoji/squirrel.png b/app/assets/images/emoji/squirrel.png new file mode 100755 index 00000000..a58a47f6 Binary files /dev/null and b/app/assets/images/emoji/squirrel.png differ diff --git a/app/assets/images/emoji/star.png b/app/assets/images/emoji/star.png new file mode 100755 index 00000000..1bfddc86 Binary files /dev/null and b/app/assets/images/emoji/star.png differ diff --git a/app/assets/images/emoji/star2.png b/app/assets/images/emoji/star2.png new file mode 100755 index 00000000..8b40ff4c Binary files /dev/null and b/app/assets/images/emoji/star2.png differ diff --git a/app/assets/images/emoji/stars.png b/app/assets/images/emoji/stars.png new file mode 100755 index 00000000..097a8424 Binary files /dev/null and b/app/assets/images/emoji/stars.png differ diff --git a/app/assets/images/emoji/station.png b/app/assets/images/emoji/station.png new file mode 100755 index 00000000..e77daa8a Binary files /dev/null and b/app/assets/images/emoji/station.png differ diff --git a/app/assets/images/emoji/statue_of_liberty.png b/app/assets/images/emoji/statue_of_liberty.png new file mode 100755 index 00000000..9ad90280 Binary files /dev/null and b/app/assets/images/emoji/statue_of_liberty.png differ diff --git a/app/assets/images/emoji/steam_locomotive.png b/app/assets/images/emoji/steam_locomotive.png new file mode 100755 index 00000000..54950776 Binary files /dev/null and b/app/assets/images/emoji/steam_locomotive.png differ diff --git a/app/assets/images/emoji/stew.png b/app/assets/images/emoji/stew.png new file mode 100755 index 00000000..e9687f9e Binary files /dev/null and b/app/assets/images/emoji/stew.png differ diff --git a/app/assets/images/emoji/straight_ruler.png b/app/assets/images/emoji/straight_ruler.png new file mode 100755 index 00000000..af8cb4bc Binary files /dev/null and b/app/assets/images/emoji/straight_ruler.png differ diff --git a/app/assets/images/emoji/strawberry.png b/app/assets/images/emoji/strawberry.png new file mode 100755 index 00000000..13eb827a Binary files /dev/null and b/app/assets/images/emoji/strawberry.png differ diff --git a/app/assets/images/emoji/sun_with_face.png b/app/assets/images/emoji/sun_with_face.png new file mode 100755 index 00000000..ee276636 Binary files /dev/null and b/app/assets/images/emoji/sun_with_face.png differ diff --git a/app/assets/images/emoji/sunflower.png b/app/assets/images/emoji/sunflower.png new file mode 100755 index 00000000..d9bad194 Binary files /dev/null and b/app/assets/images/emoji/sunflower.png differ diff --git a/app/assets/images/emoji/sunglasses.png b/app/assets/images/emoji/sunglasses.png new file mode 100755 index 00000000..1c468a1c Binary files /dev/null and b/app/assets/images/emoji/sunglasses.png differ diff --git a/app/assets/images/emoji/sunny.png b/app/assets/images/emoji/sunny.png new file mode 100755 index 00000000..d23c095e Binary files /dev/null and b/app/assets/images/emoji/sunny.png differ diff --git a/app/assets/images/emoji/sunrise.png b/app/assets/images/emoji/sunrise.png new file mode 100755 index 00000000..ec58dcc9 Binary files /dev/null and b/app/assets/images/emoji/sunrise.png differ diff --git a/app/assets/images/emoji/sunrise_over_mountains.png b/app/assets/images/emoji/sunrise_over_mountains.png new file mode 100755 index 00000000..ebc3db14 Binary files /dev/null and b/app/assets/images/emoji/sunrise_over_mountains.png differ diff --git a/app/assets/images/emoji/surfer.png b/app/assets/images/emoji/surfer.png new file mode 100755 index 00000000..b067e8cb Binary files /dev/null and b/app/assets/images/emoji/surfer.png differ diff --git a/app/assets/images/emoji/sushi.png b/app/assets/images/emoji/sushi.png new file mode 100755 index 00000000..0d179bd9 Binary files /dev/null and b/app/assets/images/emoji/sushi.png differ diff --git a/app/assets/images/emoji/suspect.png b/app/assets/images/emoji/suspect.png new file mode 100755 index 00000000..58e8921c Binary files /dev/null and b/app/assets/images/emoji/suspect.png differ diff --git a/app/assets/images/emoji/suspension_railway.png b/app/assets/images/emoji/suspension_railway.png new file mode 100755 index 00000000..aaa45f61 Binary files /dev/null and b/app/assets/images/emoji/suspension_railway.png differ diff --git a/app/assets/images/emoji/sweat.png b/app/assets/images/emoji/sweat.png new file mode 100755 index 00000000..e894b769 Binary files /dev/null and b/app/assets/images/emoji/sweat.png differ diff --git a/app/assets/images/emoji/sweat_drops.png b/app/assets/images/emoji/sweat_drops.png new file mode 100755 index 00000000..a83b3e96 Binary files /dev/null and b/app/assets/images/emoji/sweat_drops.png differ diff --git a/app/assets/images/emoji/sweat_smile.png b/app/assets/images/emoji/sweat_smile.png new file mode 100755 index 00000000..3903f717 Binary files /dev/null and b/app/assets/images/emoji/sweat_smile.png differ diff --git a/app/assets/images/emoji/sweet_potato.png b/app/assets/images/emoji/sweet_potato.png new file mode 100755 index 00000000..32117fa9 Binary files /dev/null and b/app/assets/images/emoji/sweet_potato.png differ diff --git a/app/assets/images/emoji/swimmer.png b/app/assets/images/emoji/swimmer.png new file mode 100755 index 00000000..d3878a06 Binary files /dev/null and b/app/assets/images/emoji/swimmer.png differ diff --git a/app/assets/images/emoji/symbols.png b/app/assets/images/emoji/symbols.png new file mode 100755 index 00000000..16bc1da9 Binary files /dev/null and b/app/assets/images/emoji/symbols.png differ diff --git a/app/assets/images/emoji/syringe.png b/app/assets/images/emoji/syringe.png new file mode 100755 index 00000000..7314255e Binary files /dev/null and b/app/assets/images/emoji/syringe.png differ diff --git a/app/assets/images/emoji/tada.png b/app/assets/images/emoji/tada.png new file mode 100755 index 00000000..7411b526 Binary files /dev/null and b/app/assets/images/emoji/tada.png differ diff --git a/app/assets/images/emoji/tanabata_tree.png b/app/assets/images/emoji/tanabata_tree.png new file mode 100755 index 00000000..47334641 Binary files /dev/null and b/app/assets/images/emoji/tanabata_tree.png differ diff --git a/app/assets/images/emoji/tangerine.png b/app/assets/images/emoji/tangerine.png new file mode 100755 index 00000000..fc9d4f82 Binary files /dev/null and b/app/assets/images/emoji/tangerine.png differ diff --git a/app/assets/images/emoji/taurus.png b/app/assets/images/emoji/taurus.png new file mode 100755 index 00000000..6af582f6 Binary files /dev/null and b/app/assets/images/emoji/taurus.png differ diff --git a/app/assets/images/emoji/taxi.png b/app/assets/images/emoji/taxi.png new file mode 100755 index 00000000..60a50d36 Binary files /dev/null and b/app/assets/images/emoji/taxi.png differ diff --git a/app/assets/images/emoji/tea.png b/app/assets/images/emoji/tea.png new file mode 100755 index 00000000..3ece0b70 Binary files /dev/null and b/app/assets/images/emoji/tea.png differ diff --git a/app/assets/images/emoji/telephone.png b/app/assets/images/emoji/telephone.png new file mode 100755 index 00000000..87d2559b Binary files /dev/null and b/app/assets/images/emoji/telephone.png differ diff --git a/app/assets/images/emoji/telephone_receiver.png b/app/assets/images/emoji/telephone_receiver.png new file mode 100755 index 00000000..36e21e01 Binary files /dev/null and b/app/assets/images/emoji/telephone_receiver.png differ diff --git a/app/assets/images/emoji/telescope.png b/app/assets/images/emoji/telescope.png new file mode 100755 index 00000000..51fd8a07 Binary files /dev/null and b/app/assets/images/emoji/telescope.png differ diff --git a/app/assets/images/emoji/tennis.png b/app/assets/images/emoji/tennis.png new file mode 100755 index 00000000..278d904e Binary files /dev/null and b/app/assets/images/emoji/tennis.png differ diff --git a/app/assets/images/emoji/tent.png b/app/assets/images/emoji/tent.png new file mode 100755 index 00000000..5c0d20e4 Binary files /dev/null and b/app/assets/images/emoji/tent.png differ diff --git a/app/assets/images/emoji/thought_balloon.png b/app/assets/images/emoji/thought_balloon.png new file mode 100755 index 00000000..701bdf0f Binary files /dev/null and b/app/assets/images/emoji/thought_balloon.png differ diff --git a/app/assets/images/emoji/three.png b/app/assets/images/emoji/three.png new file mode 100755 index 00000000..55644c99 Binary files /dev/null and b/app/assets/images/emoji/three.png differ diff --git a/app/assets/images/emoji/thumbsdown.png b/app/assets/images/emoji/thumbsdown.png new file mode 100755 index 00000000..41c6b825 Binary files /dev/null and b/app/assets/images/emoji/thumbsdown.png differ diff --git a/app/assets/images/emoji/thumbsup.png b/app/assets/images/emoji/thumbsup.png new file mode 100755 index 00000000..81786c1d Binary files /dev/null and b/app/assets/images/emoji/thumbsup.png differ diff --git a/app/assets/images/emoji/ticket.png b/app/assets/images/emoji/ticket.png new file mode 100755 index 00000000..cdacf1a7 Binary files /dev/null and b/app/assets/images/emoji/ticket.png differ diff --git a/app/assets/images/emoji/tiger.png b/app/assets/images/emoji/tiger.png new file mode 100755 index 00000000..d6cc84a3 Binary files /dev/null and b/app/assets/images/emoji/tiger.png differ diff --git a/app/assets/images/emoji/tiger2.png b/app/assets/images/emoji/tiger2.png new file mode 100755 index 00000000..b0c7d8dc Binary files /dev/null and b/app/assets/images/emoji/tiger2.png differ diff --git a/app/assets/images/emoji/tired_face.png b/app/assets/images/emoji/tired_face.png new file mode 100755 index 00000000..3a8eefe5 Binary files /dev/null and b/app/assets/images/emoji/tired_face.png differ diff --git a/app/assets/images/emoji/tm.png b/app/assets/images/emoji/tm.png new file mode 100755 index 00000000..c7dec75a Binary files /dev/null and b/app/assets/images/emoji/tm.png differ diff --git a/app/assets/images/emoji/toilet.png b/app/assets/images/emoji/toilet.png new file mode 100755 index 00000000..e5cc4119 Binary files /dev/null and b/app/assets/images/emoji/toilet.png differ diff --git a/app/assets/images/emoji/tokyo_tower.png b/app/assets/images/emoji/tokyo_tower.png new file mode 100755 index 00000000..e1cbd7a3 Binary files /dev/null and b/app/assets/images/emoji/tokyo_tower.png differ diff --git a/app/assets/images/emoji/tomato.png b/app/assets/images/emoji/tomato.png new file mode 100755 index 00000000..a129700b Binary files /dev/null and b/app/assets/images/emoji/tomato.png differ diff --git a/app/assets/images/emoji/tongue.png b/app/assets/images/emoji/tongue.png new file mode 100755 index 00000000..333716ee Binary files /dev/null and b/app/assets/images/emoji/tongue.png differ diff --git a/app/assets/images/emoji/tongue2.png b/app/assets/images/emoji/tongue2.png new file mode 100755 index 00000000..b0bab120 Binary files /dev/null and b/app/assets/images/emoji/tongue2.png differ diff --git a/app/assets/images/emoji/top.png b/app/assets/images/emoji/top.png new file mode 100755 index 00000000..5aa4dd44 Binary files /dev/null and b/app/assets/images/emoji/top.png differ diff --git a/app/assets/images/emoji/tophat.png b/app/assets/images/emoji/tophat.png new file mode 100755 index 00000000..7d27134d Binary files /dev/null and b/app/assets/images/emoji/tophat.png differ diff --git a/app/assets/images/emoji/tractor.png b/app/assets/images/emoji/tractor.png new file mode 100755 index 00000000..058fd3ed Binary files /dev/null and b/app/assets/images/emoji/tractor.png differ diff --git a/app/assets/images/emoji/traffic_light.png b/app/assets/images/emoji/traffic_light.png new file mode 100755 index 00000000..50c78101 Binary files /dev/null and b/app/assets/images/emoji/traffic_light.png differ diff --git a/app/assets/images/emoji/train.png b/app/assets/images/emoji/train.png new file mode 100755 index 00000000..3202d80e Binary files /dev/null and b/app/assets/images/emoji/train.png differ diff --git a/app/assets/images/emoji/train2.png b/app/assets/images/emoji/train2.png new file mode 100755 index 00000000..9c0d3ab6 Binary files /dev/null and b/app/assets/images/emoji/train2.png differ diff --git a/app/assets/images/emoji/tram.png b/app/assets/images/emoji/tram.png new file mode 100755 index 00000000..5eb29fb7 Binary files /dev/null and b/app/assets/images/emoji/tram.png differ diff --git a/app/assets/images/emoji/triangular_flag_on_post.png b/app/assets/images/emoji/triangular_flag_on_post.png new file mode 100755 index 00000000..f9a3f32d Binary files /dev/null and b/app/assets/images/emoji/triangular_flag_on_post.png differ diff --git a/app/assets/images/emoji/triangular_ruler.png b/app/assets/images/emoji/triangular_ruler.png new file mode 100755 index 00000000..383677cb Binary files /dev/null and b/app/assets/images/emoji/triangular_ruler.png differ diff --git a/app/assets/images/emoji/trident.png b/app/assets/images/emoji/trident.png new file mode 100755 index 00000000..d79a7b4c Binary files /dev/null and b/app/assets/images/emoji/trident.png differ diff --git a/app/assets/images/emoji/triumph.png b/app/assets/images/emoji/triumph.png new file mode 100755 index 00000000..92f93bd1 Binary files /dev/null and b/app/assets/images/emoji/triumph.png differ diff --git a/app/assets/images/emoji/trolleybus.png b/app/assets/images/emoji/trolleybus.png new file mode 100755 index 00000000..b9740a53 Binary files /dev/null and b/app/assets/images/emoji/trolleybus.png differ diff --git a/app/assets/images/emoji/trollface.png b/app/assets/images/emoji/trollface.png new file mode 100755 index 00000000..e234893c Binary files /dev/null and b/app/assets/images/emoji/trollface.png differ diff --git a/app/assets/images/emoji/trophy.png b/app/assets/images/emoji/trophy.png new file mode 100755 index 00000000..95d3b63f Binary files /dev/null and b/app/assets/images/emoji/trophy.png differ diff --git a/app/assets/images/emoji/tropical_drink.png b/app/assets/images/emoji/tropical_drink.png new file mode 100755 index 00000000..55ca9eed Binary files /dev/null and b/app/assets/images/emoji/tropical_drink.png differ diff --git a/app/assets/images/emoji/tropical_fish.png b/app/assets/images/emoji/tropical_fish.png new file mode 100755 index 00000000..a6d73498 Binary files /dev/null and b/app/assets/images/emoji/tropical_fish.png differ diff --git a/app/assets/images/emoji/truck.png b/app/assets/images/emoji/truck.png new file mode 100755 index 00000000..3f25ba1f Binary files /dev/null and b/app/assets/images/emoji/truck.png differ diff --git a/app/assets/images/emoji/trumpet.png b/app/assets/images/emoji/trumpet.png new file mode 100755 index 00000000..c84cfb13 Binary files /dev/null and b/app/assets/images/emoji/trumpet.png differ diff --git a/app/assets/images/emoji/tshirt.png b/app/assets/images/emoji/tshirt.png new file mode 100755 index 00000000..297a6d63 Binary files /dev/null and b/app/assets/images/emoji/tshirt.png differ diff --git a/app/assets/images/emoji/tulip.png b/app/assets/images/emoji/tulip.png new file mode 100755 index 00000000..b3ee1102 Binary files /dev/null and b/app/assets/images/emoji/tulip.png differ diff --git a/app/assets/images/emoji/turtle.png b/app/assets/images/emoji/turtle.png new file mode 100755 index 00000000..04d1d968 Binary files /dev/null and b/app/assets/images/emoji/turtle.png differ diff --git a/app/assets/images/emoji/tv.png b/app/assets/images/emoji/tv.png new file mode 100755 index 00000000..803dc3d4 Binary files /dev/null and b/app/assets/images/emoji/tv.png differ diff --git a/app/assets/images/emoji/twisted_rightwards_arrows.png b/app/assets/images/emoji/twisted_rightwards_arrows.png new file mode 100755 index 00000000..25cde18b Binary files /dev/null and b/app/assets/images/emoji/twisted_rightwards_arrows.png differ diff --git a/app/assets/images/emoji/two.png b/app/assets/images/emoji/two.png new file mode 100755 index 00000000..c191f8a3 Binary files /dev/null and b/app/assets/images/emoji/two.png differ diff --git a/app/assets/images/emoji/two_hearts.png b/app/assets/images/emoji/two_hearts.png new file mode 100755 index 00000000..b189e9ae Binary files /dev/null and b/app/assets/images/emoji/two_hearts.png differ diff --git a/app/assets/images/emoji/two_men_holding_hands.png b/app/assets/images/emoji/two_men_holding_hands.png new file mode 100755 index 00000000..d1099f21 Binary files /dev/null and b/app/assets/images/emoji/two_men_holding_hands.png differ diff --git a/app/assets/images/emoji/two_women_holding_hands.png b/app/assets/images/emoji/two_women_holding_hands.png new file mode 100755 index 00000000..619646c4 Binary files /dev/null and b/app/assets/images/emoji/two_women_holding_hands.png differ diff --git a/app/assets/images/emoji/u5272.png b/app/assets/images/emoji/u5272.png new file mode 100755 index 00000000..2148253f Binary files /dev/null and b/app/assets/images/emoji/u5272.png differ diff --git a/app/assets/images/emoji/u5408.png b/app/assets/images/emoji/u5408.png new file mode 100755 index 00000000..03ab0d87 Binary files /dev/null and b/app/assets/images/emoji/u5408.png differ diff --git a/app/assets/images/emoji/u55b6.png b/app/assets/images/emoji/u55b6.png new file mode 100755 index 00000000..ba946d3f Binary files /dev/null and b/app/assets/images/emoji/u55b6.png differ diff --git a/app/assets/images/emoji/u6307.png b/app/assets/images/emoji/u6307.png new file mode 100755 index 00000000..6557f567 Binary files /dev/null and b/app/assets/images/emoji/u6307.png differ diff --git a/app/assets/images/emoji/u6708.png b/app/assets/images/emoji/u6708.png new file mode 100755 index 00000000..e4dfe5aa Binary files /dev/null and b/app/assets/images/emoji/u6708.png differ diff --git a/app/assets/images/emoji/u6709.png b/app/assets/images/emoji/u6709.png new file mode 100755 index 00000000..cd8fb3f6 Binary files /dev/null and b/app/assets/images/emoji/u6709.png differ diff --git a/app/assets/images/emoji/u6e80.png b/app/assets/images/emoji/u6e80.png new file mode 100755 index 00000000..5df1cb87 Binary files /dev/null and b/app/assets/images/emoji/u6e80.png differ diff --git a/app/assets/images/emoji/u7121.png b/app/assets/images/emoji/u7121.png new file mode 100755 index 00000000..25f694ed Binary files /dev/null and b/app/assets/images/emoji/u7121.png differ diff --git a/app/assets/images/emoji/u7533.png b/app/assets/images/emoji/u7533.png new file mode 100755 index 00000000..fc4a9901 Binary files /dev/null and b/app/assets/images/emoji/u7533.png differ diff --git a/app/assets/images/emoji/u7981.png b/app/assets/images/emoji/u7981.png new file mode 100755 index 00000000..f550a573 Binary files /dev/null and b/app/assets/images/emoji/u7981.png differ diff --git a/app/assets/images/emoji/u7a7a.png b/app/assets/images/emoji/u7a7a.png new file mode 100755 index 00000000..c05f5cff Binary files /dev/null and b/app/assets/images/emoji/u7a7a.png differ diff --git a/app/assets/images/emoji/uk.png b/app/assets/images/emoji/uk.png new file mode 100755 index 00000000..2a62c7a0 Binary files /dev/null and b/app/assets/images/emoji/uk.png differ diff --git a/app/assets/images/emoji/umbrella.png b/app/assets/images/emoji/umbrella.png new file mode 100755 index 00000000..1db722fa Binary files /dev/null and b/app/assets/images/emoji/umbrella.png differ diff --git a/app/assets/images/emoji/unamused.png b/app/assets/images/emoji/unamused.png new file mode 100755 index 00000000..3722e6f5 Binary files /dev/null and b/app/assets/images/emoji/unamused.png differ diff --git a/app/assets/images/emoji/underage.png b/app/assets/images/emoji/underage.png new file mode 100755 index 00000000..a789b3c6 Binary files /dev/null and b/app/assets/images/emoji/underage.png differ diff --git a/app/assets/images/emoji/unlock.png b/app/assets/images/emoji/unlock.png new file mode 100755 index 00000000..22b429cd Binary files /dev/null and b/app/assets/images/emoji/unlock.png differ diff --git a/app/assets/images/emoji/up.png b/app/assets/images/emoji/up.png new file mode 100755 index 00000000..829219a8 Binary files /dev/null and b/app/assets/images/emoji/up.png differ diff --git a/app/assets/images/emoji/us.png b/app/assets/images/emoji/us.png new file mode 100755 index 00000000..38137669 Binary files /dev/null and b/app/assets/images/emoji/us.png differ diff --git a/app/assets/images/emoji/v.png b/app/assets/images/emoji/v.png new file mode 100755 index 00000000..f61267c2 Binary files /dev/null and b/app/assets/images/emoji/v.png differ diff --git a/app/assets/images/emoji/vertical_traffic_light.png b/app/assets/images/emoji/vertical_traffic_light.png new file mode 100755 index 00000000..7a5ba35f Binary files /dev/null and b/app/assets/images/emoji/vertical_traffic_light.png differ diff --git a/app/assets/images/emoji/vhs.png b/app/assets/images/emoji/vhs.png new file mode 100755 index 00000000..881081c1 Binary files /dev/null and b/app/assets/images/emoji/vhs.png differ diff --git a/app/assets/images/emoji/vibration_mode.png b/app/assets/images/emoji/vibration_mode.png new file mode 100755 index 00000000..a716e96c Binary files /dev/null and b/app/assets/images/emoji/vibration_mode.png differ diff --git a/app/assets/images/emoji/video_camera.png b/app/assets/images/emoji/video_camera.png new file mode 100755 index 00000000..274cecdd Binary files /dev/null and b/app/assets/images/emoji/video_camera.png differ diff --git a/app/assets/images/emoji/video_game.png b/app/assets/images/emoji/video_game.png new file mode 100755 index 00000000..59d45bae Binary files /dev/null and b/app/assets/images/emoji/video_game.png differ diff --git a/app/assets/images/emoji/violin.png b/app/assets/images/emoji/violin.png new file mode 100755 index 00000000..0dba5ba2 Binary files /dev/null and b/app/assets/images/emoji/violin.png differ diff --git a/app/assets/images/emoji/virgo.png b/app/assets/images/emoji/virgo.png new file mode 100755 index 00000000..72e1763f Binary files /dev/null and b/app/assets/images/emoji/virgo.png differ diff --git a/app/assets/images/emoji/volcano.png b/app/assets/images/emoji/volcano.png new file mode 100755 index 00000000..9b434539 Binary files /dev/null and b/app/assets/images/emoji/volcano.png differ diff --git a/app/assets/images/emoji/vs.png b/app/assets/images/emoji/vs.png new file mode 100755 index 00000000..86363885 Binary files /dev/null and b/app/assets/images/emoji/vs.png differ diff --git a/app/assets/images/emoji/walking.png b/app/assets/images/emoji/walking.png new file mode 100755 index 00000000..52bc0381 Binary files /dev/null and b/app/assets/images/emoji/walking.png differ diff --git a/app/assets/images/emoji/waning_crescent_moon.png b/app/assets/images/emoji/waning_crescent_moon.png new file mode 100755 index 00000000..30387780 Binary files /dev/null and b/app/assets/images/emoji/waning_crescent_moon.png differ diff --git a/app/assets/images/emoji/waning_gibbous_moon.png b/app/assets/images/emoji/waning_gibbous_moon.png new file mode 100755 index 00000000..8e324ec5 Binary files /dev/null and b/app/assets/images/emoji/waning_gibbous_moon.png differ diff --git a/app/assets/images/emoji/warning.png b/app/assets/images/emoji/warning.png new file mode 100755 index 00000000..466658d9 Binary files /dev/null and b/app/assets/images/emoji/warning.png differ diff --git a/app/assets/images/emoji/watch.png b/app/assets/images/emoji/watch.png new file mode 100755 index 00000000..d503bb87 Binary files /dev/null and b/app/assets/images/emoji/watch.png differ diff --git a/app/assets/images/emoji/water_buffalo.png b/app/assets/images/emoji/water_buffalo.png new file mode 100755 index 00000000..3bcde3ed Binary files /dev/null and b/app/assets/images/emoji/water_buffalo.png differ diff --git a/app/assets/images/emoji/watermelon.png b/app/assets/images/emoji/watermelon.png new file mode 100755 index 00000000..fc212be7 Binary files /dev/null and b/app/assets/images/emoji/watermelon.png differ diff --git a/app/assets/images/emoji/wave.png b/app/assets/images/emoji/wave.png new file mode 100755 index 00000000..e78402eb Binary files /dev/null and b/app/assets/images/emoji/wave.png differ diff --git a/app/assets/images/emoji/wavy_dash.png b/app/assets/images/emoji/wavy_dash.png new file mode 100755 index 00000000..77f626cc Binary files /dev/null and b/app/assets/images/emoji/wavy_dash.png differ diff --git a/app/assets/images/emoji/waxing_crescent_moon.png b/app/assets/images/emoji/waxing_crescent_moon.png new file mode 100755 index 00000000..c8f13dd3 Binary files /dev/null and b/app/assets/images/emoji/waxing_crescent_moon.png differ diff --git a/app/assets/images/emoji/waxing_gibbous_moon.png b/app/assets/images/emoji/waxing_gibbous_moon.png new file mode 100755 index 00000000..dd8c4845 Binary files /dev/null and b/app/assets/images/emoji/waxing_gibbous_moon.png differ diff --git a/app/assets/images/emoji/wc.png b/app/assets/images/emoji/wc.png new file mode 100755 index 00000000..dfe84d2a Binary files /dev/null and b/app/assets/images/emoji/wc.png differ diff --git a/app/assets/images/emoji/weary.png b/app/assets/images/emoji/weary.png new file mode 100755 index 00000000..0c547541 Binary files /dev/null and b/app/assets/images/emoji/weary.png differ diff --git a/app/assets/images/emoji/wedding.png b/app/assets/images/emoji/wedding.png new file mode 100755 index 00000000..ead19d52 Binary files /dev/null and b/app/assets/images/emoji/wedding.png differ diff --git a/app/assets/images/emoji/whale.png b/app/assets/images/emoji/whale.png new file mode 100755 index 00000000..5bb113e4 Binary files /dev/null and b/app/assets/images/emoji/whale.png differ diff --git a/app/assets/images/emoji/whale2.png b/app/assets/images/emoji/whale2.png new file mode 100755 index 00000000..4af657b2 Binary files /dev/null and b/app/assets/images/emoji/whale2.png differ diff --git a/app/assets/images/emoji/wheelchair.png b/app/assets/images/emoji/wheelchair.png new file mode 100755 index 00000000..eddcdd79 Binary files /dev/null and b/app/assets/images/emoji/wheelchair.png differ diff --git a/app/assets/images/emoji/white_circle.png b/app/assets/images/emoji/white_circle.png new file mode 100755 index 00000000..da782ae2 Binary files /dev/null and b/app/assets/images/emoji/white_circle.png differ diff --git a/app/assets/images/emoji/white_flower.png b/app/assets/images/emoji/white_flower.png new file mode 100755 index 00000000..c0929d0d Binary files /dev/null and b/app/assets/images/emoji/white_flower.png differ diff --git a/app/assets/images/emoji/white_square.png b/app/assets/images/emoji/white_square.png new file mode 100755 index 00000000..60cb19a1 Binary files /dev/null and b/app/assets/images/emoji/white_square.png differ diff --git a/app/assets/images/emoji/wind_chime.png b/app/assets/images/emoji/wind_chime.png new file mode 100755 index 00000000..efacf5dd Binary files /dev/null and b/app/assets/images/emoji/wind_chime.png differ diff --git a/app/assets/images/emoji/wine_glass.png b/app/assets/images/emoji/wine_glass.png new file mode 100755 index 00000000..82b0f000 Binary files /dev/null and b/app/assets/images/emoji/wine_glass.png differ diff --git a/app/assets/images/emoji/wink.png b/app/assets/images/emoji/wink.png new file mode 100755 index 00000000..756766dd Binary files /dev/null and b/app/assets/images/emoji/wink.png differ diff --git a/app/assets/images/emoji/wink2.png b/app/assets/images/emoji/wink2.png new file mode 100755 index 00000000..6ae9d497 Binary files /dev/null and b/app/assets/images/emoji/wink2.png differ diff --git a/app/assets/images/emoji/wolf.png b/app/assets/images/emoji/wolf.png new file mode 100755 index 00000000..c60c9689 Binary files /dev/null and b/app/assets/images/emoji/wolf.png differ diff --git a/app/assets/images/emoji/woman.png b/app/assets/images/emoji/woman.png new file mode 100755 index 00000000..6bf0d2b1 Binary files /dev/null and b/app/assets/images/emoji/woman.png differ diff --git a/app/assets/images/emoji/womans_clothes.png b/app/assets/images/emoji/womans_clothes.png new file mode 100755 index 00000000..aa297c7b Binary files /dev/null and b/app/assets/images/emoji/womans_clothes.png differ diff --git a/app/assets/images/emoji/womans_hat.png b/app/assets/images/emoji/womans_hat.png new file mode 100755 index 00000000..4cb2e6a6 Binary files /dev/null and b/app/assets/images/emoji/womans_hat.png differ diff --git a/app/assets/images/emoji/womens.png b/app/assets/images/emoji/womens.png new file mode 100755 index 00000000..110f8851 Binary files /dev/null and b/app/assets/images/emoji/womens.png differ diff --git a/app/assets/images/emoji/wrench.png b/app/assets/images/emoji/wrench.png new file mode 100755 index 00000000..a87072ad Binary files /dev/null and b/app/assets/images/emoji/wrench.png differ diff --git a/app/assets/images/emoji/x.png b/app/assets/images/emoji/x.png new file mode 100755 index 00000000..dff9efa8 Binary files /dev/null and b/app/assets/images/emoji/x.png differ diff --git a/app/assets/images/emoji/yellow_heart.png b/app/assets/images/emoji/yellow_heart.png new file mode 100755 index 00000000..fa41ce78 Binary files /dev/null and b/app/assets/images/emoji/yellow_heart.png differ diff --git a/app/assets/images/emoji/yen.png b/app/assets/images/emoji/yen.png new file mode 100755 index 00000000..139bc936 Binary files /dev/null and b/app/assets/images/emoji/yen.png differ diff --git a/app/assets/images/emoji/yum.png b/app/assets/images/emoji/yum.png new file mode 100755 index 00000000..fc39637e Binary files /dev/null and b/app/assets/images/emoji/yum.png differ diff --git a/app/assets/images/emoji/zap.png b/app/assets/images/emoji/zap.png new file mode 100755 index 00000000..260c531b Binary files /dev/null and b/app/assets/images/emoji/zap.png differ diff --git a/app/assets/images/emoji/zero.png b/app/assets/images/emoji/zero.png new file mode 100755 index 00000000..6e57b334 Binary files /dev/null and b/app/assets/images/emoji/zero.png differ diff --git a/app/assets/images/emoji/zzz.png b/app/assets/images/emoji/zzz.png new file mode 100755 index 00000000..30be0465 Binary files /dev/null and b/app/assets/images/emoji/zzz.png differ diff --git a/app/assets/images/file_dir.png b/app/assets/images/file_dir.png index 97b05393..ea277bb1 100644 Binary files a/app/assets/images/file_dir.png and b/app/assets/images/file_dir.png differ diff --git a/app/assets/images/merge.png b/app/assets/images/merge.png new file mode 100644 index 00000000..4a6bb2e1 Binary files /dev/null and b/app/assets/images/merge.png differ diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js deleted file mode 100644 index bb0a499a..00000000 --- a/app/assets/javascripts/admin.js +++ /dev/null @@ -1,11 +0,0 @@ -$(document).ready(function(){ - $('input#user_force_random_password').on('change', function(elem) { - var elems = $('#user_password, #user_password_confirmation'); - - if ($(this).attr('checked')) { - elems.val('').attr('disabled', true); - } else { - elems.removeAttr('disabled'); - } - }); -}); diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee new file mode 100644 index 00000000..76454c29 --- /dev/null +++ b/app/assets/javascripts/admin.js.coffee @@ -0,0 +1,12 @@ +$ -> + $('input#user_force_random_password').on 'change', (elem) -> + elems = $('#user_password, #user_password_confirmation') + + if $(@).attr 'checked' + elems.val('').attr 'disabled', true + else + elems.removeAttr 'disabled' + + $('.log-tabs a').click (e) -> + e.preventDefault() + $(this).tab('show') diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 24d99a62..f9fdb0f7 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -11,120 +11,9 @@ //= require jquery.endless-scroll //= require jquery.highlight //= require jquery.waitforimages -//= require bootstrap-modal +//= require bootstrap //= require modernizr //= require chosen-jquery //= require raphael //= require branch-graph //= require_tree . - -$(document).ready(function(){ - - $(".one_click_select").live("click", function(){ - $(this).select(); - }); - - $('body').on('ajax:complete, ajax:beforeSend, submit', 'form', function(e){ - var buttons = $('[type="submit"]', this); - switch( e.type ){ - case 'ajax:beforeSend': - case 'submit': - buttons.attr('disabled', 'disabled'); - break; - case ' ajax:complete': - default: - buttons.removeAttr('disabled'); - break; - } - }) - - $(".account-box").mouseenter(showMenu); - $(".account-box").mouseleave(resetMenu); - - $("#projects-list .project").live('click', function(e){ - if(e.target.nodeName != "A" && e.target.nodeName != "INPUT") { - location.href = $(this).attr("url"); - e.stopPropagation(); - return false; - } - }); - - /** - * Focus search field by pressing 's' key - */ - $(document).keypress(function(e) { - if( $(e.target).is(":input") ) return; - switch(e.which) { - case 115: focusSearch(); - e.preventDefault(); - } - }); - - /** - * Commit show suppressed diff - * - */ - $(".supp_diff_link").bind("click", function() { - showDiff(this); - }); - - /** - * Note markdown preview - * - */ - $(document).on('click', '#preview-link', function(e) { - $('#preview-note').text('Loading...'); - - var previewLinkText = ($(this).text() == 'Preview' ? 'Edit' : 'Preview'); - $(this).text(previewLinkText); - - var note = $('#note_note').val(); - if (note.trim().length === 0) { note = 'Nothing to preview'; } - $.post($(this).attr('href'), {note: note}, function(data) { - $('#preview-note').html(data); - }); - - $('#preview-note, #note_note').toggle(); - e.preventDefault(); - }); -}); - -function focusSearch() { - $("#search").focus(); -} - -function updatePage(data){ - $.ajax({type: "GET", url: location.href, data: data, dataType: "script"}); -} - -function showMenu() { - $(this).toggleClass('hover'); -} - -function resetMenu() { - $(this).removeClass("hover"); -} - -function slugify(text) { - return text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase(); -} - -function showDiff(link) { - $(link).next('table').show(); - $(link).remove(); -} - -(function($){ - var _chosen = $.fn.chosen; - $.fn.extend({ - chosen: function(options) { - var default_options = {'search_contains' : 'true'}; - $.extend(default_options, options); - return _chosen.apply(this, [default_options]); - }}) -})(jQuery); - - -function ajaxGet(url) { - $.ajax({type: "GET", url: url, dataType: "script"}); -} diff --git a/app/assets/javascripts/graph.js b/app/assets/javascripts/graph.js deleted file mode 100644 index 434cf70a..00000000 --- a/app/assets/javascripts/graph.js +++ /dev/null @@ -1,10 +0,0 @@ -function initGraphNav() { - $(".graph svg").css("position", "relative"); - $("body").bind("keyup", function(e) { - if(e.keyCode == 37) { // left - $(".graph svg").animate({ left: "+=400" }); - } else if(e.keyCode == 39) { // right - $(".graph svg").animate({ left: "-=400" }); - } - }); -} diff --git a/app/assets/javascripts/graph.js.coffee b/app/assets/javascripts/graph.js.coffee new file mode 100644 index 00000000..5fe8ae3f --- /dev/null +++ b/app/assets/javascripts/graph.js.coffee @@ -0,0 +1,10 @@ +initGraphNav = -> + $('.graph svg').css 'position', 'relative' + + $('body').bind 'keyup', (e) -> + if e.keyCode is 37 # left + $('.graph svg').animate left: '+=400' + else if e.keyCode is 39 # right + $('.graph svg').animate left: '-=400' + +window.initGraphNav = initGraphNav diff --git a/app/assets/javascripts/issues.js b/app/assets/javascripts/issues.js index bc056965..3ddc6926 100644 --- a/app/assets/javascripts/issues.js +++ b/app/assets/javascripts/issues.js @@ -5,6 +5,7 @@ function switchToNewIssue(form){ $('select#issue_milestone_id').chosen(); $("#new_issue_dialog").show("fade", { direction: "right" }, 150); $('.top-tabs .add_new').hide(); + disableButtonIfEmptyField("#issue_title", ".save-btn"); }); } @@ -15,6 +16,7 @@ function switchToEditIssue(form){ $('select#issue_milestone_id').chosen(); $("#edit_issue_dialog").show("fade", { direction: "right" }, 150); $('.add_new').hide(); + disableButtonIfEmptyField("#issue_title", ".save-btn"); }); } @@ -78,6 +80,10 @@ function issuesPage(){ $(this).closest("form").submit(); }); + $("#new_issue_link").click(function(){ + updateNewIssueURL(); + }); + $('body').on('ajax:success', '.close_issue, .reopen_issue, #new_issue', function(){ var t = $(this), totalIssues, @@ -124,3 +130,20 @@ function issuesCheckChanged() { $('.issues_filters').show(); } } + +function updateNewIssueURL(){ + var new_issue_link = $("#new_issue_link"); + var milestone_id = $("#milestone_id").val(); + var assignee_id = $("#assignee_id").val(); + var new_href = ""; + if(milestone_id){ + new_href = "issue[milestone_id]=" + milestone_id + "&"; + } + if(assignee_id){ + new_href = new_href + "issue[assignee_id]=" + assignee_id; + } + if(new_href.length){ + new_href = new_issue_link.attr("href") + "?" + new_href; + new_issue_link.attr("href", new_href); + } +}; diff --git a/app/assets/javascripts/loader.js b/app/assets/javascripts/loader.js deleted file mode 100644 index 6fa0b525..00000000 --- a/app/assets/javascripts/loader.js +++ /dev/null @@ -1,11 +0,0 @@ -var Loader = { - img_src: "/assets/ajax-loader.gif", - - html: - function(width) { - img = $(""); - img.attr("width", width); - img.attr("src", this.img_src); - return img; - } -} diff --git a/app/assets/javascripts/loader.js.coffee b/app/assets/javascripts/loader.js.coffee new file mode 100644 index 00000000..66f8e8b1 --- /dev/null +++ b/app/assets/javascripts/loader.js.coffee @@ -0,0 +1,5 @@ +Loader = + html: (width) -> + $('').attr src: '/assets/ajax-loader.gif', width: width + +window.Loader = Loader diff --git a/app/assets/javascripts/main.js.coffee b/app/assets/javascripts/main.js.coffee new file mode 100644 index 00000000..86b19162 --- /dev/null +++ b/app/assets/javascripts/main.js.coffee @@ -0,0 +1,92 @@ +window.updatePage = (data) -> + $.ajax({type: "GET", url: location.href, data: data, dataType: "script"}) + +window.slugify = (text) -> + text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase() + +window.ajaxGet = (url) -> + $.ajax({type: "GET", url: url, dataType: "script"}) + + # Disable button if text field is empty +window.disableButtonIfEmptyField = (field_selector, button_selector) -> + field = $(field_selector) + closest_submit = field.closest("form").find(button_selector) + + closest_submit.disable() if field.val() is "" + + field.on "keyup", -> + if $(this).val() is "" + closest_submit.disable() + else + closest_submit.enable() + +$ -> + # Click a .one_click_select field, select the contents + $(".one_click_select").live 'click', -> $(this).select() + + # Initialize chosen selects + $('select.chosen').chosen() + + # Disable form buttons while a form is submitting + $('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) -> + buttons = $('[type="submit"]', this) + + switch e.type + when 'ajax:beforeSend', 'submit' + buttons.disable() + else + buttons.enable() + + # Show/Hide the profile menu when hovering the account box + $('.account-box').hover -> $(this).toggleClass('hover') + + # Focus search field by pressing 's' key + $(document).keypress (e) -> + # Don't do anything if typing in an input + return if $(e.target).is(":input") + + switch e.which + when 115 + $("#search").focus() + e.preventDefault() + + # Commit show suppressed diff + $(".supp_diff_link").bind "click", -> + $(this).next('table').show() + $(this).remove() + + # Note markdown preview + $(document).on 'click', '#preview-link', (e) -> + $('#preview-note').text('Loading...') + + previewLinkText = if $(this).text() == 'Preview' then 'Edit' else 'Preview' + $(this).text(previewLinkText) + + note = $('#note_note').val() + + if note.trim().length == 0 + $('#preview-note').text("Nothing to preview.") + else + $.post $(this).attr('href'), {note: note}, (data) -> + $('#preview-note').html(data) + + $('#preview-note, #note_note').toggle() + e.preventDefault() + false + +(($) -> + _chosen = $.fn.chosen + $.fn.extend chosen: (options) -> + default_options = search_contains: "true" + $.extend default_options, options + _chosen.apply this, [default_options] + + # Disable an element and add the 'disabled' Bootstrap class + $.fn.extend disable: -> + $(this).attr('disabled', 'disabled').addClass('disabled') + + # Enable an element and remove the 'disabled' Bootstrap class + $.fn.extend enable: -> + $(this).removeAttr('disabled').removeClass('disabled') + +)(jQuery) diff --git a/app/assets/javascripts/note.js b/app/assets/javascripts/note.js deleted file mode 100644 index d9ae45d9..00000000 --- a/app/assets/javascripts/note.js +++ /dev/null @@ -1,169 +0,0 @@ -var NoteList = { - -notes_path: null, -target_params: null, -target_id: 0, -target_type: null, -first_id: 0, -last_id: 0, -disable:false, - -init: - function(tid, tt, path) { - this.notes_path = path + ".js"; - this.target_id = tid; - this.target_type = tt; - this.target_params = "&target_type=" + this.target_type + "&target_id=" + this.target_id; - - // get notes - this.getContent(); - - // get new notes every n seconds - this.initRefresh(); - - $('.delete-note').live('ajax:success', function() { - $(this).closest('li').fadeOut(); }); - - $("#new_note").live("ajax:before", function(){ - $(".submit_note").attr("disabled", "disabled"); - }) - - $("#new_note").live("ajax:complete", function(){ - $(".submit_note").removeAttr("disabled"); - }) - - $("#note_note").live("focus", function(){ - $(this).css("height", "80px"); - $('.note_advanced_opts').show(); - }); - - $("#note_attachment").change(function(e){ - var val = $('.input-file').val(); - var filename = val.replace(/^.*[\\\/]/, ''); - $(".file_name").text(filename); - }); - - }, - - -/** - * Load new notes to fresh list called 'new_notes_list': - * - Replace 'new_notes_list' with new list every n seconds - * - Append new notes to this list after submit - */ - -initRefresh: - function() { - // init timer - var intNew = setInterval("NoteList.getNew()", 10000); - }, - -replace: - function(html) { - $("#new_notes_list").html(html); - }, - -prepend: - function(id, html) { - if(id != this.last_id) { - $("#new_notes_list").prepend(html); - } - }, - -getNew: - function() { - // refersh notes list - $.ajax({ - type: "GET", - url: this.notes_path, - data: "last_id=" + this.last_id + this.target_params, - dataType: "script"}); - }, - -refresh: - function() { - // refersh notes list - $.ajax({ - type: "GET", - url: this.notes_path, - data: "first_id=" + this.first_id + "&last_id=" + this.last_id + this.target_params, - dataType: "script"}); - }, - - -/** - * Init load of notes: - * 1. Get content with ajax call - * 2. Set content of notes list with loaded one - */ - - -getContent: - function() { - $.ajax({ - type: "GET", - url: this.notes_path, - data: "?" + this.target_params, - complete: function(){ $('.status').removeClass("loading")}, - beforeSend: function() { $('.status').addClass("loading") }, - dataType: "script"}); - }, - -setContent: - function(fid, lid, html) { - this.last_id = lid; - this.first_id = fid; - $("#notes-list").html(html); - - // Init infinite scrolling - this.initLoadMore(); - }, - - -/** - * Paging for old notes when scroll to bottom: - * 1. Init scroll events with 'initLoadMore' - * 2. Load onlder notes with 'getOld' method - * 3. append old notes to bottom of list with 'append' - * - */ - - -getOld: - function() { - $('.loading').show(); - $.ajax({ - type: "GET", - url: this.notes_path, - data: "first_id=" + this.first_id + this.target_params, - complete: function(){ $('.status').removeClass("loading")}, - beforeSend: function() { $('.status').addClass("loading") }, - dataType: "script"}); - }, - -append: - function(id, html) { - if(this.first_id == id) { - this.disable = true; - } else { - this.first_id = id; - $("#notes-list").append(html); - } - }, - - -initLoadMore: - function() { - $(document).endlessScroll({ - bottomPixels: 400, - fireDelay: 1000, - fireOnce:true, - ceaseFire: function() { - return NoteList.disable; - }, - callback: function(i) { - NoteList.getOld(); - } - }); - } -} diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js new file mode 100644 index 00000000..e1ad1d2f --- /dev/null +++ b/app/assets/javascripts/notes.js @@ -0,0 +1,293 @@ +var NoteList = { + + notes_path: null, + target_params: null, + target_id: 0, + target_type: null, + top_id: 0, + bottom_id: 0, + loading_more_disabled: false, + reversed: false, + + init: + function(tid, tt, path) { + this.notes_path = path + ".js"; + this.target_id = tid; + this.target_type = tt; + this.reversed = $("#notes-list").hasClass("reversed"); + this.target_params = "&target_type=" + this.target_type + "&target_id=" + this.target_id; + + // get initial set of notes + this.getContent(); + + $("#notes-list, #new-notes-list").on("ajax:success", ".delete-note", function() { + $(this).closest('li').fadeOut(function() { + $(this).remove(); + NoteList.updateVotes(); + }); + }); + + $(".note-form-holder").on("ajax:before", function(){ + $(".submit_note").disable(); + }) + + $(".note-form-holder").on("ajax:complete", function(){ + $(".submit_note").enable(); + }) + + disableButtonIfEmptyField(".note-text", ".submit_note"); + + $("#note_attachment").change(function(e){ + var val = $('.input-file').val(); + var filename = val.replace(/^.*[\\\/]/, ''); + $(".file_name").text(filename); + }); + + if(this.reversed) { + var textarea = $(".note-text"); + $('.note_advanced_opts').hide(); + textarea.css("height", "40px"); + textarea.on("focus", function(){ + $(this).css("height", "80px"); + $('.note_advanced_opts').show(); + }); + } + }, + + + /** + * Handle loading the initial set of notes. + * And set up loading more notes when scrolling to the bottom of the page. + */ + + + /** + * Gets an inital set of notes. + */ + getContent: + function() { + $.ajax({ + type: "GET", + url: this.notes_path, + data: "?" + this.target_params, + complete: function(){ $('.notes-status').removeClass("loading")}, + beforeSend: function() { $('.notes-status').addClass("loading") }, + dataType: "script"}); + }, + + /** + * Called in response to getContent(). + * Replaces the content of #notes-list with the given html. + */ + setContent: + function(first_id, last_id, html) { + this.top_id = first_id; + this.bottom_id = last_id; + $("#notes-list").html(html); + + // init infinite scrolling + this.initLoadMore(); + + // init getting new notes + if (this.reversed) { + this.initRefreshNew(); + } + }, + + + /** + * Handle loading more notes when scrolling to the bottom of the page. + * The id of the last note in the list is in this.bottom_id. + * + * Set up refreshing only new notes after all notes have been loaded. + */ + + + /** + * Initializes loading more notes when scrolling to the bottom of the page. + */ + initLoadMore: + function() { + $(document).endlessScroll({ + bottomPixels: 400, + fireDelay: 1000, + fireOnce:true, + ceaseFire: function() { + return NoteList.loading_more_disabled; + }, + callback: function(i) { + NoteList.getMore(); + } + }); + }, + + /** + * Gets an additional set of notes. + */ + getMore: + function() { + // only load more notes if there are no "new" notes + $('.loading').show(); + $.ajax({ + type: "GET", + url: this.notes_path, + data: "loading_more=1&" + (this.reversed ? "before_id" : "after_id") + "=" + this.bottom_id + this.target_params, + complete: function(){ $('.notes-status').removeClass("loading")}, + beforeSend: function() { $('.notes-status').addClass("loading") }, + dataType: "script"}); + }, + + /** + * Called in response to getMore(). + * Append notes to #notes-list. + */ + appendMoreNotes: + function(id, html) { + if(id != this.bottom_id) { + this.bottom_id = id; + $("#notes-list").append(html); + } + }, + + /** + * Called in response to getMore(). + * Disables loading more notes when scrolling to the bottom of the page. + * Initalizes refreshing new notes. + */ + finishedLoadingMore: + function() { + this.loading_more_disabled = true; + + // from now on only get new notes + if (!this.reversed) { + this.initRefreshNew(); + } + // make sure we are up to date + this.updateVotes(); + }, + + + /** + * Handle refreshing and adding of new notes. + * + * New notes are all notes that are created after the site has been loaded. + * The "old" notes are in #notes-list the "new" ones will be in #new-notes-list. + * The id of the last "old" note is in this.bottom_id. + */ + + + /** + * Initializes getting new notes every n seconds. + */ + initRefreshNew: + function() { + setInterval("NoteList.getNew()", 10000); + }, + + /** + * Gets the new set of notes. + */ + getNew: + function() { + $.ajax({ + type: "GET", + url: this.notes_path, + data: "loading_new=1&after_id=" + (this.reversed ? this.top_id : this.bottom_id) + this.target_params, + dataType: "script"}); + }, + + /** + * Called in response to getNew(). + * Replaces the content of #new-notes-list with the given html. + */ + replaceNewNotes: + function(html) { + $("#new-notes-list").html(html); + this.updateVotes(); + }, + + /** + * Adds a single note to #new-notes-list. + */ + appendNewNote: + function(id, html) { + if (this.reversed) { + $("#new-notes-list").prepend(html); + } else { + $("#new-notes-list").append(html); + } + this.updateVotes(); + }, + + /** + * Recalculates the votes and updates them (if they are displayed at all). + * + * Assumes all relevant notes are displayed (i.e. there are no more notes to + * load via getMore()). + * Might produce inaccurate results when not all notes have been loaded and a + * recalculation is triggered (e.g. when deleting a note). + */ + updateVotes: + function() { + var votes = $("#votes .votes"); + var notes = $("#notes-list, #new-notes-list").find(".note.vote"); + + // only update if there is a vote display + if (votes.size()) { + var upvotes = notes.filter(".upvote").size(); + var downvotes = notes.filter(".downvote").size(); + var votesCount = upvotes + downvotes; + var upvotesPercent = votesCount ? (100.0 / votesCount * upvotes) : 0; + var downvotesPercent = votesCount ? (100.0 - upvotesPercent) : 0; + + // change vote bar lengths + votes.find(".bar-success").css("width", upvotesPercent+"%"); + votes.find(".bar-danger").css("width", downvotesPercent+"%"); + // replace vote numbers + votes.find(".upvotes").text(votes.find(".upvotes").text().replace(/\d+/, upvotes)); + votes.find(".downvotes").text(votes.find(".downvotes").text().replace(/\d+/, downvotes)); + } + } +}; + +var PerLineNotes = { + init: + function() { + /** + * Called when clicking on the "add note" or "reply" button for a diff line. + * + * Shows the note form below the line. + * Sets some hidden fields in the form. + */ + $(".diff_file_content").on("click", ".line_note_link, .line_note_reply_link", function(e) { + var form = $(".per_line_form"); + $(this).closest("tr").after(form); + form.find("#note_line_code").val($(this).data("lineCode")); + form.show(); + return false; + }); + + disableButtonIfEmptyField(".line-note-text", ".submit_inline_note"); + + /** + * Called in response to successfully deleting a note on a diff line. + * + * Removes the actual note from view. + * Removes the reply button if the last note for that line has been removed. + */ + $(".diff_file_content").on("ajax:success", ".delete-note", function() { + var trNote = $(this).closest("tr"); + trNote.fadeOut(function() { + $(this).remove(); + }); + + // check if this is the last note for this line + // elements must really be removed for this to work reliably + var trLine = trNote.prev(); + var trRpl = trNote.next(); + if (trLine.hasClass("line_holder") && trRpl.hasClass("reply")) { + trRpl.fadeOut(function() { $(this).remove(); }); + } + }); + } +} diff --git a/app/assets/javascripts/projects.js b/app/assets/javascripts/projects.js deleted file mode 100644 index 84272698..00000000 --- a/app/assets/javascripts/projects.js +++ /dev/null @@ -1,14 +0,0 @@ -function Projects() { - $("#project_name").live("change", function(){ - var slug = slugify($(this).val()); - $("#project_code").val(slug); - $("#project_path").val(slug); - }); - - $('.new_project, .edit_project').live('ajax:before', function() { - $('.project_new_holder, .project_edit_holder').hide(); - $('.ajax_loader').show(); - }); - - $('form #project_default_branch').chosen(); -} diff --git a/app/assets/javascripts/projects.js.coffee b/app/assets/javascripts/projects.js.coffee new file mode 100644 index 00000000..008fa8e9 --- /dev/null +++ b/app/assets/javascripts/projects.js.coffee @@ -0,0 +1,24 @@ +window.Projects = -> + $('#project_name').on 'change', -> + slug = slugify $(@).val() + $('#project_code, #project_path').val slug + + $('.new_project, .edit_project').on 'ajax:before', -> + $('.project_new_holder, .project_edit_holder').hide() + $('.save-project-loader').show() + + $('form #project_default_branch').chosen() + disableButtonIfEmptyField '#project_name', '.project-submit' + +$ -> + # Git clone panel switcher + scope = $ '.project_clone_holder' + if scope.length > 0 + $('a, button', scope).click -> + $('a, button', scope).removeClass 'active' + $(@).addClass 'active' + $('#project_clone', scope).val $(@).data 'clone' + + # Ref switcher + $('.project-refs-select').on 'change', -> + $(@).parents('form').submit() diff --git a/app/assets/javascripts/snippets.js b/app/assets/javascripts/snippets.js deleted file mode 100644 index 11e18eb7..00000000 --- a/app/assets/javascripts/snippets.js +++ /dev/null @@ -1,9 +0,0 @@ -$(document).ready(function(){ - $("#snippets-table .snippet").live('click', function(e){ - if(e.target.nodeName != "A" && e.target.nodeName != "INPUT") { - location.href = $(this).attr("url"); - e.stopPropagation(); - return false; - } - }); -}); diff --git a/app/assets/javascripts/snippets.js.coffee b/app/assets/javascripts/snippets.js.coffee new file mode 100644 index 00000000..af4385de --- /dev/null +++ b/app/assets/javascripts/snippets.js.coffee @@ -0,0 +1,6 @@ +$ -> + $('#snippets-table .snippet').live 'click', (e) -> + if e.target.nodeName isnt 'A' and e.target.nodeName isnt 'INPUT' + location.href = $(@).attr 'url' + e.stopPropagation() + false diff --git a/app/assets/javascripts/team.js b/app/assets/javascripts/team.js deleted file mode 100644 index f4b04354..00000000 --- a/app/assets/javascripts/team.js +++ /dev/null @@ -1,8 +0,0 @@ -function backToMembers(){ - $("#new_team_member").hide("slide", { direction: "right" }, 150, function(){ - $("#team-table").show("slide", { direction: "left" }, 150, function() { - $("#new_team_member").remove(); - $(".add_new").show(); - }); - }); -} diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss index 3c160358..c5b37916 100644 --- a/app/assets/stylesheets/common.scss +++ b/app/assets/stylesheets/common.scss @@ -1,11 +1,9 @@ -.diff_file_header a, -.file_stats a { - color:$style_color; -} - - /** LAYOUT **/ +body { + margin-bottom:20px; +} + .container { padding-top:0; z-index:5; @@ -40,30 +38,6 @@ color: $link_color; } -.widget { - @include shade; - padding:20px; - margin-bottom:20px; - border: 1px solid #DDD; - border-radius: 5px; - background:#fafafa; - - .link_holder { - background:#eee; - position:relative; - left:-20px; - top:20px; - padding:10px 20px; - width:100%; - border-top:1px solid #ccc; - - a { - font-size:14px; - color:#666; - } - } -} - .help li { color:#111 } .back_link { @@ -88,16 +62,6 @@ padding-left:20px; } -.number { - border-radius: 4px; - text-shadow: none; - background: rgba(0,0,0,.12); - text-align: center; - padding: 2px 4px; - line-height:18px; - margin-left:2px; -} - table a code { position: relative; top: -2px; @@ -129,26 +93,18 @@ table a code { border-bottom:1px solid #ccc; h4 { - color:#444; - font-size:22px; + color:#666; + font-size:18px; + line-height:38px; padding-top:5px; margin:2px; + font-weight:normal; } } .git_url_wrapper { margin-right:50px } -.file_stats { - span { - img { - width:14px; - float:left; - margin-right:6px; - padding:2px 0; - } - } -} .handle:hover { cursor:move; @@ -172,10 +128,6 @@ span.update-author { display:block; } /** END UPDATE ITEM **/ -.ajax-tab-loading { - padding:40px; - display:none; -} .dashboard-loader { float:left; margin:10px; @@ -186,15 +138,144 @@ span.update-author { font-weight:bold; } -a.project-update.titled { - position:relative; - padding-left:35% !important; - .title-block { - padding:10px; - width:35%; - position:absolute; - left:0; - top:0; +.neib { + margin-right:10px; +} + +.label { + background-color: #474D57; + + &.label-tag { + background: none; + border: none; + padding:4px 6px; + color:#444; + text-shadow:0 0 1px #fff; + + &.grouped { + float: left; + margin-right: 6px; + padding: 6px; + } + } + &.label-issue { + background-color: #eee; + border: 1px solid #ccc; + padding:4px 6px; + color:#444; + text-shadow:0 0 1px #fff; + + &.grouped { + float: left; + margin-right: 6px; + padding: 6px; + } + } + + &.label-success { + background-color: #8D8; + color: #333; + text-shadow: 0 1px 1px white; + } + + &.label-error { + background-color: #D88; + color: #333; + text-shadow: 0 1px 1px white; + } +} + +.event_label { + @extend .label; + background-color: #999; + + &.pushed { + background-color: #4A97BD; + } + + &.opened { + background-color: #469847; + } + + &.closed { + background-color: #B94A48; + } + + &.merged { + background-color: #2A2; + } + + &.joined { + background-color: #1ca9dd; + } + + &.left { + background-color: #888; + float:none; + } +} + +form { + @extend .form-horizontal; + + .actions { + @extend .form-actions; + } + + .clearfix { + @extend .control-group; + } + + .input { + @extend .controls; + } + + label { + @extend .control-label; + } + .xlarge { + @extend .input-xlarge; + } + .xxlarge { + @extend .input-xxlarge; + } +} + + +.field_with_errors { + display:inline; +} + +ul.breadcrumb { + background:white; + border:none; + li { + display: inline; + text-shadow: 0 1px 0 white + } + + a { + color:#474D57; + font-weight:bold; + font-size:14px; + } + + .arrow { + background: url("images.png") no-repeat -85px -77px; + width: 19px; + height: 16px; + float: left; + position: relative; + left: -10px; + padding:0; + margin:0; + } +} + +input[type=text] { + &.large_text { + padding:6px; + font-size:16px; } } @@ -270,40 +351,6 @@ p.time { } -/** - * Dashboard page - * - */ -.dashboard_category { - margin-bottom:30px; - h3 a { - color:#474D57; - &:hover { - text-decoration:underline; - } - } - - .dashboard_block { - .dash_project_item { - margin-bottom:10px; - border:none; - padding:0px 5px; - .project_link { - color:#888; - &:hover { - color:#111; - .ico.project { - background-position:-209px -21px; - } - } - } - h4 { - color:#666; - } - } - } -} - .styled_image { border:2px solid #ddd; } @@ -393,46 +440,48 @@ p.time { } } -.btn { - &.very_small { - font-size:11px; - padding:2px 6px; - margin:2px; +.votes { + font-size: 13px; + line-height: 15px; + .progress { + height: 4px; + margin: 0; + .bar { + float: left; + height: 100%; + } + .bar-success { + background-color: #468847; + @include bg-gradient(#62C462, #51A351); + } + .bar-danger { + background-color: #B94A48; + @include bg-gradient(#EE5F5B, #BD362F); + } } - - &.grouped { - margin-right:7px; - float:left; + .upvotes { + display: inline-block; + color: #468847; } - - &.padded { - margin-right:3px; - padding:4px 10px 4px; + .downvotes { + display: inline-block; + color: #B94A48; } } - - -.prettyprint { - background-color: #fefbf3; - padding: 9px; - border: 1px solid rgba(0,0,0,.2); - -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.1); - -moz-box-shadow: 0 1px 2px rgba(0,0,0,.1); - box-shadow: 0 1px 2px rgba(0,0,0,.1); +.votes-block { + margin: 14px 6px 6px 0; + .downvotes { + float: right; + } } - -.hint { - font-style: italic; - color: #999; -} - -.upvotes { - font-size: 14px; - font-weight: bold; - color: #468847; - text-align: right; - padding: 4px; - margin: 2px; +.votes-inline { + display: inline-block; + margin: 0 8px; + .progress { + display: inline-block; + padding: 0 0 2px; + width: 45px; + } } /* Fix for readme code (stopped it from being yellow) */ @@ -549,14 +598,6 @@ li.note { } -/** - * Milestones list - * - */ - -.milestone { - @extend .wll; -} /** * Admin area @@ -603,11 +644,10 @@ li.note { * */ .event_lp { - @extend .alert-info; + @extend .ui-box; + color:#777; margin-bottom:20px; padding:8px; - border-style: solid; - border-width: 1px; @include border-radius(4px); min-height:22px; @@ -621,88 +661,19 @@ li.note { cursor:pointer; } -/** - * Issues, MRs legend - * - */ - -.list_legend { - float:left; - margin-right:20px; - .icon { - width:12px; - height:12px; - float:left; - margin-right:5px; - margin-top: 2px; - @include border-radius(4px); - &.today{ - background: #ADA; - border:1px solid #8B8; - } - &.closed { - background: #DDD; - border:1px solid #BBB; - } - &.yours { - background: #AAD; - border:1px solid #88B; - } - &.merged { - background: #DAD; - border:1px solid #B8B; - } - } - .text { - padding-bottom: 10px; - float:left; - } -} - .merge_request, .issue { - .list_legend { - margin-right: 5px; - margin-top: 14px; - .icon { - width:8px; - height:8px; - float:left; - margin-right:5px; - @include border-radius(4px); - border:1px solid #ddd; - } - } - &.today{ background: #EFE; border-color:#CEC; - .icon { - background: #ADA; - border:1px solid #8B8; - } } &.closed { background: #F5f5f5; border-color:#E5E5E5; - .icon { - background: #DDD; - border:1px solid #BBB; - } - } - &.yours { - .icon { - background: #AAD; - border:1px solid #88B; - } } &.merged { background: #F5f5f5; border-color:#E5E5E5; - .icon { - background: #DAD; - border:1px solid #B8B; - } } } @@ -714,7 +685,7 @@ li.note { margin-right:40px; .prev { - @extend .borders; + @extend .thumbnail; height:120px; width:175px; margin-bottom:10px; @@ -743,3 +714,31 @@ li.note { text-align:center; margin-bottom:10px; } + +.oauth_select_holder { + padding:20px; + img { + padding:5px; + margin-right:10px; + } + .active { + img { + border:1px solid #ccc; + background:$hover; + @include border-radius(5px); + } + } +} + +.btn-build-token { + float: left; + padding: 6px 20px; + margin-right: 12px; +} + +.gitlab-promo { + a { + color:#aaa; + margin-right: 30px; + } +} diff --git a/app/assets/stylesheets/gitlab_bootstrap.scss b/app/assets/stylesheets/gitlab_bootstrap.scss deleted file mode 100644 index 03beeaef..00000000 --- a/app/assets/stylesheets/gitlab_bootstrap.scss +++ /dev/null @@ -1,832 +0,0 @@ -body { - margin-bottom:20px; -} - -pre { - font-family:'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; - - &.dark { - background: #333; - color:#f5f5f5; - } -} - -a { - outline: none; - color: $link_color; - &:hover { - text-decoration:none; - color: $blue_link; - } - - &.btn { - color: $style_color; - } - - &.dark { - color: $style_color; - } - - &.lined { - text-decoration:underline; - &:hover { text-decoration:underline; } - } - - &.gray { - color:gray; - } - - &.supp_diff_link { - text-align:center; - padding:20px 0; - background:#f1f1f1; - width:100%; - float:left; - } - - &.neib { - margin-right:15px; - } -} - -.neib { - margin-right:10px; -} - -.alert-message { - @extend .alert; - - &.success { - @extend .alert-success; - } - - &.error { - @extend .alert-error; - } -} - -.alert { - &.alert-well { - background:#ddd; - border:1px solid #ccc; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #ddd), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#ddd 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#ddd 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#ddd 6.6%, #dfdfdf); - color:#111; - } -} - -h3, h4, h5, h6 { - line-height: 36px; -} - -h5 { - font-size:14px; -} - - -table { - width:100%; - th { - padding-top: 9px; - font-weight: bold; - vertical-align: middle; - } - th, td { - padding: 10px 10px 9px; - line-height: 18px; - text-align: left; - } - - &.bordered-table { - border: 1px solid #DDD; - border-collapse: separate; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - } - - &.zebra-striped { - @extend .table-striped; - } -} - -.btn { - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f1f1f1), color-stop(25%, #f1f1f1), to(#e6e6e6)); - background-image: -webkit-linear-gradient(#f1f1f1, #f1f1f1 25%, #e6e6e6); - background-image: -moz-linear-gradient(top, #f1f1f1, #f1f1f1 25%, #e6e6e6); - background-image: -ms-linear-gradient(#f1f1f1, #f1f1f1 25%, #e6e6e6); - background-image: -o-linear-gradient(#f1f1f1, #f1f1f1 25%, #e6e6e6); - background-image: linear-gradient(#f1f1f1, #f1f1f1 25%, #e6e6e6); - - &:hover { - } - - &.btn-primary { - background:$link_color; - border-color: #2A79A3; - &:hover { - background:$blue_link; - } - } - &.primary { - @extend .btn-primary; - } - - &.success { - color: #fff; - text-shadow: 0 0 1px #111; - background: #5bb75b;; - font-weight: bold; - - &:hover { - background-color: #51a351; - color: #fff; - } - } - - &.danger, - &.btn-danger { - color:#fff; - background: #DA4E49; - border-color: #BD362F; - - &:hover { - color:#fff; - background: #EE4E49; - } - } - - &.danger { - @extend .btn-danger; - } - - &.small { - @extend .btn-small; - } - - &.active { - border-color:#aaa; - background-color:#ccc; - } -} - -a:focus { - outline: none; -} - -.nav-pills a:hover { - background-color:#888; -} - -.nav-pills .active a { - background-color: $style_color; -} - -.label { - background-color: #474D57; - &.label-important { - background-color: #B94A48; - } - - &.label-issue { - background-color: #eee; - border: 1px solid #ccc; - padding:4px 6px; - color:#444; - text-shadow:0 0 1px #fff; - - &.grouped { - float: left; - margin-right: 6px; - padding: 6px; - } - } -} - -.nav-tabs > li > a, .nav-pills > li > a { - color:$style_color; -} - -.nav-tabs > .active > a { - font-weight:bold; -} - -/** COLORS **/ -.cgray { color:gray; } -.cred { color:#D12F19; } -.cgreen { color:#44aa22; } -.cblack { color:#111; } -.cdark { color:#444 } -.cwhite { color:#fff !important } -.bgred { background: #F2DEDE !important} - -/** COMMON STYLES **/ -.left { - float:left; -} -.right { - float:right !important; -} -.width-50p{ - width:50%; -} -.width-49p{ - width:49%; -} -.width-30p{ - width:30%; -} -.width-65p{ - width:65%; -} -.width-100p{ - width:100%; -} -.append-bottom-10 { - margin-bottom:10px; -} -.append-bottom-20 { - margin-bottom:20px; -} -.prepend-top-10 { - margin-top:10px; -} - -.prepend-top-20 { - margin-top:20px; -} - -.padded { - padding:20px; -} - -.ipadded { - padding:20px !important; -} -.lborder { - border-left:1px solid #eee; -} - -.borders { - border: 1px solid #ccc; - @include shade; -} -.no-borders { - border:none; -} -table.no-borders { - border:none; - tr, td { border:none } -} -.no-padding { - padding:0 !important; -} -.underlined { - border-bottom: 1px solid $border_color; -} -.vlink { - color: $link_color !important; -} - -.pretty_label { - @include round-borders-all(4px); - padding:2px 4px; - background-image: -webkit-gradient(linear, 0 0, 0 26, color-stop(0.076, #fefefe), to(#F6F7F8)); - background-image: -webkit-linear-gradient(#fefefe 7.6%, #F6F7F8); - background-image: -moz-linear-gradient(#fefefe 7.6%, #F6F7F8); - background-image: -o-linear-gradient(#fefefe 7.6%, #F6F7F8); - color: #777; - border: 1px solid #DEDFE1; - - &.branch { - border:none; - font-size:13px; - background: #474D57; - color:#fff; - font-weight:bold; - font-family: monospace; - } -} - -.event_label { - @extend .label; - background-color: #999; - - &.pushed { - background-color: #3A87AD; - } - - &.opened { - background-color: #468847; - } - - &.closed { - background-color: #B94A48; - } - - &.merged { - background-color: #2A2; - } -} - -img.avatar { - float:left; - margin-right:15px; - width:40px; - border:1px solid #ddd; - padding:1px; - - &.s16 { - width:16px; - height:16px; - } - &.s24 { - width:24px; - height:24px; - } - &.s32 { - width:32px; - height:32px; - } -} - -img.lil_av { - padding-left: 4px; - padding-right:3px; -} - -form { - @extend .form-horizontal; - - .actions { - @extend .form-actions; - } - - .clearfix { - @extend .control-group; - } - - .input { - @extend .controls; - } - - label { - @extend .control-label; - } - .xlarge { - @extend .input-xlarge; - } - .xxlarge { - @extend .input-xxlarge; - } -} - -/** - * List li block element #1 - * - */ -.wll { - background-color: #FFF; - padding: 10px 5px; - min-height: 20px; - border-bottom: 1px solid #eee; - border-bottom: 1px solid rgba(0, 0, 0, 0.05); - &.smoke { - background-color:#f5f5f5; - } - &:hover { - background:$hover; - } - &:last-child { border:none } - p { padding-top:5px; margin:0; color:$style_color;} - .author { color: #999; } - p { - color:#222; - margin-bottom: 0; - img { - position:relative; - top:3px; - } - } -} - - -/** - * Block element #2 - * - */ -.entry { - position: relative; - padding: 7px 15px; - margin-bottom: 18px; - color: #404040; - filter:none; - - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); - - background:#F1F1F1; - border: 1px solid #ccc; - - - p { - color:$style_color; - margin-bottom: 0; - img { - position:relative; - top:3px; - } - } -} - - -/** - * Big UI Block for show page content - * - */ -.ui-box { - background:#F9F9F9; - margin-bottom: 25px; - @include round-borders-all(4px); - border-color: #CCC; - @include solid_shade; - - ul { - margin:0; - } - - h5, .title { - padding: 0 10px; - @include round-borders-top(4px); - border-bottom: 1px solid #bbb; - background:#eee; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); - - &.small { - line-height: 28px; - font-size: 14px; - line-height:28px; - text-shadow: 0 1px 1px white; - } - - form { - padding:9px 0; - margin:0px; - } - - .nav-pills { - li { - padding:3px 0; - &.active a { background-color:$style_color; } - a { - border-radius:7px; - } - } - } - } - - .bottom { - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); - @include round-borders-bottom(4px); - border-bottom:none; - border-top: 1px solid #bbb; - } - - &.padded { - h5, .title { - margin: -20px; - margin-bottom: 0; - padding: 5px 20px; - } - .middle_title { - background:#f5f5f5; - margin:20px -20px; - padding: 0 20px; - border-top:1px solid #eee; - border-bottom:1px solid #eee; - font-size:14px; - color:#777; - } - } - .row_title { - font-weight:bold; - color:#444; - &:hover { - color:#444; - text-decoration:underline; - } - } - - li, .wll { - padding:10px; - &:first-child { - @include round-borders-top(4px); - border-top:none; - } - - &:last-child { - @include round-borders-bottom(4px); - border:none; - } - } - -} - -table.admin-table { - @extend .table-bordered; - @extend .zebra-striped; - @include solid_shade; - th { - border-color: #CCC; - border-bottom: 1px solid #bbb; - background:#eee; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); - } -} - -.field_with_errors { - display:inline; -} - -ul.breadcrumb { - background:white; - border:none; - li { - display: inline; - text-shadow: 0 1px 0 white - } - - a { - color:#474D57; - font-weight:bold; - font-size:14px; - } - - .arrow { - background: url("images.png") no-repeat -85px -77px; - width: 19px; - height: 16px; - float: left; - position: relative; - left: -10px; - padding:0; - margin:0; - } -} - -.nothing_here_message { - text-align:center; - padding:20px; - color:#777; -} - -/** - * UI box element - * contains top, middle, bottom blocks - * - */ -.main_box { - @extend .borders; - @extend .prepend-top-20; - @extend .append-bottom-20; - border-width:1px; - @include solid_shade; - - - img { max-width: 100%; } - - pre { - code { - background: none !important; - } - } - - .top_box_content, - .middle_box_content, - .bottom_box_content { - padding:15px; - - pre { - background: none !important; - margin:0; - border:none; - padding:0; - } - } - - .middle_box_content { - border-radius:0; - border:none; - font-size:12px; - background-color:#f5f5f5; - border:none; - border-top:1px solid #eee; - } - - .bottom_box_content { - border-top:1px solid #eee; - } -} - -input[type=text] { - &.large_text { - padding:6px; - font-size:16px; - } -} - -p { - &.slead { - color:#456; - font-size:16px; - margin-bottom: 12px; - font-weight: 200; - line-height: 24px; - } -} - -h3.page_title { - color:#456; - font-size:20px; - font-weight: normal; - line-height: 28px; -} - -/** - * File content holder - * - */ -.file_holder { - border:1px solid #CCC; - margin-bottom:1em; - @include solid_shade; - - .file_title { - border-bottom: 1px solid #bbb; - background:#eee; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); - margin: 0; - font-weight: normal; - font-weight: bold; - text-align: left; - color: #666; - padding: 9px 10px; - height:18px; - - .options { - float:right; - margin-top: -5px; - } - - .file_name { - color:$style_color; - font-size:14px; - text-shadow: 0 1px 1px #fff; - small { - color:#999; - font-size:13px; - } - } - } - .file_content { - background:#fff; - font-size: 11px; - - &.wiki { - font-size: 13px; - code { - padding:0 4px; - } - padding:20px; - h1, h2 { - line-height: 46px; - } - h3, h4 { - line-height: 40px; - } - } - - &.image_file { - background:#eee; - text-align:center; - img { - padding:100px; - max-width:300px; - } - } - - &.blob_file { - - } - - /** - * Blame file - */ - &.blame { - tr { - border-bottom: 1px solid #eee; - } - td { - padding:5px; - } - .author, - .blame_commit { - background:#f5f5f5; - vertical-align:top; - } - .lines { - pre { - padding:0; - margin:0; - background:none; - border:none; - } - } - } - - &.logs { - background:#eee; - max-height: 700px; - overflow-y: auto; - - ol { - margin-left:40px; - padding: 10px 0; - border-left: 1px solid #CCC; - margin-bottom:0; - background: white; - li { - color:#888; - p { - margin:0; - color:#333; - line-height:24px; - padding-left: 10px; - } - - &:hover { - background:$hover; - } - } - } - } - - /** - * Code file - */ - &.code { - padding:0; - td.code { - width: 100%; - .highlight { - margin-left: 55px; - overflow:auto; - overflow-y:hidden; - } - } - .highlight pre { - white-space: pre; - word-wrap:normal; - } - - table.highlighttable { - border: none; - } - body.project-page table.highlighttable td { border: none } - table.highlighttable tr:hover { background:none;} - - table.highlighttable pre{ - line-height:16px !important; - font-size:12px !important; - } - - table.highlighttable .linenodiv pre { - text-align: right; - padding-right: 4px; - color:#666; - } - } - } -} diff --git a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss new file mode 100644 index 00000000..ae66bd20 --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss @@ -0,0 +1,153 @@ +/** + * =================================== + * Contain 3 main UI block elements: + * .main_box - for show pages + * .ui-box - for simple block & widgets + * =================================== + */ + +/** + * UI box element + * contains top, middle, bottom blocks + * + */ +.main_box { + @extend .borders; + @extend .prepend-top-20; + @extend .append-bottom-20; + border-width:1px; + @include solid_shade; + + + img { max-width: 100%; } + + pre { + code { + background: none !important; + } + } + + .top_box_content, + .middle_box_content, + .bottom_box_content { + padding:15px; + + pre { + background: none !important; + margin:0; + border:none; + padding:0; + } + } + + .middle_box_content { + border-radius:0; + border:none; + font-size:12px; + background-color:#f5f5f5; + border:none; + border-top:1px solid #eee; + } + + .bottom_box_content { + border-top:1px solid #eee; + } +} + +/** + * Big UI Block for show page content + * + */ +.ui-box { + background:#F9F9F9; + margin-bottom: 25px; + @include round-borders-all(4px); + border-color: #CCC; + @include solid_shade; + + &.white { + background:#fff; + } + + ul { + margin:0; + } + + h5, .title { + padding: 0 10px; + @include round-borders-top(4px); + @include bg-gray-gradient; + border-bottom: 1px solid #bbb; + + &.small { + line-height: 28px; + font-size: 14px; + line-height:28px; + text-shadow: 0 1px 1px white; + } + + form { + padding:9px 0; + margin:0px; + } + + .nav-pills { + li { + padding:3px 0; + &.active a { background-color:$style_color; } + a { + border-radius:7px; + } + } + } + } + + .bottom { + @include bg-gray-gradient; + @include round-borders-bottom(4px); + border-bottom:none; + border-top: 1px solid #bbb; + } + + &.padded { + h5, .title { + margin: -20px; + margin-bottom: 0; + padding: 5px 20px; + } + .middle_title { + background:#f5f5f5; + margin:20px -20px; + padding: 0 20px; + border-top:1px solid #eee; + border-bottom:1px solid #eee; + font-size:14px; + color:#777; + } + } + .row_title { + font-weight:bold; + color:#444; + &:hover { + color:#444; + text-decoration:underline; + } + } + + li, .wll { + padding:10px; + &:first-child { + @include round-borders-top(4px); + border-top:none; + } + + &:last-child { + @include round-borders-bottom(4px); + border:none; + } + } + + .ui-box-body { + padding:10px; + } +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/buttons.scss b/app/assets/stylesheets/gitlab_bootstrap/buttons.scss new file mode 100644 index 00000000..380fc3e3 --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/buttons.scss @@ -0,0 +1,93 @@ +.btn { + @include bg-gradient(#f7f7f7, #d5d5d5); + border-color:#aaa; + &:hover { + @include bg-gray-gradient; + border-color:#bbb; + color:#333; + } + + &.primary { + background:#2a79A3; + @include bg-gradient(#47A7b7, #2585b5); + border-color: #2A79A3; + color:#fff; + text-shadow: 0 1px 1px #268; + &:hover { + background:$blue_link; + color:#fff; + } + + &.disabled { + color:#fff; + background:#29B; + } + } + + &.success { + @extend .btn-success; + + &:hover { + @extend .btn-success; + background: #51a351; + } + + &.disabled { + color:#fff; + background:#2b2; + } + } + + &.save-btn { + @extend .wide; + @extend .primary; + } + + &.cancel-btn { + float:right; + } + + &.wide { + padding-left:30px; + padding-right:30px; + } + + &.danger { + @extend .btn-danger; + border-color: #BD362F; + + &:hover { + color:#fff; + background: #EE4E49; + } + } + + &.danger { + @extend .btn-danger; + } + + &.small { + @extend .btn-small; + } + + &.active { + border-color:#aaa; + background-color:#ccc; + } + + &.very_small { + font-size:11px; + padding:2px 6px; + margin:2px; + } + + &.grouped { + margin-right:7px; + float:left; + } + + &.padded { + margin-right:3px; + padding:4px 10px 4px; + } +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/common.scss b/app/assets/stylesheets/gitlab_bootstrap/common.scss new file mode 100644 index 00000000..b9459ee6 --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/common.scss @@ -0,0 +1,84 @@ +/** COLORS **/ +.cgray { color:gray } +.cred { color:#D12F19 } +.cgreen { color:#4a2 } +.cblack { color:#111 } +.cdark { color:#444 } +.cwhite { color:#fff!important } +.bgred { background:#F2DEDE!important } + +/** COMMON CLASSES **/ +.left { float:left } +.right { float:right!important } +.width-50p { width:50% } +.width-49p { width:49% } +.width-30p { width:30% } +.width-65p { width:65% } +.width-100p { width:100% } +.append-bottom-10 { margin-bottom:10px } +.append-bottom-20 { margin-bottom:20px } +.prepend-top-10 { margin-top:10px } +.prepend-top-20 { margin-top:20px } +.padded { padding:20px } +.ipadded { padding:20px!important } +.lborder { border-left:1px solid #eee } +.no-padding { padding:0 !important; } +.underlined { border-bottom: 1px solid #CCC; } +.no-borders { border:none; } +.vlink { color: $link_color !important; } +.borders { border: 1px solid #ccc; @include shade; } +.hint { font-style: italic; color: #999; } + +/** PILLS & TABS**/ +.nav-pills a:hover { background-color:#888; } +.nav-pills .active a { background-color: $style_color; } +.nav-tabs > li > a, .nav-pills > li > a { color:$style_color; } +.nav.nav-tabs { + li { + > a { + padding:8px 20px; + margin-right: 7px; + border-color: #EEE; + color:#888; + border-bottom: 1px solid #ddd; + .badge { + background-color: #eee; + color:#888; + text-shadow:0 1px 1px #fff; + } + } + &.active { + > a { + border-color: #CCC; + border-bottom: 1px solid #fff; + color:#333; + } + } + } +} + +/** ALERT MESSAGES **/ +.alert-message { @extend .alert; } +.alert-messag.success { @extend .alert-success; } +.alert-message.error { @extend .alert-error; } + +/** AVATARS **/ +img.avatar { float:left; margin-right:15px; width:40px; border:1px solid #ddd; padding:1px; } +img.avatar.s16 { width:16px; height:16px; } +img.avatar.s24 { width:24px; height:24px; } +img.avatar.s32 { width:32px; height:32px; } +img.lil_av { padding-left: 4px; padding-right:3px; } + +/** HELPERS **/ +.nothing_here_message { text-align:center; padding:20px; color:#777; } +p.slead { color:#456; font-size:16px; margin-bottom: 12px; font-weight: 200; line-height: 24px; } + +/** FORMS **/ +input[type='search'].search-text-input { + background-image: url("icon-search.png"); + background-repeat: no-repeat; + background-position: 10px; + padding-left:25px; + @include border-radius(4px); + border:1px solid #ccc; +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/files.scss b/app/assets/stylesheets/gitlab_bootstrap/files.scss new file mode 100644 index 00000000..d39fdb27 --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/files.scss @@ -0,0 +1,156 @@ +/** + * File content holder + * + */ +.file_holder { + border:1px solid #BBB; + margin-bottom:1em; + @include solid_shade; + + .file_title { + border-bottom: 1px solid #bbb; + @include bg-dark-gray-gradient; + margin: 0; + font-weight: normal; + font-weight: bold; + text-align: left; + color: #666; + padding: 9px 10px; + height:18px; + + .options { + float:right; + margin-top: -5px; + } + + .file_name { + color:$style_color; + font-size:14px; + text-shadow: 0 1px 1px #fff; + small { + color:#999; + font-size:13px; + } + } + } + .file_content { + background:#fff; + font-size: 11px; + + &.wiki { + font-size: 13px; + code { + padding:0 4px; + } + padding:20px; + h1, h2 { + line-height: 46px; + } + h3, h4 { + line-height: 40px; + } + } + + &.image_file { + background:#eee; + text-align:center; + img { + padding:100px; + max-width:300px; + } + } + + &.blob_file { + + } + + /** + * Blame file + */ + &.blame { + tr { + border-bottom: 1px solid #eee; + } + td { + padding:5px; + } + .author, + .blame_commit { + background:#f5f5f5; + vertical-align:top; + } + .lines { + pre { + padding:0; + margin:0; + background:none; + border:none; + } + } + } + + &.logs { + background:#eee; + max-height: 700px; + overflow-y: auto; + + ol { + margin-left:40px; + padding: 10px 0; + border-left: 1px solid #CCC; + margin-bottom:0; + background: white; + li { + color:#888; + p { + margin:0; + color:#333; + line-height:24px; + padding-left: 10px; + } + + &:hover { + background:$hover; + } + } + } + } + + /** + * Code file + */ + &.code { + padding:0; + td.code { + width: 100%; + .highlight { + margin-left: 55px; + overflow:auto; + overflow-y:hidden; + } + } + .highlight pre { + white-space: pre; + word-wrap:normal; + } + + table.highlighttable { + border: none; + } + body.project-page table.highlighttable td { border: none } + table.highlighttable tr:hover { background:none;} + + table.highlighttable pre{ + line-height:16px !important; + font-size:12px !important; + } + + table.highlighttable .linenodiv pre { + text-align: right; + padding-right: 4px; + color:#666; + } + } + } +} + diff --git a/app/assets/stylesheets/gitlab_bootstrap/lists.scss b/app/assets/stylesheets/gitlab_bootstrap/lists.scss new file mode 100644 index 00000000..402ba04b --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/lists.scss @@ -0,0 +1,30 @@ +/** LISTS **/ + +ul { + /** + * List li block element #1 + * + */ + .wll { + background-color: #FFF; + padding: 10px 5px; + min-height: 20px; + border-bottom: 1px solid #eee; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); + + &.smoke { background-color:#f5f5f5; } + &:hover { background:$hover; } + &:last-child { border:none } + .author { color: #999; } + + p { + padding-top:5px; + margin:0; + color:#222; + img { + position:relative; + top:3px; + } + } + } +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/tables.scss b/app/assets/stylesheets/gitlab_bootstrap/tables.scss new file mode 100644 index 00000000..29293867 --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/tables.scss @@ -0,0 +1,45 @@ +table { + @extend .table; + @extend .table-striped; + @include solid_shade; + border:1px solid #bbb; + width:100%; + + th { + font-weight: bold; + vertical-align: middle; + border-bottom: 1px solid #bbb; + text-shadow: 0 1px 1px #fff; + @include bg-dark-gray-gradient; + } + + th, td { + padding: 8px; + line-height: 18px; + text-align: left; + } + + td { + border-color:#f1f1f1; + &:first-child { + border-left:1px solid #bbb; + } + + &:last-child { + border-right:1px solid #bbb; + } + } + + &.bordered { + @extend .table-bordered; + } + + &.lite { + border:none; + box-shadow:none; + tr, td { + border:none; + background:none !important; + } + } +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/typography.scss b/app/assets/stylesheets/gitlab_bootstrap/typography.scss new file mode 100644 index 00000000..97e85492 --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/typography.scss @@ -0,0 +1,71 @@ +/** + * Headers + * + */ +h3, h4, h5, h6 { line-height: 36px; } +h5 { font-size:14px; } +h3.page_title { + color:#456; + font-size:20px; + font-weight: normal; + line-height: 28px; +} + +/** CODE **/ +pre { + font-family:'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; + + &.dark { + background: #333; + color:#f5f5f5; + } +} + +/** + * Links + * + */ +a { + outline: none; + color: $link_color; + &:hover { + text-decoration:none; + color: $blue_link; + } + + &.btn { + color: $style_color; + &:hover { + color: $style_color; + } + } + + &.dark { + color: $style_color; + } + + &.lined { + text-decoration:underline; + &:hover { text-decoration:underline; } + } + + &.gray { + color:gray; + } + + &.supp_diff_link { + text-align:center; + padding:20px 0; + background:#f1f1f1; + width:100%; + float:left; + } + + &.neib { + margin-right:15px; + } +} + +a:focus { + outline: none; +} diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index a08f05bd..9b003b8a 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -1,12 +1,21 @@ -table.highlighttable -{ +table.highlighttable { margin:0px; padding:0px; font-size:12px; table-layout:fixed; background: #EEE; + box-shadow: none; + border: none; + td.linenos { + background:#eee; + border-left:none; + } + td.code { + border-right:none; + } } + td.code, td.linenos{ padding:0; diff --git a/app/assets/stylesheets/main.scss b/app/assets/stylesheets/main.scss index ad8be0bc..75001d3a 100644 --- a/app/assets/stylesheets/main.scss +++ b/app/assets/stylesheets/main.scss @@ -1,30 +1,14 @@ @import "bootstrap"; @import "bootstrap-responsive"; -/** GITLAB colors **/ -$text_color:#222; -$lite_text_color: #666; -$link_color:#2A79A3; -$active_link_color:#2FA0BB; -$active_bg_color:#79C3E0; -$active_bd_color: #2FA0BB; -$border_color:#CCC; -$lite_border_color:#EEE; -$min_app_width:980px; -$max_app_width:980px; -$app_padding:20px; -$bg_color: #FFF; -$styled_border_color: #2FA0BB; -$color: "#4BB8D2"; +/** GitLab colors **/ +$link_color:#3A89A3; $blue_link: #2fa0bb; +$style_color: #474d57; +$hover: #fdf5d9; - -/** Style colors **/ -$style_color: #474D57; -$hover: #FDF5D9; - -/** GITLAB Fonts **/ -@font-face { font-family: Korolev; src: url('korolev-medium-compressed.otf'); } +/** GitLab Fonts **/ +@font-face { font-family: Korolev; src: url('korolev-medium-compressed.otf'); } /** MIXINS **/ @mixin shade { @@ -72,7 +56,27 @@ $hover: #FDF5D9; border-radius: $radius; } +@mixin bg-gradient($from, $to) { + background-image: -webkit-gradient(linear, 0 0, 0 100%, from($from), to($to)); + background-image: -webkit-linear-gradient($from, $to); + background-image: -moz-linear-gradient($from, $to); + background-image: -o-linear-gradient($from, $to); +} +@mixin bg-gray-gradient { + background:#eee; + background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); + background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); + background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); + background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); +} + +@mixin bg-dark-gray-gradient { + background:#eee; + background-image: -webkit-linear-gradient(#e9e9e9, #d7d7d7); + background-image: -moz-linear-gradient(#e9e9e9, #d7d7d7); + background-image: -o-linear-gradient(#e9e9e9, #d7d7d7); +} /** * Header of application. @@ -109,11 +113,17 @@ $hover: #FDF5D9; @import "themes/ui_modern.scss"; /** - * Gitlab bootstrap. + * GitLab bootstrap. * Overrides some styles of twitter bootstrap. - * Also give some common classes for gitlab app + * Also give some common classes for GitLab app */ -@import "gitlab_bootstrap.scss"; +@import "gitlab_bootstrap/common.scss"; +@import "gitlab_bootstrap/typography.scss"; +@import "gitlab_bootstrap/buttons.scss"; +@import "gitlab_bootstrap/blocks.scss"; +@import "gitlab_bootstrap/files.scss"; +@import "gitlab_bootstrap/tables.scss"; +@import "gitlab_bootstrap/lists.scss"; /** @@ -125,7 +135,6 @@ $hover: #FDF5D9; */ @import "common.scss"; - /** * Styles related to specific part of app */ @@ -151,6 +160,11 @@ $hover: #FDF5D9; */ @import "sections/notes.scss"; +/** + * This file represent profile styles + */ +@import "sections/profile.scss"; + /** * Devise styles */ diff --git a/app/assets/stylesheets/projects.css.scss b/app/assets/stylesheets/projects.css.scss deleted file mode 100644 index 4bdf5dee..00000000 --- a/app/assets/stylesheets/projects.css.scss +++ /dev/null @@ -1,385 +0,0 @@ -.git_url_wrapper { margin-right:50px } - -.sidebar aside a{ - display: block; - position: relative; - padding: 15px 10px; - margin: 10px 0 0 0; - - font-size:13px; - font-weight:bold; - color:#333; - - &.current { - color: white; - background: $active_bg_color; - border: 1px solid $active_bd_color; - border-radius:5px; - - -webkit-border-top-right-radius: 0; - -webkit-border-bottom-right-radius: 0; - -moz-border-radius-topright: 0px; - -moz-border-radius-bottomright: 0px; - border-top-right-radius: 0; - border-bottom-right-radius: 0; - margin-right: -1px; - } -} - -body table .commit a{color: #{$blue_link}} -body table th, body table td{ border-bottom: 1px solid #DEE2E3;} -body .fixed{position: fixed; } - -/** File stat **/ -.file_stats { - span { - img { - width:14px; - float:left; - margin-right: 6px; - padding:2px 0; - } - } -} - -.round-borders { - @include round-borders-all(4px); - padding: 4px 0px; -} - -table.round-borders { - float:left; - text-align: left; -} - - - -/** PROJECTS **/ -input.ssh_project_url { - padding:5px; - margin:0px; - float:right; - width:400px; - text-align:center; -} - -#projects-list .project { - height:50px; -} - -#tree-slider .tree-item, -#projects-list .project, -#snippets-table .snippet, -#issues-table .issue{ - cursor:pointer; -} - -.clear { - clear: both; -} - - - -#user_projects_limit{ - width: 60px; -} - -.handle:hover{ - cursor: move; -} - -.project-refs-form { - span { - background: none !important; - position:static !important; - width:auto !important; - height: auto !important; - } -} - -.project-refs-select { - width:200px; -} - -.filter .left { margin-right:15px; } - -body table .commit { - a.tree-commit-link { - color:#444; - &:hover { - text-decoration:underline; - } - } -} - -/** NEW PROJECT **/ -.new-project-hodler { - .icon span { background-position: -31px -70px; } - td { border-bottom: 1px solid #DEE2E3; } -} - -/** Feed entry **/ -.commit, -.snippet, -.message { - .title { - color:#666; - a { color:#666 !important; } - p { margin-top:0px; } - } - .author { color: #999 } -} - -/** JQuery UI **/ -.ui-autocomplete { @include round-borders-all(5px); } -.ui-menu-item { cursor: pointer } -.ui-selectmenu{ - @include round-borders-all(4px); - margin-right:10px; - font-size:1.5em; - height:auto; - font-weight:bold; - .ui-selectmenu-status { - padding:3px 10px; - } -} - -#holder { - background:#FAFAFA; - border: 1px solid #EEE; - cursor: move; - height: 70%; - overflow: hidden; -} - -/* Project Dashboard Page */ -html, body { height: 100%; } - -.news-feed h2{float: left;} -.news-feed .project-updates {margin-bottom: 20px; display: block; width: 100%;} -.news-feed .project-updates .data{ padding: 0} -.news-feed .project-updates a.project-update {padding: 10px; border-bottom: 1px solid #eee; overflow: hidden; display: block;} -.news-feed .project-updates a.project-update:last-child{border-bottom: 0} -.news-feed .project-updates a.project-update img{float: left; margin-right: 10px;} -.news-feed .project-updates a.project-update span.update-title, .dashboard-page .news-feed .project-updates li a span.update-author{display: block;} -.news-feed .project-updates a.project-update span.update-title{margin-bottom: 10px} -.news-feed .project-updates a.project-update span.update-author{color: #999; font-weight: normal; font-style: italic;} -.news-feed .project-updates a.project-update span.update-author strong{font-weight: bold; font-style: normal;} -/* eo Dashboard Page */ - - -/** Update entry **/ -.update-data { padding: 0 } -.update-data { width:100%; } -.update-data.ui-box .data { padding:0; } -a.update-item {padding: 10px; border-bottom: 1px solid #eee; overflow: hidden; display: block;} -a.update-item:last-child{border-bottom: 0} -a.update-item img{float: left; margin-right: 10px;} -a.update-item span.update-title, .dashboard-page .news-feed .project-updates li a span.update-author{display: block;} -a.update-item span.update-title{margin-bottom: 10px} -a.update-item span.update-author{color: #999; font-weight: normal; font-style: italic;} -a.update-item span.update-author strong{font-weight: bold; font-style: normal;} - - -body .team_member_new .span-6, .team_member_edit .span-6{ padding:10px 0; } - -body.projects-page input.text.git-url.project_list_url { width:165px; } - - -body table.no-borders th { - background:none; - border-bottom:1px solid #CCC; - color:#333; -} - -body table.no-borders tr, -body table.no-borders td{ - border:none; -} - -.ajax-tab-loading { - padding:40px; - display:none; -} - -#tree-content-holder { float:left; width:100%; } - -#tree-readme-holder { - float:left; - width:100%; - - .readme { - @include round-borders-all(4px); - padding: 4px 15px; - background:#F7F7F7; - } -} - - - -/* Commit Page */ -.entity-info {float: right;} -.entity-button{ - background-image: -webkit-gradient(linear, 0 0, 0 26, color-stop(0.192, #fff), to(#f4f4f4)); - background-image: -webkit-linear-gradient(#fff 19.2%, #f4f4f4); - background-image: -moz-linear-gradient(#fff 19.2%, #f4f4f4); - background-image: -o-linear-gradient(#fff 19.2%, #f4f4f4); - box-shadow: 0 -1px 0 white inset; - display: block; - border: 1px solid #eee; - border-radius: 5px; - margin-bottom: 2px; - position: relative; - padding: 4px 10px; - font-size: 11px; - padding-right: 20px; -} - -.entity-button i{ - background: url('images.png') no-repeat -138px -27px; - width: 6px; - height: 9px; - float: right; - position: absolute; - top: 6px; - right: 5px; -} -.box-arrow{float: right; background: #E3E5EA; padding: 10px; border-radius: 5px; margin-top: 2px; text-shadow: none; color: #999; margin: 1.5em 0;} - -h4.dash-tabs { - margin: 0; - border-bottom: 1px solid #ccc; - padding: 10px 10px; - font-size: 11px; - padding-left:20px; - font-weight: bold; text-transform: uppercase; - background: #F7F7F7; - margin-bottom:20px; - height:13px; - -} - -.dash-button { - border-right: 1px solid #ddd; - background:none; - padding: 10px 15px; - float:left; - position:relative; - top:-10px; - left:0px; - height:13px; - - &:first-child { - border-left: 1px solid #ddd; - } - &.active { - background: #eaeaea; - } -} - - -.dashboard-loader { - float:right; - margin-right:30px; - display:none; -} - - -.merge-tabs { - margin: 0; - border: 1px solid #ccc; - padding: 5px; - font-size: 12px; - background: #F7F7F7; - margin-bottom:20px; - height:26px; - - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - border-radius: 4px; - - .tab { - font-weight: bold; - border-right: 1px solid #ddd; - background:none; - padding: 10px; - min-width:60px; - float:left; - position:relative; - top:-5px; - left:-5px; - height:16px; - padding-left:34px; - - span { - width: 20px; - height: 20px; - display: inline-block; - position: absolute; - left: 8px; - top: 8px; - } - - &.active { - background: #eaeaea; - } - } -} -.merge-tabs.repository .tab span{ background: url("images.png") no-repeat -38px -77px; } -.activities-tab span { background: url("images.png") no-repeat -161px -1px; } -.stat-tab span, -.team-tab span, -.snippets-tab span { background: url("images.png") no-repeat -38px -77px; } -.files-tab span { background: url("images.png") no-repeat -112px -23px; } - -.merge-notes-tab span { background: url("images.png") no-repeat -161px -1px; } -.merge-commits-tab span { background: url("images.png") no-repeat -86px 1px; } -.merge-diffs-tab span { background: url("images.png") no-repeat -118px 1px; } -.merge-tabs .dashboard-loader { padding:8px; } - -.user-mention { - color: #2FA0BB; - font-weight: bold; -} - -.author { - color: #999; -} - - - - -.dark_scheme_box { - padding:20px 0; - - label { - float:left; - box-shadow: 0 0px 5px rgba(0,0,0,.3); - - img { - } - } -} - -a.project-update.titled { - position: relative; - padding-left: 235px !important; - - .title-block { - padding: 10px; - width: 205px; - position: absolute; - left: 0; - top: 0; - } -} - -.add_new { - float: right; - background: #A6B807; - color: white; - padding: 4px 10px; - @include round-borders-all(4px); - font-size:11px; - margin: 10px 0; -} diff --git a/app/assets/stylesheets/ref_select.scss b/app/assets/stylesheets/ref_select.scss index 6f6a1bc9..ed6760f1 100644 --- a/app/assets/stylesheets/ref_select.scss +++ b/app/assets/stylesheets/ref_select.scss @@ -12,37 +12,45 @@ width:120px; } -.project-refs-form .chzn-container { +.project-refs-form .chzn-container { position: relative; top: 0; left: 0; margin-right: 10px; - .chzn-drop { + .chzn-drop { margin:7px 0; - border: 1px solid #CCC; - min-width: 300px; + min-width: 400px; + border: 2px solid $blue_link; + @include border-radius(4px); - .chzn-results { + .chzn-results { max-height:300px; + + .group-result { + color: $blue_link; + } + .active-result { + &.highlighted { + background: $blue_link; + } + } } .chzn-search input { - min-width:200px; + min-width:365px; } } - .chzn-single { - background:#ddd; - //border:none; - //box-shadow:none; + .chzn-single { + @include bg-gray-gradient; - div { + div { background:transparent; border-left:none; } - span { + span { font-weight: normal; } } diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss index e2db701d..75e38aee 100644 --- a/app/assets/stylesheets/sections/commits.scss +++ b/app/assets/stylesheets/sections/commits.scss @@ -206,4 +206,24 @@ min-width:65px; font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; } + + .commit-author-name { + color: #777; + } +} + +.diff_file_header a, +.file_stats a { + color:$style_color; +} + +.file_stats { + span { + img { + width:14px; + float:left; + margin-right:6px; + padding:2px 0; + } + } } diff --git a/app/assets/stylesheets/sections/graph.scss b/app/assets/stylesheets/sections/graph.scss index 33d91de5..2aa4463e 100644 --- a/app/assets/stylesheets/sections/graph.scss +++ b/app/assets/stylesheets/sections/graph.scss @@ -6,11 +6,7 @@ h4 { padding:0 10px; border-bottom: 1px solid #bbb; - background:#eee; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); + @include bg-gray-gradient; } .graph { diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/sections/header.scss index d0fb662e..8328a5ab 100644 --- a/app/assets/stylesheets/sections/header.scss +++ b/app/assets/stylesheets/sections/header.scss @@ -22,7 +22,7 @@ header { * */ .app_logo { - width:230px; + width:200px; float:left; position:relative; top:-5px; @@ -31,7 +31,7 @@ header { h1 { padding-top: 5px; - width:102px; + width:90px; background: url('logo_dark.png') no-repeat 0px -3px; float:left; margin-left:5px; diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss index 1b61ec3f..10def510 100644 --- a/app/assets/stylesheets/sections/issues.scss +++ b/app/assets/stylesheets/sections/issues.scss @@ -1,55 +1,55 @@ -.issue_form_box { +.issue_form_box { @extend .main_box; - .issue_title { + .issue_title { @extend .top_box_content; - .clearfix { - margin-bottom:0px; - input { + .clearfix { + margin-bottom:0px; + input { @extend .span8; } } } - .issue_middle_block { + .issue_middle_block { @extend .middle_box_content; height:30px; - .issue_assignee { + .issue_assignee { @extend .span6; float:left; } - .issue_milestone { + .issue_milestone { @extend .span4; float:left; } } - .issue_description { + .issue_description { @extend .bottom_box_content; } } -.issues_table { - .issue { +.issues_table { + .issue { padding:7px 10px; - .issue_check { + .issue_check { float:left; padding: 8px 0; padding-right: 8px; min-width: 15px; } - p { + p { padding-top:0; padding-bottom:2px; } - img.avatar { + img.avatar { width:32px; margin-top:4px; } } } -input.check_all_issues { +input.check_all_issues { float:left; padding: 0; margin:0; @@ -59,36 +59,41 @@ input.check_all_issues { height: 22px; } -.issues_content { - .title { +.issues_content { + .title { height: 40px; } } -#issues-table-holder { - .issues_filters { - form { - padding:0; - margin:0; - margin-top:7px - } - } +@media (min-width: 800px) { .issues_filters select { width:160px; } } +@media (min-width: 1000px) { .issues_filters select { width:200px; } } +@media (min-width: 1200px) { .issues_filters select { width:220px; } } - .issues_bulk_update { - margin: 0; - form { + +#issues-table-holder { + .issues_filters { + form { padding:0; margin:0; margin-top:7px } - .update_selected_issues { + } + + .issues_bulk_update { + margin: 0; + form { + padding:0; + margin:0; + margin-top:7px + } + .update_selected_issues { position:relative; top:-2px; margin-left:4px; float:left; } - - .update_issues_text { + + .update_issues_text { padding:3px; line-height: 18px; float:left; @@ -96,6 +101,15 @@ input.check_all_issues { } } -#update_status { +#update_status { width:100px; } + + +/** + * Milestones list + * + */ +.milestone { + @extend .wll; +} diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss index ec84a64e..c932f0fc 100644 --- a/app/assets/stylesheets/sections/merge_requests.scss +++ b/app/assets/stylesheets/sections/merge_requests.scss @@ -1,52 +1,35 @@ -/** +/** * MR form * */ -.mr_branch_box { +.mr_branch_box { @extend .ui-box; margin-bottom:20px; - .body { + .body { background:#f1f1f1; } - .commit { - margin:0; - padding:0; - padding: 5px; - margin-bottom: 5px; - - .committed_ago { - display:none; - } - .browse_code_link_holder { - display:none; - } - list-style:none; - &:hover { - background:none; - } - } } /** * MR -> show: Automerge widget * */ -.automerge_widget { - &.can_be_merged { +.automerge_widget { + &.can_be_merged { background: #DFF0D8; } - form { + form { margin-bottom:0; - .clearfix { + .clearfix { margin-bottom:0; } } - .accept_group { + .accept_group { float:left; border: 1px solid #ADA; padding: 2px; @@ -54,28 +37,29 @@ border-radius: 5px; background: #CEB; - .accept_merge_request { + .accept_merge_request { + font-size:13px; float:left; } - .remove_branch_holder { + .remove_branch_holder { margin-left:20px; margin-right:10px; float:left; } - label { + label { color:#444; } } - .how_to_merge_link { + .how_to_merge_link { @extend .primary; } } -.mr_nav_tabs { - li { - a { +.mr_nav_tabs { + li { + a { font-weight:bold; padding:8px 20px; text-align:center; @@ -83,19 +67,57 @@ } } -li.merge_request { +li.merge_request { padding:7px 10px; - img.avatar { + img.avatar { width: 32px; margin-top: 4px; } - p { + p { padding: 0px; padding-bottom: 2px; } } -.merge_in_progress { +.merge_in_progress { @extend .padded; @extend .append-bottom-10; } + +.label_branch { + @include round-borders-all(4px); + padding:2px 4px; + border:none; + font-size:14px; + background: #474D57; + color:#fff; + font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; +} + +.mr_source_commit, +.mr_target_commit { + .commit { + margin:0; + padding:0; + padding: 5px; + margin-bottom: 5px; + .avatar { position:relative } + .row_title { + color:#444; + } + .commit-author-name, + .dash, + .committed_ago, + .browse_code_link_holder { + display:none; + } + list-style:none; + &:hover { + background:none; + } + } +} + +.mr_direction_tip { + margin-top:40px +} diff --git a/app/assets/stylesheets/sections/nav.scss b/app/assets/stylesheets/sections/nav.scss index 4a773644..2d902918 100644 --- a/app/assets/stylesheets/sections/nav.scss +++ b/app/assets/stylesheets/sections/nav.scss @@ -2,21 +2,17 @@ * Main Menu of Application * */ -ul.main_menu { +ul.main_menu { border-radius: 4px; margin: auto; margin:30px 0; - background:#eee; - border:1px solid #bbb; + border:1px solid #AAA; height:37px; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); + @include bg-gray-gradient; position:relative; overflow:hidden; @include shade; - .count { + .count { position: relative; top: -1px; display: inline-block; @@ -33,12 +29,12 @@ ul.main_menu { border-radius: 8px; -moz-border-radius: 8px; } - .label { + .label { background:$hover; text-shadow:none; color:$style_color; } - li { + li { list-style-type: none; margin: 0; display: table-cell; @@ -47,7 +43,7 @@ ul.main_menu { border-left: 1px solid #EEE; border-bottom:2px solid #CFCFCF; - &:first-child{ + &:first-child{ -webkit-border-top-left-radius: 4px; -webkit-border-bottom-left-radius: 4px; -moz-border-radius-topleft: 4px; @@ -57,31 +53,30 @@ ul.main_menu { border-left: 0; } - &.current { + &.current { background-color:#D5D5D5; - border-bottom: 2px solid $style_color; border-right: 1px solid #BBB; border-left: 1px solid #BBB; border-radius: 0 0 1px 1px; - &:first-child{ + &:first-child{ border-bottom:none; border-left:none; } } - &.home { - a { + &.home { + a { background: url(home_icon.PNG) no-repeat center center; text-indent:-9999px; min-width:20px; - img { + img { position:relative; top:4px; } } } } - a { + a { display: block; text-align: center; font-weight:bold; @@ -89,7 +84,7 @@ ul.main_menu { line-height:36px; color: $style_color; text-shadow:0 1px 1px white; - + padding:0 10px; } } /* diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss index 30587ef5..267a9b43 100644 --- a/app/assets/stylesheets/sections/notes.scss +++ b/app/assets/stylesheets/sections/notes.scss @@ -2,18 +2,14 @@ * Notes * */ -#notes-list, -#new_notes_list { +#notes-list, +#new-notes-list { display:block; list-style:none; margin:0px; padding:0px; } -#new_notes_list li:last-child{ - border-bottom:1px solid #aaa; -} - .issue_notes, .wiki_notes { .note_content { @@ -30,17 +26,21 @@ } #new_note { - #note_note { - height:25px; - } .attach_holder { display:none; } } -.note { - padding: 8px 0; - border-bottom: 1px solid #eee; +.preview_note { + margin: 2px; + border: 1px solid #ddd; + padding: 10px; + min-height: 60px; + background:#f5f5f5; +} + +.note { + padding: 8px 0; overflow: hidden; display: block; img {float: left; margin-right: 10px;} @@ -49,19 +49,36 @@ .note-author { color: $style_color;} .note-title { margin-left:45px; padding-top: 5px;} - .avatar { + .avatar { margin-top:3px; } - .delete-note { - display:none; + .delete-note { + display:none; float:right; } - &:hover { + &:hover { .delete-note { display:block; } } } +#notes-list:not(.reversed) .note, +#new-notes-list:not(.reversed) .note { + border-bottom: 1px solid #eee; +} +#notes-list.reversed .note, +#new-notes-list.reversed .note { + border-top: 1px solid #eee; +} + +/* mark vote notes */ +.voting_notes .note { + padding: 8px 0; +} + +.notes-status { + margin: 18px; +} p.notify_controls input{ @@ -72,18 +89,18 @@ p.notify_controls span{ font-weight: 700; } -tr.line_notes_row { +tr.line_notes_row { border-bottom:1px solid #DDD; border-left: 7px solid #2A79A3; - &.reply { + &.reply { background:#eee; border-left: 7px solid #2A79A3; border-top:1px solid #ddd; - td { + td { padding:7px 10px; } - a.line_note_reply_link { + a.line_note_reply_link { @include round-borders-all(4px); padding: 3px 10px; margin-left:5px; @@ -92,9 +109,9 @@ tr.line_notes_row { border-color: #2A79A3; } } - ul { + ul { margin:0; - li { + li { padding:0; border:none; } @@ -103,26 +120,26 @@ tr.line_notes_row { .line_notes_row, .per_line_form { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } -.per_line_form { +.per_line_form { background:#f5f5f5; border-top:1px solid #eee; form { margin: 0; } - td { + td { border-bottom:1px solid #ddd; } - .note_actions { + .note_actions { margin:0; padding-top: 10px; - .buttons { + .buttons { float:left; width:300px; } - .options { - .labels { + .options { + .labels { float:left; padding-left:10px; - label { + label { padding: 6px 0; margin: 0; width:120px; @@ -132,7 +149,7 @@ tr.line_notes_row { } } -td .line_note_link { +td .line_note_link { position:absolute; margin-left:-70px; margin-top:-10px; @@ -144,14 +161,14 @@ td .line_note_link { opacity: 0.0; filter: alpha(opacity=0); - &:hover { + &:hover { opacity: 1.0; filter: alpha(opacity=100); } } .diff_file_content tr.line_holder:hover > td { background: $hover !important; } -.diff_file_content tr.line_holder:hover > td .line_note_link { +.diff_file_content tr.line_holder:hover > td .line_note_link { opacity: 1.0; filter: alpha(opacity=100); } @@ -169,8 +186,8 @@ td .line_note_link { margin: 0; } - .note_advanced_opts { - h6 { + .note_advanced_opts { + h6 { line-height: 32px; padding-right: 15px; } @@ -183,7 +200,7 @@ td .line_note_link { overflow:hidden; margin:0 0 5px !important; - .input_file { + .input_file { .file_upload { position: absolute; right:14px; @@ -196,7 +213,7 @@ td .line_note_link { height:28px; overflow:hidden; } - .input-file { + .input-file { width: 260px; height: 41px; float: right; @@ -204,3 +221,8 @@ td .line_note_link { } } } + +.note-text { + border: 1px solid #aaa; + box-shadow:none; +} diff --git a/app/assets/stylesheets/sections/profile.scss b/app/assets/stylesheets/sections/profile.scss new file mode 100644 index 00000000..206da3a9 --- /dev/null +++ b/app/assets/stylesheets/sections/profile.scss @@ -0,0 +1,8 @@ +.profile_history { + .event_feed { + min-height:20px; + .avatar { + width:20px; + } + } +} diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss index 0866b43f..92d0fc43 100644 --- a/app/assets/stylesheets/sections/projects.scss +++ b/app/assets/stylesheets/sections/projects.scss @@ -14,6 +14,32 @@ text-shadow: 0 1px 1px #fff; padding: 2px 10px; } + ul { + li { + padding:0; + a { + display:block; + .project_name { + color:#4fa2bd; + font-size:14px; + line-height:18px; + } + .arrow { + float:right; + padding:10px; + margin:0; + } + .last_activity { + padding-top:5px; + display:block; + span, strong { + font-size:12px; + color:#666; + } + } + } + } + } @extend .leftbar; @extend .ui-box; } @@ -33,9 +59,38 @@ color:#888; } .btn { - padding:6px; + padding:6px 10px; margin-left:10px; margin-bottom:8px; } } + .adv_settings { + h6 { margin-left:40px; } + } +} + +.project_clone_panel { + @include border-radius(4px); + @include bg-gray-gradient; + padding: 4px 7px; + border: 1px solid #CCC; + margin-bottom:5px; +} + +.project_clone_holder { + input[type="text"] { + border: 1px solid #BBB; + box-shadow: none; + } +} + +.save-project-loader { + img { + margin-top:50px; + margin-bottom:50px; + } + h3 { + @extend .page_title; + } + } diff --git a/app/assets/stylesheets/sections/tree.scss b/app/assets/stylesheets/sections/tree.scss index 2663fc9a..07568706 100644 --- a/app/assets/stylesheets/sections/tree.scss +++ b/app/assets/stylesheets/sections/tree.scss @@ -1,4 +1,4 @@ -#tree-holder { +#tree-holder { #tree-content-holder { float:left; width:100%; @@ -11,40 +11,44 @@ padding:12px; background: #F7F7F7; - pre { + pre { overflow: auto; } } } - .tree_progress { + .tree_progress { display:none; margin:20px; - &.loading { + &.loading { display:block; } } #tree-slider { @include border-radius(0); - .tree-item { - &:hover { - td { background: $hover; } + .tree-item { + &:hover { + td { + background: $hover; + border-top:1px solid #FEA; + border-bottom:1px solid #FEA; + } cursor:pointer; } } } - .tree-item { - .tree-item-file-name { + .tree-item { + .tree-item-file-name { vertical-align:middle; - a { - &:hover { + a { + &:hover { color:$blue_link; } } - img { + img { position: relative; top:-1px; } @@ -52,41 +56,19 @@ } - #tree-slider { - @include solid_shade; - width:100%; - - border-color:#ccc; - - td { - padding:8px; - border-color:#f1f1f1; + #tree-slider { + td { background:#fafafa; } - - tr:first-child td:first-child, - tr:first-child td:last-child { - border-radius:0; - } - - th { - border-color: #CCC; - border-bottom: 1px solid #bbb; - background:#eee; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); - } } - .tree-commit-link { + .tree-commit-link { color:#333; } - a.tree-commit-link { + a.tree-commit-link { color: #666; - &:hover { + &:hover { text-decoration: underline; } } diff --git a/app/contexts/merge_requests_load.rb b/app/contexts/merge_requests_load.rb index 6778db3b..e2f68e38 100644 --- a/app/contexts/merge_requests_load.rb +++ b/app/contexts/merge_requests_load.rb @@ -1,13 +1,13 @@ class MergeRequestsLoad < BaseContext def execute - type = params[:f].to_i + type = params[:f] merge_requests = project.merge_requests merge_requests = case type - when 1 then merge_requests - when 2 then merge_requests.closed - when 3 then merge_requests.opened.assigned(current_user) + when 'all' then merge_requests + when 'closed' then merge_requests.closed + when 'assigned-to-me' then merge_requests.opened.assigned(current_user) else merge_requests.opened end.page(params[:page]).per(20) diff --git a/app/contexts/notes/load_context.rb b/app/contexts/notes/load_context.rb index c89a7d19..f92a7801 100644 --- a/app/contexts/notes/load_context.rb +++ b/app/contexts/notes/load_context.rb @@ -3,30 +3,31 @@ module Notes def execute target_type = params[:target_type] target_id = params[:target_id] - first_id = params[:first_id] - last_id = params[:last_id] + after_id = params[:after_id] + before_id = params[:before_id] @notes = case target_type - when "commit" - then project.commit_notes(project.commit(target_id)).fresh.limit(20) - when "snippet" - then project.snippets.find(target_id).notes - when "wall" - then project.common_notes.order("created_at DESC").fresh.limit(50) + when "commit" + project.commit_notes(project.commit(target_id)).fresh.limit(20) when "issue" - then project.issues.find(target_id).notes.inc_author.order("created_at DESC").limit(20) + project.issues.find(target_id).notes.inc_author.fresh.limit(20) when "merge_request" - then project.merge_requests.find(target_id).notes.inc_author.order("created_at DESC").limit(20) + project.merge_requests.find(target_id).notes.inc_author.fresh.limit(20) + when "snippet" + project.snippets.find(target_id).notes.fresh + when "wall" + # this is the only case, where the order is DESC + project.common_notes.order("created_at DESC, id DESC").limit(50) when "wiki" - then project.wikis.reverse.map {|w| w.notes.fresh }.flatten[0..20] + project.wiki_notes.limit(20) end - @notes = if last_id - @notes.where("id > ?", last_id) - elsif first_id - @notes.where("id < ?", first_id) - else + @notes = if after_id + @notes.where("id > ?", after_id) + elsif before_id + @notes.where("id < ?", before_id) + else @notes end end diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb index ad80f4d5..5152f6fa 100644 --- a/app/controllers/admin/dashboard_controller.rb +++ b/app/controllers/admin/dashboard_controller.rb @@ -1,8 +1,4 @@ -class Admin::DashboardController < ApplicationController - layout "admin" - before_filter :authenticate_user! - before_filter :authenticate_admin! - +class Admin::DashboardController < AdminController def index @workers = Resque.workers @pending_jobs = Resque.size(:post_receive) diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb index 7f832fd5..91a1d633 100644 --- a/app/controllers/admin/hooks_controller.rb +++ b/app/controllers/admin/hooks_controller.rb @@ -1,8 +1,4 @@ -class Admin::HooksController < ApplicationController - layout "admin" - before_filter :authenticate_user! - before_filter :authenticate_admin! - +class Admin::HooksController < AdminController def index @hooks = SystemHook.all @hook = SystemHook.new @@ -15,7 +11,7 @@ class Admin::HooksController < ApplicationController redirect_to admin_hooks_path, notice: 'Hook was successfully created.' else @hooks = SystemHook.all - render :index + render :index end end diff --git a/app/controllers/admin/logs_controller.rb b/app/controllers/admin/logs_controller.rb index c130b4b8..28c321a9 100644 --- a/app/controllers/admin/logs_controller.rb +++ b/app/controllers/admin/logs_controller.rb @@ -1,6 +1,2 @@ -class Admin::LogsController < ApplicationController - layout "admin" - before_filter :authenticate_user! - before_filter :authenticate_admin! +class Admin::LogsController < AdminController end - diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 80d11f03..24406525 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -1,7 +1,4 @@ -class Admin::ProjectsController < ApplicationController - layout "admin" - before_filter :authenticate_user! - before_filter :authenticate_admin! +class Admin::ProjectsController < AdminController before_filter :admin_project, only: [:edit, :show, :update, :destroy, :team_update] def index @@ -43,7 +40,7 @@ class Admin::ProjectsController < ApplicationController def update owner_id = params[:project].delete(:owner_id) - if owner_id + if owner_id @admin_project.owner = User.find(owner_id) end @@ -60,7 +57,7 @@ class Admin::ProjectsController < ApplicationController redirect_to admin_projects_url, notice: 'Project was successfully deleted.' end - private + private def admin_project @admin_project = Project.find_by_code(params[:id]) diff --git a/app/controllers/admin/resque_controller.rb b/app/controllers/admin/resque_controller.rb index dc575cc2..9d8e7e30 100644 --- a/app/controllers/admin/resque_controller.rb +++ b/app/controllers/admin/resque_controller.rb @@ -1,5 +1,4 @@ -class Admin::ResqueController < ApplicationController - layout 'admin' +class Admin::ResqueController < AdminController def show end -end \ No newline at end of file +end diff --git a/app/controllers/admin/team_members_controller.rb b/app/controllers/admin/team_members_controller.rb index 57803b01..07320805 100644 --- a/app/controllers/admin/team_members_controller.rb +++ b/app/controllers/admin/team_members_controller.rb @@ -1,8 +1,4 @@ -class Admin::TeamMembersController < ApplicationController - layout "admin" - before_filter :authenticate_user! - before_filter :authenticate_admin! - +class Admin::TeamMembersController < AdminController def edit @admin_team_member = UsersProject.find(params[:id]) end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 1e8f420b..e2d61864 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -1,8 +1,4 @@ -class Admin::UsersController < ApplicationController - layout "admin" - before_filter :authenticate_user! - before_filter :authenticate_admin! - +class Admin::UsersController < AdminController def index @admin_users = User.scoped @admin_users = @admin_users.filter(params[:filter]) @@ -24,7 +20,7 @@ class Admin::UsersController < ApplicationController @admin_user = User.find(params[:id]) UsersProject.user_bulk_import( - @admin_user, + @admin_user, params[:project_ids], params[:project_access] ) @@ -41,22 +37,22 @@ class Admin::UsersController < ApplicationController @admin_user = User.find(params[:id]) end - def block + def block @admin_user = User.find(params[:id]) if @admin_user.block redirect_to :back, alert: "Successfully blocked" - else + else redirect_to :back, alert: "Error occured. User was not blocked" end end - def unblock + def unblock @admin_user = User.find(params[:id]) if @admin_user.update_attribute(:blocked, false) redirect_to :back, alert: "Successfully unblocked" - else + else redirect_to :back, alert: "Error occured. User was not unblocked" end end diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb new file mode 100644 index 00000000..bce9f692 --- /dev/null +++ b/app/controllers/admin_controller.rb @@ -0,0 +1,11 @@ +# Provides a base class for Admin controllers to subclass +# +# Automatically sets the layout and ensures an administrator is logged in +class AdminController < ApplicationController + layout 'admin' + before_filter :authenticate_admin! + + def authenticate_admin! + return render_404 unless current_user.is_admin? + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7c1941ec..5ac5c639 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -11,11 +11,11 @@ class ApplicationController < ActionController::Base helper_method :abilities, :can? rescue_from Gitlab::Gitolite::AccessDenied do |exception| - render "errors/gitolite", layout: "error" + render "errors/gitolite", layout: "error", status: 500 end rescue_from Encoding::CompatibilityError do |exception| - render "errors/encoding", layout: "error", status: 404 + render "errors/encoding", layout: "error", status: 500 end rescue_from ActiveRecord::RecordNotFound do |exception| @@ -84,10 +84,6 @@ class ApplicationController < ActionController::Base abilities << Ability end - def authenticate_admin! - return render_404 unless current_user.is_admin? - end - def authorize_project!(action) return access_denied! unless can?(current_user, action, project) end @@ -116,22 +112,12 @@ class ApplicationController < ActionController::Base end end - def load_refs - if params[:ref].blank? - @branch = params[:branch].blank? ? nil : params[:branch] - @tag = params[:tag].blank? ? nil : params[:tag] - @ref = @branch || @tag || @project.try(:default_branch) || Repository.default_ref - else - @ref = params[:ref] - end - end - def render_404 render file: File.join(Rails.root, "public", "404"), layout: false, status: "404" end def require_non_empty_project - redirect_to @project unless @project.repo_exists? && @project.has_commits? + redirect_to @project if @project.empty_repo? end def no_cache_headers diff --git a/app/controllers/commits_controller.rb b/app/controllers/commits_controller.rb index 717912d9..1e7aec00 100644 --- a/app/controllers/commits_controller.rb +++ b/app/controllers/commits_controller.rb @@ -52,6 +52,7 @@ class CommitsController < ApplicationController @commits = result[:commits] @commit = result[:commit] @diffs = result[:diffs] + @refs_are_same = result[:same] @line_notes = [] @commits = CommitDecorator.decorate(@commits) @@ -59,12 +60,19 @@ class CommitsController < ApplicationController def patch @commit = project.commit(params[:id]) - + send_data( @commit.to_patch, type: "text/plain", disposition: 'attachment', - filename: (@commit.id.to_s + ".patch") + filename: "#{@commit.id}.patch" ) end + + protected + + def load_refs + @ref ||= params[:ref].presence || params[:branch].presence || params[:tag].presence + @ref ||= @ref || @project.try(:default_branch) || 'master' + end end diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 889a7d98..1d78a6d9 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -17,7 +17,7 @@ class IssuesController < ApplicationController before_filter :authorize_write_issue!, only: [:new, :create] # Allow modify issue - before_filter :authorize_modify_issue!, only: [:close, :edit, :update] + before_filter :authorize_modify_issue!, only: [:edit, :update] # Allow destroy issue before_filter :authorize_admin_issue!, only: [:destroy] @@ -37,7 +37,7 @@ class IssuesController < ApplicationController end def new - @issue = @project.issues.new + @issue = @project.issues.new(params[:issue]) respond_with(@issue) end @@ -60,7 +60,13 @@ class IssuesController < ApplicationController @issue.save respond_to do |format| - format.html { redirect_to project_issue_path(@project, @issue) } + format.html do + if @issue.valid? + redirect_to project_issue_path(@project, @issue) + else + render :new + end + end format.js end end @@ -81,8 +87,6 @@ class IssuesController < ApplicationController end def destroy - return access_denied! unless can?(current_user, :admin_issue, @issue) - @issue.destroy respond_to do |format| @@ -162,10 +166,10 @@ class IssuesController < ApplicationController def issues_filter { - all: "1", - closed: "2", - to_me: "3", - open: "0" + all: "all", + closed: "closed", + to_me: "assigned-to-me", + open: "open" } end end diff --git a/app/controllers/labels_controller.rb b/app/controllers/labels_controller.rb new file mode 100644 index 00000000..e703f822 --- /dev/null +++ b/app/controllers/labels_controller.rb @@ -0,0 +1,25 @@ +class LabelsController < ApplicationController + before_filter :authenticate_user! + before_filter :project + before_filter :module_enabled + + layout "project" + + # Authorize + before_filter :add_project_abilities + + # Allow read any issue + before_filter :authorize_read_issue! + + respond_to :js, :html + + def index + @labels = @project.issues.tag_counts_on(:labels).order('count DESC') + end + + protected + + def module_enabled + return render_404 unless @project.issues_enabled + end +end diff --git a/app/controllers/merge_requests_controller.rb b/app/controllers/merge_requests_controller.rb index 4938a294..187bb407 100644 --- a/app/controllers/merge_requests_controller.rb +++ b/app/controllers/merge_requests_controller.rb @@ -103,10 +103,12 @@ class MergeRequestsController < ApplicationController def branch_from @commit = project.commit(params[:ref]) + @commit = CommitDecorator.decorate(@commit) end def branch_to @commit = project.commit(params[:ref]) + @commit = CommitDecorator.decorate(@commit) end protected diff --git a/app/controllers/milestones_controller.rb b/app/controllers/milestones_controller.rb index 7acb3781..10f089f1 100644 --- a/app/controllers/milestones_controller.rb +++ b/app/controllers/milestones_controller.rb @@ -17,8 +17,8 @@ class MilestonesController < ApplicationController respond_to :html def index - @milestones = case params[:f].to_i - when 1; @project.milestones + @milestones = case params[:f] + when 'all'; @project.milestones else @project.milestones.active end diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index d472936b..2fb783b2 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -1,4 +1,9 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController + Gitlab.config.omniauth_providers.each do |provider| + define_method provider['name'] do + handle_omniauth + end + end # Extend the standard message generation to accept our custom exception def failure_message @@ -9,7 +14,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController error ||= env["omniauth.error.type"].to_s error.to_s.humanize if error end - + def ldap # We only find ourselves here if the authentication to LDAP was successful. @user = User.find_for_ldap_auth(request.env["omniauth.auth"], current_user) @@ -19,4 +24,27 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController sign_in_and_redirect @user end + private + + def handle_omniauth + oauth = request.env['omniauth.auth'] + provider, uid = oauth['provider'], oauth['uid'] + + if current_user + # Change a logged-in user's authentication method: + current_user.extern_uid = uid + current_user.provider = provider + current_user.save + redirect_to profile_path + else + @user = User.find_or_new_for_omniauth(oauth) + + if @user + sign_in_and_redirect @user + else + flash[:notice] = "There's no such user!" + redirect_to new_user_session_path + end + end + end end diff --git a/app/controllers/profile_controller.rb b/app/controllers/profile_controller.rb index a95a3310..7ddbfe11 100644 --- a/app/controllers/profile_controller.rb +++ b/app/controllers/profile_controller.rb @@ -16,9 +16,6 @@ class ProfileController < ApplicationController def token end - def password - end - def password_update params[:user].reject!{ |k, v| k != "password" && k != "password_confirmation"} @@ -32,10 +29,14 @@ class ProfileController < ApplicationController def reset_private_token current_user.reset_authentication_token! - redirect_to profile_token_path + redirect_to profile_account_path end - private + def history + @events = current_user.recent_events.page(params[:page]).per(20) + end + + private def user @user = current_user diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index bd7f811e..170b8892 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -50,7 +50,7 @@ class ProjectsController < ApplicationController respond_to do |format| format.html do - if @project.repo_exists? && @project.has_commits? + unless @project.empty_repo? @last_push = current_user.recent_push(@project.id) render :show else diff --git a/app/controllers/refs_controller.rb b/app/controllers/refs_controller.rb index 3f81a2ca..90361437 100644 --- a/app/controllers/refs_controller.rb +++ b/app/controllers/refs_controller.rb @@ -1,3 +1,5 @@ +require 'github/markup' + class RefsController < ApplicationController include Gitlab::Encode before_filter :project diff --git a/app/controllers/team_members_controller.rb b/app/controllers/team_members_controller.rb index 0846f096..a50dcd3e 100644 --- a/app/controllers/team_members_controller.rb +++ b/app/controllers/team_members_controller.rb @@ -5,7 +5,10 @@ class TeamMembersController < ApplicationController # Authorize before_filter :add_project_abilities before_filter :authorize_read_project! - before_filter :authorize_admin_project!, except: [:show] + before_filter :authorize_admin_project!, except: [:index, :show] + + def index + end def show @team_member = project.users_projects.find(params[:id]) @@ -17,13 +20,12 @@ class TeamMembersController < ApplicationController end def create - @team_member = UsersProject.new(params[:team_member]) - @team_member.project = project - if @team_member.save - redirect_to team_project_path(@project) - else - render "new" - end + @project.add_users_ids_to_team( + params[:user_ids], + params[:project_access] + ) + + redirect_to project_team_index_path(@project) end def update @@ -33,7 +35,7 @@ class TeamMembersController < ApplicationController unless @team_member.valid? flash[:alert] = "User should have at least one role" end - redirect_to team_project_path(@project) + redirect_to project_team_index_path(@project) end def destroy @@ -41,7 +43,7 @@ class TeamMembersController < ApplicationController @team_member.destroy respond_to do |format| - format.html { redirect_to team_project_path(@project) } + format.html { redirect_to project_team_index_path(@project) } format.js { render nothing: true } end end diff --git a/app/decorators/commit_decorator.rb b/app/decorators/commit_decorator.rb index cc8fa975..c85f7400 100644 --- a/app/decorators/commit_decorator.rb +++ b/app/decorators/commit_decorator.rb @@ -1,12 +1,23 @@ class CommitDecorator < ApplicationDecorator decorates :commit + # Returns a string describing the commit for use in a link title + # + # Example + # + # "Commit: Alex Denisov - Project git clone panel" + def link_title + "Commit: #{author_name} - #{title}" + end + # Returns the commits title. # # Usually, the commit title is the first line of the commit message. # In case this first line is longer than 80 characters, it is cut off # after 70 characters and ellipses (`&hellp;`) are appended. def title + return no_commit_message if safe_message.blank? + title_end = safe_message.index(/\n/) if (!title_end && safe_message.length > 80) || (title_end && title_end > 80) safe_message[0..69] << "…".html_safe @@ -26,4 +37,10 @@ class CommitDecorator < ApplicationDecorator safe_message.split(/\n/, 2)[1].try(:chomp) end end + + protected + + def no_commit_message + "--no commit message" + end end diff --git a/app/decorators/event_decorator.rb b/app/decorators/event_decorator.rb index 7df9081f..ce0aaa03 100644 --- a/app/decorators/event_decorator.rb +++ b/app/decorators/event_decorator.rb @@ -8,7 +8,9 @@ class EventDecorator < ApplicationDecorator "#{self.author_name} #{self.action_name} MR ##{self.target_id}:" + self.merge_request_title elsif self.push? "#{self.author_name} #{self.push_action_name} #{self.ref_type} " + self.ref_name - else + elsif self.membership_changed? + "#{self.author_name} #{self.action_name} #{self.project.name}" + else "" end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 38191f55..0938dc23 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -62,7 +62,7 @@ module ApplicationHelper { label: "#{@project.name} / Wall", url: wall_project_path(@project) }, { label: "#{@project.name} / Tree", url: tree_project_ref_path(@project, @project.root_ref) }, { label: "#{@project.name} / Commits", url: project_commits_path(@project) }, - { label: "#{@project.name} / Team", url: team_project_path(@project) } + { label: "#{@project.name} / Team", url: project_team_index_path(@project) } ] end @@ -78,16 +78,16 @@ module ApplicationHelper end def show_last_push_widget?(event) - event && + event && event.last_push_to_non_root? && !event.rm_ref? && - event.project && + event.project && event.project.merge_requests_enabled end def tab_class(tab_key) active = case tab_key - + # Project Area when :wall; wall_tab? when :wiki; controller.controller_name == "wikis" @@ -104,7 +104,8 @@ module ApplicationHelper # Profile Area when :profile; current_page?(controller: "profile", action: :show) - when :password; current_page?(controller: "profile", action: :password) + when :history; current_page?(controller: "profile", action: :history) + when :account; current_page?(controller: "profile", action: :account) when :token; current_page?(controller: "profile", action: :token) when :design; current_page?(controller: "profile", action: :design) when :ssh_keys; controller.controller_name == "keys" @@ -126,4 +127,19 @@ module ApplicationHelper def hexdigest(string) Digest::SHA1.hexdigest string end + + def project_last_activity project + activity = project.last_activity + if activity && activity.created_at + time_ago_in_words(activity.created_at) + " ago" + else + "Never" + end + end + + def authbutton(provider, size = 64) + file_name = "#{provider.to_s.split('_').first}_#{size}.png" + image_tag("authbuttons/#{file_name}", + alt: "Sign in with #{provider.to_s.titleize}") + end end diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 88e3473b..111982e9 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -1,38 +1,5 @@ module GitlabMarkdownHelper - # Replaces references (i.e. @abc, #123, !456, ...) in the text with links to - # the appropriate items in Gitlab. - # - # text - the source text - # html_options - extra options for the reference links as given to link_to - # - # note: reference links will only be generated if @project is set - # - # see Gitlab::Markdown for details on the supported syntax - def gfm(text, html_options = {}) - return text if text.nil? - return text if @project.nil? - - # Extract pre blocks so they are not altered - # from http://github.github.com/github-flavored-markdown/ - extractions = {} - text.gsub!(%r{
.*?
|.*?}m) do |match| - md5 = Digest::MD5.hexdigest(match) - extractions[md5] = match - "{gfm-extraction-#{md5}}" - end - - # TODO: add popups with additional information - - parser = Gitlab::Markdown.new(@project, html_options) - text = parser.parse(text) - - # Insert pre block extractions - text.gsub!(/\{gfm-extraction-(\h{32})\}/) do - extractions[$1] - end - - text.html_safe - end + include Gitlab::Markdown # Use this in places where you would normally use link_to(gfm(...), ...). # @@ -44,7 +11,9 @@ module GitlabMarkdownHelper # explicitly produce the correct linking behavior (i.e. # "outer text gfm ref more outer text"). def link_to_gfm(body, url, html_options = {}) - gfm_body = gfm(body, html_options) + return "" if body.blank? + + gfm_body = gfm(escape_once(body), html_options) gfm_body.gsub!(%r{.*?}m) do |match| "#{match}#{link_to("", url, html_options)[0..-5]}" # "".length +1 @@ -54,17 +23,24 @@ module GitlabMarkdownHelper end def markdown(text) - @__renderer ||= Redcarpet::Markdown.new(Redcarpet::Render::GitlabHTML.new(self, filter_html: true, with_toc_data: true), { - no_intra_emphasis: true, - tables: true, - fenced_code_blocks: true, - autolink: true, - strikethrough: true, - lax_html_blocks: true, - space_after_headers: true, - superscript: true - }) + unless @markdown + gitlab_renderer = Redcarpet::Render::GitlabHTML.new(self, + # see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch- + filter_html: true, + with_toc_data: true, + hard_wrap: true) + @markdown = Redcarpet::Markdown.new(gitlab_renderer, + # see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use + no_intra_emphasis: true, + tables: true, + fenced_code_blocks: true, + autolink: true, + strikethrough: true, + lax_html_blocks: true, + space_after_headers: true, + superscript: true) + end - @__renderer.render(text).html_safe + @markdown.render(text).html_safe end end diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb new file mode 100644 index 00000000..65389e38 --- /dev/null +++ b/app/helpers/notes_helper.rb @@ -0,0 +1,17 @@ +module NotesHelper + def loading_more_notes? + params[:loading_more].present? + end + + def loading_new_notes? + params[:loading_new].present? + end + + def note_vote_class(note) + if note.upvote? + "vote upvote" + elsif note.downvote? + "vote downvote" + end + end +end diff --git a/app/helpers/profile_helper.rb b/app/helpers/profile_helper.rb new file mode 100644 index 00000000..80d67009 --- /dev/null +++ b/app/helpers/profile_helper.rb @@ -0,0 +1,7 @@ +module ProfileHelper + def oauth_active_class provider + if current_user.provider == provider.to_s + 'active' + end + end +end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb new file mode 100644 index 00000000..c7dc54ee --- /dev/null +++ b/app/helpers/projects_helper.rb @@ -0,0 +1,10 @@ +module ProjectsHelper + def grouper_project_members(project) + @project.users_projects.sort_by(&:project_access).reverse.group_by(&:project_access) + end + + def remove_from_team_message(project, member) + "You are going to remove #{member.user_name} from #{project.name}. Are you sure?" + end +end + diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb index 1740864b..b5d7ccb7 100644 --- a/app/helpers/tab_helper.rb +++ b/app/helpers/tab_helper.rb @@ -8,7 +8,7 @@ module TabHelper end def project_tab_class - [:show, :files, :team, :edit, :update].each do |action| + [:show, :files, :edit, :update].each do |action| return "current" if current_page?(controller: "projects", action: action, id: @project) end diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index ed3053d8..2b7265ca 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -18,10 +18,25 @@ module TreeHelper end def tree_full_path(content) - if params[:path] + content.name.force_encoding('utf-8') + if params[:path] File.join(params[:path], content.name) else content.name end end + + # Public: Determines if a given filename is compatible with GitHub::Markup. + # + # filename - Filename string to check + # + # Returns boolean + def markup?(filename) + filename.end_with?(*%w(.textile .rdoc .org .creole + .mediawiki .rst .asciidoc .pod)) + end + + def gitlab_markdown?(filename) + filename.end_with?(*%w(.mdown .md .markdown)) + end end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index d0571b7b..0afc1d31 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -76,6 +76,21 @@ class Notify < ActionMailer::Base mail(to: recipient(recipient_id), subject: subject("changed issue ##{@issue.id}", @issue.title)) end + def project_access_granted_email(user_project_id) + @users_project = UsersProject.find user_project_id + @project = @users_project.project + mail(to: @users_project.user.email, + subject: subject("access to project was granted")) + end + + def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id) + @issue = Issue.find issue_id + @issue_status = status + @updated_by = User.find updated_by_user_id + mail(to: recipient(recipient_id), + subject: subject("changed issue ##{@issue.id}", @issue.title)) + end + private # Look up a User by their ID and return their email address @@ -96,18 +111,18 @@ class Notify < ActionMailer::Base # Examples # # >> subject('Lorem ipsum') - # => "gitlab | Lorem ipsum" + # => "GitLab | Lorem ipsum" # # # Automatically inserts Project name when @project is set # >> @project = Project.last # => # # >> subject('Lorem ipsum') - # => "gitlab | Lorem ipsum | Ruby on Rails" + # => "GitLab | Lorem ipsum | Ruby on Rails" # # # Accepts multiple arguments # >> subject('Lorem ipsum', 'Dolor sit amet') - # => "gitlab | Lorem ipsum | Dolor sit amet" + # => "GitLab | Lorem ipsum | Dolor sit amet" def subject(*extra) - "gitlab | " << extra.join(' | ') << (@project ? " | #{@project.name}" : "") + "GitLab | " << extra.join(' | ') << (@project ? " | #{@project.name}" : "") end end diff --git a/app/models/commit.rb b/app/models/commit.rb index 5c6b4d88..73583e9e 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -1,6 +1,7 @@ class Commit include ActiveModel::Conversion include Gitlab::Encode + include StaticModel extend ActiveModel::Naming attr_accessor :commit @@ -22,8 +23,7 @@ class Commit :to_patch, to: :commit - - class << self + class << self def find_or_first(repo, commit_id = nil, root_ref) commit = if commit_id repo.commit(commit_id) @@ -82,20 +82,24 @@ class Commit end def compare(project, from, to) - first = project.commit(to.try(:strip)) - last = project.commit(from.try(:strip)) - - result = { + result = { commits: [], diffs: [], - commit: nil + commit: nil, + same: false } + return result unless from && to + + first = project.commit(to.try(:strip)) + last = project.commit(from.try(:strip)) + if first && last commits = [first, last].sort_by(&:created_at) younger = commits.first older = commits.last + result[:same] = (younger.id == older.id) result[:commits] = project.repo.commits_between(younger.id, older.id).map {|c| Commit.new(c)} result[:diffs] = project.repo.diff(younger.id, older.id) rescue [] result[:commit] = Commit.new(older) @@ -105,10 +109,6 @@ class Commit end end - def persisted? - false - end - def initialize(raw_commit, head = nil) @commit = raw_commit @head = head @@ -155,7 +155,7 @@ class Commit prev_commit.try :id end - def parents_count + def parents_count parents && parents.count || 0 end end diff --git a/app/models/event.rb b/app/models/event.rb index e20b79e2..b11b21bd 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -10,6 +10,8 @@ class Event < ActiveRecord::Base Pushed = 5 Commented = 6 Merged = 7 + Joined = 8 # User joined project + Left = 9 # User left project belongs_to :project belongs_to :target, polymorphic: true @@ -33,11 +35,19 @@ class Event < ActiveRecord::Base end # Next events currently enabled for system - # - push + # - push # - new issue # - merge request def allowed? - push? || issue? || merge_request? + push? || issue? || merge_request? || membership_changed? + end + + def project_name + if project + project.name + else + "(deleted)" + end end def push? @@ -56,35 +66,47 @@ class Event < ActiveRecord::Base action == self.class::Reopened end - def issue? + def issue? target_type == "Issue" end - def merge_request? + def merge_request? target_type == "MergeRequest" end - def new_issue? - target_type == "Issue" && + def new_issue? + target_type == "Issue" && action == Created end - def new_merge_request? - target_type == "MergeRequest" && + def new_merge_request? + target_type == "MergeRequest" && action == Created end - def changed_merge_request? - target_type == "MergeRequest" && + def changed_merge_request? + target_type == "MergeRequest" && [Closed, Reopened].include?(action) end - def changed_issue? - target_type == "Issue" && + def changed_issue? + target_type == "Issue" && [Closed, Reopened].include?(action) end - def issue + def joined? + action == Joined + end + + def left? + action == Left + end + + def membership_changed? + joined? || left? + end + + def issue target if target_type == "Issue" end @@ -92,7 +114,7 @@ class Event < ActiveRecord::Base target if target_type == "MergeRequest" end - def author + def author @author ||= User.find(author_id) end @@ -101,7 +123,11 @@ class Event < ActiveRecord::Base "closed" elsif merged? "merged" - else + elsif joined? + 'joined' + elsif left? + 'left' + else "opened" end end diff --git a/app/models/issue.rb b/app/models/issue.rb index 6409eeba..96a54907 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -1,6 +1,6 @@ class Issue < ActiveRecord::Base include IssueCommonality - include Upvote + include Votes acts_as_taggable_on :labels diff --git a/app/models/key.rb b/app/models/key.rb index cfcb1f63..a39a4a16 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -1,16 +1,18 @@ require 'digest/md5' class Key < ActiveRecord::Base - include SshKey belongs_to :user belongs_to :project + attr_protected :user_id + validates :title, presence: true, length: { within: 0..255 } validates :key, presence: true, + format: { :with => /ssh-.{3} / }, length: { within: 0..5000 } before_save :set_identifier @@ -50,6 +52,10 @@ class Key < ActiveRecord::Base user.projects end end + + def last_deploy? + Key.where(identifier: identifier).count == 0 + end end # == Schema Information # diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 542817b0..184ac5fc 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -2,7 +2,7 @@ require File.join(Rails.root, "app/models/commit") class MergeRequest < ActiveRecord::Base include IssueCommonality - include Upvote + include Votes BROKEN_DIFF = "--broken-diff" @@ -162,7 +162,7 @@ class MergeRequest < ActiveRecord::Base end def automerge!(current_user) - if Gitlab::Merge.new(self, current_user).merge + if Gitlab::Merge.new(self, current_user).merge && self.unmerged_commits.empty? self.merge!(current_user.id) true end diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 592c57f9..d416fb63 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -28,17 +28,9 @@ class Milestone < ActiveRecord::Base end def percent_complete - @percent_complete ||= begin - total_i = self.issues.count - closed_i = self.issues.closed.count - if total_i > 0 - (closed_i * 100) / total_i - else - 100 - end - rescue => ex - 0 - end + ((self.issues.closed.count * 100) / self.issues.count).abs + rescue ZeroDivisionError + 100 end def expires_at diff --git a/app/models/note.rb b/app/models/note.rb index 711a4ee6..34edb94e 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -36,7 +36,7 @@ class Note < ActiveRecord::Base scope :today, where("created_at >= :date", date: Date.today) scope :last_week, where("created_at >= :date", date: (Date.today - 7.days)) scope :since, lambda { |day| where("created_at >= :date", date: (day)) } - scope :fresh, order("created_at DESC") + scope :fresh, order("created_at ASC, id ASC") scope :inc_author_project, includes(:project, :author) scope :inc_author, includes(:author) @@ -103,7 +103,13 @@ class Note < ActiveRecord::Base # Returns true if this is an upvote note, # otherwise false is returned def upvote? - note =~ /^\+1/ ? true : false + note.start_with?('+1') || note.start_with?(':+1:') + end + + # Returns true if this is a downvote note, + # otherwise false is returned + def downvote? + note.start_with?('-1') || note.start_with?(':-1:') end end # == Schema Information diff --git a/app/models/project.rb b/app/models/project.rb index 3fe11916..56d5d791 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -8,7 +8,7 @@ class Project < ActiveRecord::Base # # Relations - # + # belongs_to :owner, class_name: "User" has_many :users, through: :users_projects has_many :events, dependent: :destroy @@ -25,12 +25,12 @@ class Project < ActiveRecord::Base attr_accessor :error_code - # + # # Protected attributes # attr_protected :private_flag, :owner_id - # + # # Scopes # scope :public_only, where(private_flag: false) @@ -104,6 +104,8 @@ class Project < ActiveRecord::Base length: { within: 1..255 } validates :owner, presence: true + validates :issues_enabled, :wall_enabled, :merge_requests_enabled, + :wiki_enabled, inclusion: { in: [true, false] } validate :check_limit validate :repo_name @@ -158,7 +160,7 @@ class Project < ActiveRecord::Base end def last_activity - events.last || nil + events.order("created_at ASC").last end def last_activity_date @@ -169,6 +171,10 @@ class Project < ActiveRecord::Base end end + def wiki_notes + Note.where(noteable_id: wikis.map(&:id), noteable_type: 'Wiki', project_id: self.id) + end + def project_id self.id end @@ -187,7 +193,7 @@ end # private_flag :boolean(1) default(TRUE), not null # code :string(255) # owner_id :integer(4) -# default_branch :string(255) default("master"), not null +# default_branch :string(255) # issues_enabled :boolean(1) default(TRUE), not null # wall_enabled :boolean(1) default(TRUE), not null # merge_requests_enabled :boolean(1) default(TRUE), not null diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index 4da1432c..7c30f7a0 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -1,4 +1,6 @@ class ProtectedBranch < ActiveRecord::Base + include GitHost + belongs_to :project validates_presence_of :project_id validates_presence_of :name @@ -7,7 +9,7 @@ class ProtectedBranch < ActiveRecord::Base after_destroy :update_repository def update_repository - Gitlab::GitHost.system.update_project(project.path, project) + git_host.update_repository(project) end def commit diff --git a/app/models/tree.rb b/app/models/tree.rb index bc95d335..d65e50ab 100644 --- a/app/models/tree.rb +++ b/app/models/tree.rb @@ -16,7 +16,7 @@ class Tree def initialize(raw_tree, project, ref = nil, path = nil) @project, @ref, @path = project, ref, path, @tree = if path - raw_tree / path + raw_tree / path.dup.force_encoding('ascii-8bit') else raw_tree end diff --git a/app/models/user.rb b/app/models/user.rb index ad6af6a6..47876722 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -86,33 +86,20 @@ class User < ActiveRecord::Base where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)') end - def self.find_for_ldap_auth(auth, signed_in_resource=nil) - uid = auth.info.uid - provider = auth.provider - name = auth.info.name.force_encoding("utf-8") - email = auth.info.email.downcase unless auth.info.email.nil? - raise OmniAuth::Error, "LDAP accounts must provide an uid and email address" if uid.nil? or email.nil? + def self.create_from_omniauth(auth, ldap = false) + gitlab_auth.create_from_omniauth(auth, ldap) + end - if @user = User.find_by_extern_uid_and_provider(uid, provider) - @user - # workaround for backward compatibility - elsif @user = User.find_by_email(email) - logger.info "Updating legacy LDAP user #{email} with extern_uid => #{uid}" - @user.update_attributes(:extern_uid => uid, :provider => provider) - @user - else - logger.info "Creating user from LDAP login {uid => #{uid}, name => #{name}, email => #{email}}" - password = Devise.friendly_token[0, 8].downcase - @user = User.create( - :extern_uid => uid, - :provider => provider, - :name => name, - :email => email, - :password => password, - :password_confirmation => password, - :projects_limit => Gitlab.config.default_projects_limit - ) - end + def self.find_or_new_for_omniauth(auth) + gitlab_auth.find_or_new_for_omniauth(auth) + end + + def self.find_for_ldap_auth(auth, signed_in_resource = nil) + gitlab_auth.find_for_ldap_auth(auth, signed_in_resource) + end + + def self.gitlab_auth + Gitlab::Auth.new end def self.search query @@ -148,4 +135,3 @@ end # bio :string(255) # blocked :boolean(1) default(FALSE), not null # - diff --git a/app/models/users_project.rb b/app/models/users_project.rb index 36e6d904..ce64a10f 100644 --- a/app/models/users_project.rb +++ b/app/models/users_project.rb @@ -1,4 +1,6 @@ class UsersProject < ActiveRecord::Base + include GitHost + GUEST = 10 REPORTER = 20 DEVELOPER = 30 @@ -12,12 +14,29 @@ class UsersProject < ActiveRecord::Base after_save :update_repository after_destroy :update_repository - validates_uniqueness_of :user_id, scope: [:project_id] + validates_uniqueness_of :user_id, scope: [:project_id], message: "already exists in project" validates_presence_of :user_id validates_presence_of :project_id delegate :name, :email, to: :user, prefix: true + def self.bulk_delete(project, user_ids) + UsersProject.transaction do + UsersProject.where(:user_id => user_ids, :project_id => project.id).each do |users_project| + users_project.destroy + end + end + end + + def self.bulk_update(project, user_ids, project_access) + UsersProject.transaction do + UsersProject.where(:user_id => user_ids, :project_id => project.id).each do |users_project| + users_project.project_access = project_access + users_project.save + end + end + end + def self.bulk_import(project, user_ids, project_access) UsersProject.transaction do user_ids.each do |user_id| @@ -46,10 +65,10 @@ class UsersProject < ActiveRecord::Base def self.access_roles { - "Guest" => GUEST, - "Reporter" => REPORTER, + "Guest" => GUEST, + "Reporter" => REPORTER, "Developer" => DEVELOPER, - "Master" => MASTER + "Master" => MASTER } end @@ -58,9 +77,7 @@ class UsersProject < ActiveRecord::Base end def update_repository - Gitlab::GitHost.system.new.configure do |c| - c.update_project(project.path, project) - end + git_host.update_repository(project) end def project_access_human diff --git a/app/models/wiki.rb b/app/models/wiki.rb index 3c4952cd..ebb1ff49 100644 --- a/app/models/wiki.rb +++ b/app/models/wiki.rb @@ -28,7 +28,6 @@ class Wiki < ActiveRecord::Base end new_wiki end - end end # == Schema Information diff --git a/app/observers/issue_observer.rb b/app/observers/issue_observer.rb index 92b0f836..62fd9bf8 100644 --- a/app/observers/issue_observer.rb +++ b/app/observers/issue_observer.rb @@ -9,8 +9,16 @@ class IssueObserver < ActiveRecord::Observer def after_update(issue) send_reassigned_email(issue) if issue.is_being_reassigned? - Note.create_status_change_note(issue, current_user, 'closed') if issue.is_being_closed? - Note.create_status_change_note(issue, current_user, 'reopened') if issue.is_being_reopened? + + status = nil + status = 'closed' if issue.is_being_closed? + status = 'reopened' if issue.is_being_reopened? + if status + Note.create_status_change_note(issue, current_user, status) + [issue.author, issue.assignee].compact.each do |recipient| + Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user) + end + end end protected diff --git a/app/observers/key_observer.rb b/app/observers/key_observer.rb index fac53a67..a3f17bde 100644 --- a/app/observers/key_observer.rb +++ b/app/observers/key_observer.rb @@ -1,9 +1,12 @@ class KeyObserver < ActiveRecord::Observer + include GitHost + def after_save(key) - key.update_repository + git_host.set_key(key.identifier, key.key, key.projects) end def after_destroy(key) - key.repository_delete_key + return if key.is_deploy_key && !key.last_deploy? + git_host.remove_key(key.identifier, key.projects) end end diff --git a/app/observers/project_observer.rb b/app/observers/project_observer.rb index 135959ab..03a61709 100644 --- a/app/observers/project_observer.rb +++ b/app/observers/project_observer.rb @@ -4,6 +4,18 @@ class ProjectObserver < ActiveRecord::Observer end def after_destroy(project) + log_info("Project \"#{project.name}\" was removed") + project.destroy_repository end + + def after_create project + log_info("#{project.owner.name} created a new project \"#{project.name}\"") + end + + protected + + def log_info message + Gitlab::AppLogger.info message + end end diff --git a/app/observers/user_observer.rb b/app/observers/user_observer.rb index d12bcc99..654621f7 100644 --- a/app/observers/user_observer.rb +++ b/app/observers/user_observer.rb @@ -1,5 +1,17 @@ class UserObserver < ActiveRecord::Observer def after_create(user) + log_info("User \"#{user.name}\" (#{user.email}) was created") + Notify.new_user_email(user.id, user.password).deliver end + + def after_destroy user + log_info("User \"#{user.name}\" (#{user.email}) was removed") + end + + protected + + def log_info message + Gitlab::AppLogger.info message + end end diff --git a/app/observers/users_project_observer.rb b/app/observers/users_project_observer.rb new file mode 100644 index 00000000..0512e606 --- /dev/null +++ b/app/observers/users_project_observer.rb @@ -0,0 +1,23 @@ +class UsersProjectObserver < ActiveRecord::Observer + def after_create(users_project) + Notify.project_access_granted_email(users_project.id).deliver + + Event.create( + project_id: users_project.project.id, + action: Event::Joined, + author_id: users_project.user.id + ) + end + + def after_update(users_project) + Notify.project_access_granted_email(users_project.id).deliver + end + + def after_destroy(users_project) + Event.create( + project_id: users_project.project.id, + action: Event::Left, + author_id: users_project.user.id + ) + end +end diff --git a/app/roles/account.rb b/app/roles/account.rb index 63a9b5c5..b8c445a3 100644 --- a/app/roles/account.rb +++ b/app/roles/account.rb @@ -1,6 +1,13 @@ -module Account +module Account + # Returns a string for use as a Gitolite user identifier + # + # Note that Gitolite 2.x requires the following pattern for users: + # + # ^@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$ def identifier - email.gsub /[^[:alnum:]]/, "_" + # Replace non-word chars with underscores, then make sure it starts with + # valid chars + email.gsub(/\W/, '_').gsub(/\A([\W\_])+/, '') end def is_admin? diff --git a/app/roles/git_host.rb b/app/roles/git_host.rb new file mode 100644 index 00000000..aa620f77 --- /dev/null +++ b/app/roles/git_host.rb @@ -0,0 +1,5 @@ +module GitHost + def git_host + Gitlab::Gitolite.new + end +end diff --git a/app/roles/git_merge.rb b/app/roles/git_merge.rb deleted file mode 100644 index 95e5942f..00000000 --- a/app/roles/git_merge.rb +++ /dev/null @@ -1,2 +0,0 @@ -module GitMerge -end diff --git a/app/roles/issue_commonality.rb b/app/roles/issue_commonality.rb index a8fd679d..ac972a70 100644 --- a/app/roles/issue_commonality.rb +++ b/app/roles/issue_commonality.rb @@ -16,7 +16,7 @@ module IssueCommonality validates :title, presence: true, length: { within: 0..255 } - + validates :closed, inclusion: { in: [true, false] } scope :opened, where(closed: false) scope :closed, where(closed: true) diff --git a/app/roles/push_event.rb b/app/roles/push_event.rb index ff8e28a2..a607f212 100644 --- a/app/roles/push_event.rb +++ b/app/roles/push_event.rb @@ -90,6 +90,8 @@ module PushEvent def push_with_commits? md_ref? && commits.any? && parent_commit && last_commit + rescue Grit::NoSuchPathError + false end def last_push_to_non_root? diff --git a/app/roles/push_observer.rb b/app/roles/push_observer.rb index 1067404d..947ed423 100644 --- a/app/roles/push_observer.rb +++ b/app/roles/push_observer.rb @@ -1,3 +1,6 @@ +# Includes methods for handling Git Push events +# +# Triggered by PostReceive job module PushObserver def observe_push(oldrev, newrev, ref, user) data = post_receive_data(oldrev, newrev, ref, user) @@ -84,11 +87,10 @@ module PushObserver data end - - # This method will be called after each post receive - # and only if user present in gitlab. - # All callbacks for post receive should be placed here + # This method will be called after each post receive and only if the provided + # user is present in GitLab. # + # All callbacks for post receive should be placed here. def trigger_post_receive(oldrev, newrev, ref, user) # Create push event self.observe_push(oldrev, newrev, ref, user) @@ -101,5 +103,11 @@ module PushObserver # Create satellite self.satellite.create unless self.satellite.exists? + + # Discover the default branch, but only if it hasn't already been set to + # something else + if default_branch.nil? + update_attributes(default_branch: discover_default_branch) + end end end diff --git a/app/roles/repository.rb b/app/roles/repository.rb index 7f1d6f84..01156ac1 100644 --- a/app/roles/repository.rb +++ b/app/roles/repository.rb @@ -1,4 +1,6 @@ module Repository + include GitHost + def valid_repo? repo rescue @@ -6,6 +8,10 @@ module Repository false end + def empty_repo? + !repo_exists? || !has_commits? + end + def commit(commit_id = nil) Commit.find_or_first(repo, commit_id, root_ref) end @@ -36,7 +42,7 @@ module Repository def has_post_receive_file? hook_file = File.join(path_to_repo, 'hooks', 'post-receive') - File.exists?(hook_file) + File.exists?(hook_file) end def tags @@ -48,7 +54,7 @@ module Repository end def url_to_repo - Gitlab::GitHost.url_to_repo(path) + git_host.url_to_repo(path) end def path_to_repo @@ -56,16 +62,16 @@ module Repository end def update_repository - Gitlab::GitHost.system.update_project(path, self) + git_host.update_repository(self) end def destroy_repository - Gitlab::GitHost.system.destroy_project(self) + git_host.remove_repository(self) end def repo_exists? @repo_exists ||= (repo && !repo.branches.empty?) - rescue + rescue @repo_exists = false end @@ -73,6 +79,14 @@ module Repository @heads ||= repo.heads end + def branches_names + heads.map(&:name) + end + + def ref_names + [branches_names + tags].flatten + end + def tree(fcommit, path = nil) fcommit = commit if fcommit == :head tree = fcommit.tree @@ -88,24 +102,42 @@ module Repository end.sort_by(&:name) end + # Discovers the default branch based on the repository's available branches + # + # - If no branches are present, returns nil + # - If one branch is present, returns its name + # - If two or more branches are present, returns the one that has a name + # matching root_ref (default_branch or 'master' if default_branch is nil) + def discover_default_branch + branches = heads.collect(&:name) + + if branches.length == 0 + nil + elsif branches.length == 1 + branches.first + else + branches.select { |v| v == root_ref }.first + end + end + def has_commits? !!commit end - def root_ref + def root_ref default_branch || "master" end - def root_ref? branch + def root_ref?(branch) root_ref == branch end # Archive Project to .tar.gz # - # Already packed repo archives stored at + # Already packed repo archives stored at # app_root/tmp/repositories/project_name/project_name-commit-id.tag.gz # - def archive_repo ref + def archive_repo(ref) ref = ref || self.root_ref commit = self.commit(ref) return nil unless commit @@ -115,10 +147,13 @@ module Repository storage_path = File.join(Rails.root, "tmp", "repositories", self.code) file_path = File.join(storage_path, file_name) + # Put files into a directory before archiving + prefix = self.code + "/" + # Create file if not exists unless File.exists?(file_path) FileUtils.mkdir_p storage_path - file = self.repo.archive_to_file(ref, nil, file_path) + file = self.repo.archive_to_file(ref, prefix, file_path) end file_path @@ -129,6 +164,6 @@ module Repository end def http_url_to_repo - http_url = [Gitlab.config.url, "/", path, ".git"].join() + http_url = [Gitlab.config.url, "/", path, ".git"].join('') end end diff --git a/app/roles/ssh_key.rb b/app/roles/ssh_key.rb deleted file mode 100644 index 5e1d2c23..00000000 --- a/app/roles/ssh_key.rb +++ /dev/null @@ -1,18 +0,0 @@ -module SshKey - def update_repository - Gitlab::GitHost.system.new.configure do |c| - c.update_keys(identifier, key) - c.update_projects(projects) - end - end - - def repository_delete_key - Gitlab::GitHost.system.new.configure do |c| - #delete key file is there is no identically deploy keys - if !is_deploy_key || Key.where(identifier: identifier).count() == 0 - c.delete_key(identifier) - end - c.update_projects(projects) - end - end -end diff --git a/app/roles/static_model.rb b/app/roles/static_model.rb new file mode 100644 index 00000000..d26c8f47 --- /dev/null +++ b/app/roles/static_model.rb @@ -0,0 +1,35 @@ +# Provides an ActiveRecord-like interface to a model whose data is not persisted to a database. +module StaticModel + extend ActiveSupport::Concern + + module ClassMethods + # Used by ActiveRecord's polymorphic association to set object_id + def primary_key + 'id' + end + + # Used by ActiveRecord's polymorphic association to set object_type + def base_class + self + end + end + + # Used by AR for fetching attributes + # + # Pass it along if we respond to it. + def [](key) + send(key) if respond_to?(key) + end + + def to_param + id + end + + def persisted? + false + end + + def destroyed? + false + end +end diff --git a/app/roles/team.rb b/app/roles/team.rb index 27b1cc65..8aef405a 100644 --- a/app/roles/team.rb +++ b/app/roles/team.rb @@ -36,4 +36,17 @@ module Team UsersProject.bulk_import(self, users_ids, access_role) self.update_repository end + + # Update multiple project users + # to same access role by user ids + def update_users_ids_to_role(users_ids, access_role) + UsersProject.bulk_update(self, users_ids, access_role) + self.update_repository + end + + # Delete multiple users from project by user ids + def delete_users_ids_from_team(users_ids) + UsersProject.bulk_delete(self, users_ids) + self.update_repository + end end diff --git a/app/roles/upvote.rb b/app/roles/upvote.rb deleted file mode 100644 index 7efa6f20..00000000 --- a/app/roles/upvote.rb +++ /dev/null @@ -1,6 +0,0 @@ -module Upvote - # Return the number of +1 comments (upvotes) - def upvotes - notes.select(&:upvote?).size - end -end diff --git a/app/roles/votes.rb b/app/roles/votes.rb new file mode 100644 index 00000000..043a6feb --- /dev/null +++ b/app/roles/votes.rb @@ -0,0 +1,32 @@ +module Votes + # Return the number of +1 comments (upvotes) + def upvotes + notes.select(&:upvote?).size + end + + def upvotes_in_percent + if votes_count.zero? + 0 + else + 100.0 / votes_count * upvotes + end + end + + # Return the number of -1 comments (downvotes) + def downvotes + notes.select(&:downvote?).size + end + + def downvotes_in_percent + if votes_count.zero? + 0 + else + 100.0 - upvotes_in_percent + end + end + + # Return the total number of votes + def votes_count + upvotes + downvotes + end +end diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 2e67abfc..3e26f566 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -35,11 +35,13 @@ %h3 Latest projects %hr - @projects.each do |project| - %h5 + %p = link_to project.name, [:admin, project] .span6 %h3 Latest users %hr - @users.each do |user| - %h5 - = link_to user.name, [:admin, user] + %p + = link_to [:admin, user] do + = user.name + %small= user.email diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index 43288424..f17355fb 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -23,7 +23,7 @@ Hooks %small (#{@hooks.count}) %br - %table.admin-table + %table %tr %th URL %th Method diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml index 800d3bb2..0efe6db7 100644 --- a/app/views/admin/logs/show.html.haml +++ b/app/views/admin/logs/show.html.haml @@ -1,9 +1,26 @@ -.file_holder#README - .file_title - %i.icon-file - githost.log - .file_content.logs - %ol - - Gitlab::Logger.read_latest.each do |line| - %li - %p= line +%ul.nav.nav-tabs.log-tabs + %li.active + = link_to "githost.log", "#githost", 'data-toggle' => 'tab' + %li + = link_to "application.log", "#application", 'data-toggle' => 'tab' +.tab-content + .tab-pane.active#githost + .file_holder#README + .file_title + %i.icon-file + githost.log + .file_content.logs + %ol + - Gitlab::GitLogger.read_latest.each do |line| + %li + %p= line + .tab-pane#application + .file_holder#README + .file_title + %i.icon-file + application.log + .file_content.logs + %ol + - Gitlab::AppLogger.read_latest.each do |line| + %li + %p= line diff --git a/app/views/admin/projects/_form.html.haml b/app/views/admin/projects/_form.html.haml index 7cebddf2..4848e739 100644 --- a/app/views/admin/projects/_form.html.haml +++ b/app/views/admin/projects/_form.html.haml @@ -10,19 +10,17 @@ Project name is .input = f.text_field :name, placeholder: "Example Project", class: "xxlarge" - = f.submit project.new_record? ? 'Create project' : 'Save Project', class: "btn primary" %hr - .alert.alert-info - %h5 Advanced settings: + .adv_settings + %h6 Advanced settings: .clearfix = f.label :path do - Git Clone + Path .input .input-prepend - %span.add-on= Gitlab.config.ssh_path - = f.text_field :path, placeholder: "example_project", disabled: !!project.id - %span.add-on= ".git" + %strong + = text_field_tag :ppath, @admin_project.path_to_repo, class: "xlarge", disabled: true .clearfix = f.label :code do URL @@ -34,7 +32,7 @@ - unless project.new_record? .clearfix = f.label :owner_id - .input= f.select :owner_id, User.all.map { |user| [user.name, user.id] } + .input= f.select :owner_id, User.all.map { |user| [user.name, user.id] }, {}, {class: 'chosen'} - if project.repo_exists? .clearfix @@ -42,8 +40,9 @@ .input= f.select(:default_branch, project.heads.map(&:name), {}, style: "width:210px;") - unless project.new_record? - .alert.alert-info - %h5 Features: + %hr + .adv_settings + %h6 Features: .clearfix = f.label :issues_enabled, "Issues" @@ -63,13 +62,13 @@ - unless project.new_record? .actions - = f.submit 'Save Project', class: "btn primary" + = f.submit 'Save Project', class: "btn save-btn" + = link_to 'Cancel', admin_projects_path, class: "btn cancel-btn" :javascript $(function(){ - $('#project_owner_id').chosen(); new Projects(); }) diff --git a/app/views/admin/projects/_new_form.html.haml b/app/views/admin/projects/_new_form.html.haml new file mode 100644 index 00000000..d793b6f3 --- /dev/null +++ b/app/views/admin/projects/_new_form.html.haml @@ -0,0 +1,29 @@ += form_for [:admin, @admin_project] do |f| + - if @admin_project.errors.any? + .alert-message.block-message.error + %span= @admin_project.errors.full_messages.first + .clearfix.project_name_holder + = f.label :name do + Project name is + .input + = f.text_field :name, placeholder: "Example Project", class: "xxlarge" + = f.submit 'Create project', class: "btn primary project-submit" + + %hr + %div.adv_settings + %h6 Advanced settings: + .clearfix + = f.label :path do + Git Clone + .input + .input-prepend + %span.add-on= Gitlab.config.ssh_path + = f.text_field :path, placeholder: "example_project", disabled: !@admin_project.new_record? + %span.add-on= ".git" + .clearfix + = f.label :code do + URL + .input + .input-prepend + %span.add-on= web_app_url + = f.text_field :code, placeholder: "example" diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 882b2ab5..bd38ae72 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -1,4 +1,4 @@ -%h3 +%h3.page_title Projects = link_to 'New Project', new_admin_project_path, class: "btn small right" %br @@ -6,7 +6,7 @@ = text_field_tag :name, params[:name], class: "xlarge" = submit_tag "Search", class: "btn submit primary" -%table.admin-table +%table %thead %th Name %th Path diff --git a/app/views/admin/projects/new.html.haml b/app/views/admin/projects/new.html.haml index ac6526bf..933cb671 100644 --- a/app/views/admin/projects/new.html.haml +++ b/app/views/admin/projects/new.html.haml @@ -1,3 +1,12 @@ -%h3.page_title New project -%hr -= render 'form', project: @admin_project +.project_new_holder + %h3.page_title + New Project + %hr + = render 'new_form' +%div.save-project-loader.hide + %center + = image_tag "ajax_loader.gif" + %h3 Creating project & repository. Please wait a few minutes + +:javascript + $(function(){ new Projects(); }); diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 5ed56477..63987410 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -3,7 +3,11 @@ = link_to 'Edit', edit_admin_project_path(@admin_project), class: "btn right small" %br -%table.zebra-striped.table-bordered +%table.zebra-striped + %thead + %tr + %th Project + %th %tr %td %b @@ -40,7 +44,7 @@ %small (#{@admin_project.users_projects.count}) %br -%table.zebra-striped.table-bordered +%table.zebra-striped %thead %tr %th Name @@ -60,32 +64,18 @@ %h3 Add new team member %br = form_tag team_update_admin_project_path(@admin_project), class: "bulk_import", method: :put do - %table.zebra-striped.table-bordered + %table.zebra-striped %thead %tr %th Users %th Project Access: %tr - %td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true - %td= select_tag :project_access, options_for_select(Project.access_options), class: "project-access-select" + %td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5' + %td= select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3"} %tr %td= submit_tag 'Add', class: "btn primary" %td Read more about project permissions %strong= link_to "here", help_permissions_path, class: "vlink" - -:css - form select { - width:150px; - } - - #user_ids { - width:300px; - } - -:javascript - $('select#user_ids').chosen(); - $('select#repo_access').chosen(); - $('select#project_access').chosen(); diff --git a/app/views/admin/resque/show.html.haml b/app/views/admin/resque/show.html.haml index 0375d94b..8850e378 100644 --- a/app/views/admin/resque/show.html.haml +++ b/app/views/admin/resque/show.html.haml @@ -1,2 +1,4 @@ -%h3 Resque -%iframe{src: "/info/resque", width: 1168, height: 600, style: "border: none"} \ No newline at end of file +%h3.page_title Resque +%br +.ui-box + %iframe{src: resque_url, width: '100%', height: 600, style: "border: none"} diff --git a/app/views/admin/team_members/_form.html.haml b/app/views/admin/team_members/_form.html.haml index 6a128de9..9cd94fdd 100644 --- a/app/views/admin/team_members/_form.html.haml +++ b/app/views/admin/team_members/_form.html.haml @@ -8,20 +8,9 @@ .clearfix %label Project Access: .input - = f.select :project_access, options_for_select(Project.access_options, @admin_team_member.project_access), {}, class: "project-access-select" + = f.select :project_access, options_for_select(Project.access_options, @admin_team_member.project_access), {}, class: "project-access-select chosen span3" %br .actions = f.submit 'Save', class: "btn primary" = link_to 'Cancel', :back, class: "btn" - -:css - form select { - width:300px; - } - -:javascript - $('select#team_member_user_id').chosen(); - $('select#team_member_project_id').chosen(); - $('select#team_member_repo_access').chosen(); - $('select#team_member_project_access').chosen(); diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index 5e68a23f..7010c272 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -2,71 +2,79 @@ = form_for [:admin, @admin_user] do |f| -if @admin_user.errors.any? #error_explanation - %ul + %ul.unstyled.alert.alert-error - @admin_user.errors.full_messages.each do |msg| %li= msg .row - .span6 - .clearfix - = f.label :name - .input - = f.text_field :name - %span.help-inline * required - .clearfix - = f.label :email - .input - = f.text_field :email - %span.help-inline * required - %hr + .span7 + .ui-box + %br + .clearfix + = f.label :name + .input + = f.text_field :name + %span.help-inline * required + .clearfix + = f.label :email + .input + = f.text_field :email + %span.help-inline * required + %hr + -if f.object.new_record? + .clearfix + = f.label :force_random_password do + %span Generate random password + .input= f.check_box :force_random_password, {}, true, nil + + %div.password-fields + .clearfix + = f.label :password + .input= f.password_field :password, disabled: f.object.force_random_password + .clearfix + = f.label :password_confirmation + .input= f.password_field :password_confirmation, disabled: f.object.force_random_password + %hr + .clearfix + = f.label :skype + .input= f.text_field :skype + .clearfix + = f.label :linkedin + .input= f.text_field :linkedin + .clearfix + = f.label :twitter + .input= f.text_field :twitter + .span5 + .ui-box + %br + .clearfix + = f.label :projects_limit + .input= f.number_field :projects_limit - -if f.object.new_record? - .clearfix - = f.label :admin, class: "checkbox" do - = f.check_box :force_random_password, {}, true, nil - %span Generate random password - - %div.password-fields .clearfix - = f.label :password - .input= f.password_field :password, disabled: f.object.force_random_password - .clearfix - = f.label :password_confirmation - .input= f.password_field :password_confirmation, disabled: f.object.force_random_password - %hr - .clearfix - = f.label :skype - .input= f.text_field :skype - .clearfix - = f.label :linkedin - .input= f.text_field :linkedin - .clearfix - = f.label :twitter - .input= f.text_field :twitter - .span6 - .clearfix - = f.label :projects_limit - .input= f.text_field :projects_limit, class: "small_input" + = f.label :admin do + %strong.cred Administrator + .input= f.check_box :admin + - unless @admin_user.new_record? + %hr + .padded.cred + - if @admin_user.blocked + %span + This user is blocked and is not able to login to GitLab + .clearfix + = link_to 'Unblock User', unblock_admin_user_path(@admin_user), method: :put, class: "btn small right" + - else + %span + Blocked users will be removed from all projects & will not be able to login to GitLab. + .clearfix + = link_to 'Block User', block_admin_user_path(@admin_user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn small right danger" - .alert - .clearfix - %p Make the user a GitLab administrator. - = f.label :admin, class: "checkbox" do - = f.check_box :admin - %span Administrator - - unless @admin_user.new_record? - .alert.alert-error - - if @admin_user.blocked - %span - = link_to 'Unblock', unblock_admin_user_path(@admin_user), method: :put, class: "btn small" - This user is blocked and is not able to login to GitLab - - else - %span - = link_to 'Block', block_admin_user_path(@admin_user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn small danger" - Blocked users will be removed from all projects & will not be able to login to GitLab. + .row + .span6 + .span6 .actions - = f.submit 'Save', class: "btn primary" + = f.submit 'Save', class: "btn save-btn" - if @admin_user.new_record? - = link_to 'Cancel', admin_users_path, class: "btn" + = link_to 'Cancel', admin_users_path, class: "btn cancel-btn" - else - = link_to 'Cancel', admin_user_path(@admin_user), class: "btn" + = link_to 'Cancel', admin_user_path(@admin_user), class: "btn cancel-btn" diff --git a/app/views/admin/users/edit.html.haml b/app/views/admin/users/edit.html.haml index 0e94be9e..032e3cfa 100644 --- a/app/views/admin/users/edit.html.haml +++ b/app/views/admin/users/edit.html.haml @@ -1,3 +1,3 @@ -%h3= @admin_user.name +%h3.page_title #{@admin_user.name} → Edit user %hr = render 'form' diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 3d027217..5ef94ef5 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -1,4 +1,4 @@ -%h3 +%h3.page_title Users = link_to 'New User', new_admin_user_path, class: "btn small right" %br @@ -19,7 +19,7 @@ = link_to admin_users_path(filter: "wop") do Without projects -%table.admin-table +%table %thead %th Admin %th Name diff --git a/app/views/admin/users/new.html.haml b/app/views/admin/users/new.html.haml index 87d6b0f2..70ead0d3 100644 --- a/app/views/admin/users/new.html.haml +++ b/app/views/admin/users/new.html.haml @@ -1,3 +1,3 @@ -%h2 New user -%hr +%h3.page_title New user +%br = render 'form' diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 4cca8ed7..731916e9 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -8,7 +8,11 @@ %br -%table.zebra-striped.table-bordered +%table.zebra-striped + %thead + %tr + %th Profile + %th %tr %td %b @@ -57,15 +61,15 @@ %h3 Add User to Projects %br = form_tag team_update_admin_user_path(@admin_user), class: "bulk_import", method: :put do - %table.table-bordered + %table %thead %tr %th Projects %th Project Access: %tr - %td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name), multiple: true - %td= select_tag :project_access, options_for_select(Project.access_options), class: "project-access-select" + %td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5' + %td= select_tag :project_access, options_for_select(Project.access_options), class: "project-access-select chosen span3" %tr %td= submit_tag 'Add', class: "btn primary" @@ -78,9 +82,9 @@ %h3 Projects %br - %table.zebra-striped.table-bordered - %tr - %thead + %table.zebra-striped + %thead + %tr %th Name %th Project Access %th @@ -93,17 +97,3 @@ %td= select_tag :tm_project_access, options_for_select(Project.access_options, tm.project_access), class: "medium project-access-select", disabled: :disabled %td= link_to 'Edit Access', edit_admin_team_member_path(tm), class: "btn small" %td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn small danger" - -:css - form select { - width:150px; - } - - #project_ids { - width:300px; - } - -:javascript - $('select#project_ids').chosen(); - $('select#repo_access').chosen(); - $('select#project_access').chosen(); diff --git a/app/views/commits/_commit.html.haml b/app/views/commits/_commit.html.haml index 686a4337..61371d14 100644 --- a/app/views/commits/_commit.html.haml +++ b/app/views/commits/_commit.html.haml @@ -4,8 +4,8 @@ %strong= link_to "Browse Code »", tree_project_ref_path(@project, commit.id), class: "right" %p = link_to commit.short_id(8), project_commit_path(@project, id: commit.id), class: "commit_short_id" - %strong.cgray= commit.author_name - – + %strong.commit-author-name= commit.author_name + %span.dash – = image_tag gravatar_icon(commit.author_email), class: "avatar", width: 16 = link_to_gfm truncate(commit.title, length: 50), project_commit_path(@project, id: commit.id), class: "row_title" diff --git a/app/views/commits/_commit_box.html.haml b/app/views/commits/_commit_box.html.haml index 506f4e09..572337de 100644 --- a/app/views/commits/_commit_box.html.haml +++ b/app/views/commits/_commit_box.html.haml @@ -11,10 +11,10 @@ = link_to tree_project_ref_path(@project, @commit.id), class: "browse-button primary grouped" do %strong Browse Code » %h3.commit-title.page_title - = gfm @commit.title + = gfm escape_once(@commit.title) - if @commit.description.present? %pre.commit-description - = gfm @commit.description + = gfm escape_once(@commit.description) .commit-info .row .span4 diff --git a/app/views/commits/_head.html.haml b/app/views/commits/_head.html.haml index a211329f..a8111a72 100644 --- a/app/views/commits/_head.html.haml +++ b/app/views/commits/_head.html.haml @@ -1,9 +1,5 @@ %ul.nav.nav-tabs - %li - = form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do - = select_tag "ref", grouped_options_refs, onchange: "$(this.form).trigger('submit');", class: "project-refs-select" - = hidden_field_tag :destination, "commits" - + %li= render partial: 'shared/ref_switcher', locals: {destination: 'commits'} %li{class: "#{'active' if current_page?(project_commits_path(@project)) }"} = link_to project_commits_path(@project) do Commits @@ -20,14 +16,8 @@ Tags %span.badge= @project.repo.tag_count - - if current_page?(project_commits_path(@project)) && current_user.private_token %li.right %span.rss-icon = link_to project_commits_path(@project, :atom, { private_token: current_user.private_token, ref: @ref }), title: "Feed" do = image_tag "rss_ui.png", title: "feed" - -:javascript - $(function(){ - $('.project-refs-select').chosen(); - }); diff --git a/app/views/commits/_text_file.html.haml b/app/views/commits/_text_file.html.haml index 0f6210f2..9f5b5345 100644 --- a/app/views/commits/_text_file.html.haml +++ b/app/views/commits/_text_file.html.haml @@ -13,14 +13,11 @@ %td.old_line = link_to raw(type == "new" ? " " : line_old), "##{line_code}", id: line_code - if @comments_allowed - = link_to "", "#", class: "line_note_link", "line_code" => line_code, title: "Add note for this line" + = render "notes/per_line_note_link", line_code: line_code %td.new_line= link_to raw(type == "old" ? " " : line_new) , "##{line_code}", id: line_code %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw "#{line}  " - if @comments_allowed - - comments = @line_notes.select { |n| n.line_code == line_code }.sort_by(&:created_at).reverse + - comments = @line_notes.select { |n| n.line_code == line_code }.sort_by(&:created_at) - unless comments.empty? - - comments.each_with_index do |note, i| - = render "notes/reply_button", line_code: line_code if i.zero? - = render "notes/per_line_show", note: note - - @line_notes.reject!{ |n| n == note } + = render "notes/per_line_notes_with_reply", notes: comments diff --git a/app/views/commits/compare.html.haml b/app/views/commits/compare.html.haml index be915cd1..db15ba53 100644 --- a/app/views/commits/compare.html.haml +++ b/app/views/commits/compare.html.haml @@ -1,16 +1,16 @@ = render "head" -%h3 +%h3.page_title Compare View %hr %div - %p + %p.slead Fill input field with commit id like - %code '4eedf23' + %code.label_branch 4eedf23 or branch/tag name like - %code master - & press compare button for commits list, code diff. + %code.label_branch master + and press compare button for commits list, code diff. %br @@ -19,22 +19,24 @@ = text_field_tag :from, params[:from], placeholder: "master", class: "xlarge" = "..." = text_field_tag :to, params[:to], placeholder: "aa8b4ef", class: "xlarge" + - if @refs_are_same + .alert + %span Refs are the same .actions - = submit_tag "Compare", class: "btn btn-primary" + = submit_tag "Compare", class: "btn primary wide commits-compare-btn" - -- unless @commits.empty? +- if @commits.present? %div.ui-box %h5.small Commits (#{@commits.count}) %ul.unstyled= render @commits -- unless @diffs.empty? - %h4 Diff - = render "commits/diffs", diffs: @diffs + - unless @diffs.empty? + %h4 Diff + = render "commits/diffs", diffs: @diffs :javascript $(function() { - var availableTags = #{@project.heads.map(&:name).to_json}; + var availableTags = #{@project.ref_names.to_json}; $("#from").autocomplete({ source: availableTags, @@ -45,5 +47,7 @@ source: availableTags, minLength: 1 }); + + disableButtonIfEmptyField('#to', '.commits-compare-btn'); }); diff --git a/app/views/commits/show.html.haml b/app/views/commits/show.html.haml index 9a483aa2..d12fff96 100644 --- a/app/views/commits/show.html.haml +++ b/app/views/commits/show.html.haml @@ -1,16 +1,10 @@ = render "commits/commit_box" = render "commits/diffs", diffs: @commit.diffs -= render "notes/notes", tid: @commit.id, tt: "commit" += render "notes/notes_with_form", tid: @commit.id, tt: "commit" = render "notes/per_line_form" :javascript - $(document).ready(function(){ - $(".line_note_link, .line_note_reply_link").live("click", function(e) { - var form = $(".per_line_form"); - $(this).parent().parent().after(form); - form.find("#note_line_code").val($(this).attr("line_code")); - form.show(); - return false; - }); + $(function(){ + PerLineNotes.init(); }); diff --git a/app/views/dashboard/index.html.haml b/app/views/dashboard/index.html.haml index ba7d019c..791c18e3 100644 --- a/app/views/dashboard/index.html.haml +++ b/app/views/dashboard/index.html.haml @@ -19,22 +19,31 @@ = link_to new_project_path, class: "btn very_small info" do %i.icon-plus New Project - - @projects.each do |project| - = link_to project_path(project), class: dom_class(project) do - %h4 - %span.ico.project - = truncate(project.name, length: 25) - %span.right - → + %ul.unstyled + - @projects.each do |project| + %li.wll + = link_to project_path(project), class: dom_class(project) do + %strong.project_name= truncate(project.name, length: 25) + %span.arrow + → + %span.last_activity + %strong Last activity: + %span= project_last_activity(project) .bottom= paginate @projects, theme: "gitlab" - %hr %div %span.rss-icon = link_to dashboard_path(:atom, { private_token: current_user.private_token }) do = image_tag "rss_ui.png", title: "feed" %strong News Feed + %hr + .gitlab-promo + = link_to "Homepage", "http://gitlabhq.com" + = link_to "Blog", "http://blog.gitlabhq.com" + = link_to "@gitlabhq", "https://twitter.com/gitlabhq" + + - else %h3.nothing_here_message There are no projects you have access to. %br diff --git a/app/views/deploy_keys/_form.html.haml b/app/views/deploy_keys/_form.html.haml index 461f1f5d..6beba562 100644 --- a/app/views/deploy_keys/_form.html.haml +++ b/app/views/deploy_keys/_form.html.haml @@ -11,8 +11,13 @@ .input= f.text_field :title .clearfix = f.label :key - .input= f.text_area :key, class: "xlarge" - .actions - = f.submit 'Save', class: "primary btn" - = link_to "Cancel", project_deploy_keys_path(@project), class: "btn" + .input + = f.text_area :key, class: [:xxlarge, :thin_area] + %p.hint + Paste a machine public key here. Read more about how generate it + = link_to "here", help_ssh_path + + .actions + = f.submit 'Save', class: "save-btn btn" + = link_to "Cancel", project_deploy_keys_path(@project), class: "btn cancel-btn" diff --git a/app/views/deploy_keys/index.html.haml b/app/views/deploy_keys/index.html.haml index 0ee9d03b..b9c654a1 100644 --- a/app/views/deploy_keys/index.html.haml +++ b/app/views/deploy_keys/index.html.haml @@ -1,11 +1,17 @@ = render "repositories/head" -- if can? current_user, :admin_project, @project - .alert-message.block-message - Deploy keys allow read-only access to repository. + +%p.slead + Deploy keys allow read-only access to repository. It matches perfectly for CI, staging or production servers. + + - if can? current_user, :admin_project, @project = link_to new_project_deploy_key_path(@project), class: "btn small", title: "New Deploy Key" do Add Deploy Key - - if @keys.any? %table + %thead + %tr + %th Keys + %th + %th - @keys.each do |key| = render(partial: 'show', locals: {key: key}) diff --git a/app/views/deploy_keys/new.html.haml b/app/views/deploy_keys/new.html.haml index a2f1010b..e973cb7d 100644 --- a/app/views/deploy_keys/new.html.haml +++ b/app/views/deploy_keys/new.html.haml @@ -1,6 +1,6 @@ = render "repositories/head" -%h3 New Deploy key +%h3.page_title New Deploy key %hr = render 'form' diff --git a/app/views/deploy_keys/show.html.haml b/app/views/deploy_keys/show.html.haml index b1e0dc5c..c94cf10d 100644 --- a/app/views/deploy_keys/show.html.haml +++ b/app/views/deploy_keys/show.html.haml @@ -1,7 +1,14 @@ = render "repositories/head" -%h3= @key.title +%h3.page_title + Deploy key: + = @key.title + %small + created at + = @key.created_at.stamp("Aug 21, 2011") +.back_link + = link_to project_deploy_keys_path(@project) do + ← To keys list %hr %pre= @key.key -.actions +.right = link_to 'Remove', project_deploy_key_path(@key.project, @key), confirm: 'Are you sure?', method: :delete, class: "danger btn delete-key" - .clear diff --git a/app/views/devise/sessions/_new_ldap.html.erb b/app/views/devise/sessions/_new_ldap.html.erb deleted file mode 100644 index 35dfaf71..00000000 --- a/app/views/devise/sessions/_new_ldap.html.erb +++ /dev/null @@ -1,39 +0,0 @@ -<%= form_tag(user_omniauth_callback_path(:ldap), :class => "login-box", :id => 'new_ldap_user' ) do %> - <%= image_tag "login-logo.png", :width => "304", :height => "66", :class => "login-logo", :alt => "Login Logo" %> - - <%= text_field_tag :username, nil, {:class => "text top", :placeholder => "LDAP Login"} %> - <%= password_field_tag :password, nil, {:class => "text bottom", :placeholder => "Password"} %> - -
- <%= submit_tag "LDAP Sign in", :class => "primary btn" %> - - <%- if devise_mapping.omniauthable? %> - <%- (resource_class.omniauth_providers - [:ldap]).each do |provider| %> -
- <%= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider), :class => "btn primary" %>
- <% end -%> - <% end -%> - -
- Other Sign in - - -<% end %> - - -<%= form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => { :class => "login-box" }) do |f| %> - <%= f.text_field :email, :class => "text top", :placeholder => "Email" %> - <%= f.password_field :password, :class => "text bottom", :placeholder => "Password" %> - - <% if devise_mapping.rememberable? -%> -
- <% end -%> -
- <%= f.submit "Sign in", :class => "primary btn" %> -
<%= render :partial => "devise/shared/links" %>
- -<% end %> diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml new file mode 100644 index 00000000..4233aa61 --- /dev/null +++ b/app/views/devise/sessions/_new_ldap.html.haml @@ -0,0 +1,29 @@ += form_tag(user_omniauth_callback_path(:ldap), :class => "login-box", :id => 'new_ldap_user' ) do + = image_tag "login-logo.png", :width => "304", :height => "66", :class => "login-logo", :alt => "Login Logo" + = text_field_tag :username, nil, {:class => "text top", :placeholder => "LDAP Login"} + = password_field_tag :password, nil, {:class => "text bottom", :placeholder => "Password"} + %br/ + = submit_tag "LDAP Sign in", :class => "primary btn" + - if devise_mapping.omniauthable? + - (resource_class.omniauth_providers - [:ldap]).each do |provider| + %hr/ + = link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider), :class => "btn primary" + %br/ + %hr/ + %a#other_form_toggle{:href => "#", :onclick => "javascript:$('#new_user').toggle();"} Other Sign in + :javascript + $(function() { + $('#new_user').toggle(); + }); += form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => { :class => "login-box" }) do |f| + = f.text_field :email, :class => "text top", :placeholder => "Email" + = f.password_field :password, :class => "text bottom", :placeholder => "Password" + - if devise_mapping.rememberable? + .clearfix.inputs-list + %label.checkbox.remember_me{:for => "user_remember_me"} + = f.check_box :remember_me + %span Remember me + %br/ + = f.submit "Sign in", :class => "primary btn" + .right + = render :partial => "devise/shared/links" diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb deleted file mode 100644 index a0383866..00000000 --- a/app/views/devise/sessions/new.html.erb +++ /dev/null @@ -1,27 +0,0 @@ -<% unless ldap_enable? -%> - - <%= form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => { :class => "login-box" }) do |f| %> - <%= image_tag "login-logo.png", :width => "304", :height => "66", :class => "login-logo", :alt => "Login Logo" %> - - <%= f.text_field :email, :class => "text top", :placeholder => "Email" %> - <%= f.password_field :password, :class => "text bottom", :placeholder => "Password" %> - - <% if devise_mapping.rememberable? -%> -
- <% end -%> -
- <%= f.submit "Sign in", :class => "primary btn" %> -
<%= render :partial => "devise/shared/links" %>
- - <%- if devise_mapping.omniauthable? %> - <%- resource_class.omniauth_providers.each do |provider| %> -
- <%= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider), :class => "btn primary" %>
- <% end -%> - <% end -%> - - <% end %> - -<% else %> - <%= render :partial => 'devise/sessions/new_ldap' %> -<% end %> diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml new file mode 100644 index 00000000..07ecf70b --- /dev/null +++ b/app/views/devise/sessions/new.html.haml @@ -0,0 +1,21 @@ +- if ldap_enable? + = render :partial => 'devise/sessions/new_ldap' +- else + = form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => { :class => "login-box" }) do |f| + = image_tag "login-logo.png", :width => "304", :height => "66", :class => "login-logo", :alt => "Login Logo" + = f.text_field :email, :class => "text top", :placeholder => "Email" + = f.password_field :password, :class => "text bottom", :placeholder => "Password" + - if devise_mapping.rememberable? + .clearfix.inputs-list + %label.checkbox.remember_me{:for => "user_remember_me"} + = f.check_box :remember_me + %span Remember me + %br/ + = f.submit "Sign in", :class => "primary btn wide" + .right + = render :partial => "devise/shared/links" + - if devise_mapping.omniauthable? + %hr/ + - resource_class.omniauth_providers.each do |provider| + %span + = link_to authbutton(provider, 32), omniauth_authorize_path(resource_name, provider) diff --git a/app/views/errors/encoding.html.haml b/app/views/errors/encoding.html.haml index 4662437f..d7b5e68e 100644 --- a/app/views/errors/encoding.html.haml +++ b/app/views/errors/encoding.html.haml @@ -1,5 +1,3 @@ -.alert-message.block-message.error - %h3 Encoding Error - %hr - %p - Page can't be loaded because of an encoding error. +%h1 Encoding Error +%hr +%p Page can't be loaded because of an encoding error. diff --git a/app/views/errors/gitolite.html.haml b/app/views/errors/gitolite.html.haml index 50268b1a..699e6984 100644 --- a/app/views/errors/gitolite.html.haml +++ b/app/views/errors/gitolite.html.haml @@ -1,6 +1,6 @@ %h1 Git Error %hr -%h2 Gitlab was unable to access your Gitolite system. +%h2 GitLab was unable to access your Gitolite system. .git_error_tips %h4 Tips for Administrator: @@ -21,5 +21,5 @@ Permissions: %pre = preserve do - sudo chmod -R 770 /home/git/repositories/ - sudo chown -R git:git /home/git/repositories/ + sudo chmod -R 770 #{Gitlab.config.git_base_path} + sudo chown -R git:git #{Gitlab.config.git_base_path} diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml index 1e5c00cb..cb25d831 100644 --- a/app/views/events/_commit.html.haml +++ b/app/views/events/_commit.html.haml @@ -5,4 +5,4 @@ %strong.cdark= commit.author_name – = image_tag gravatar_icon(commit.author_email), class: "avatar", width: 16 - = gfm truncate(commit.title, length: 50) rescue "--broken encoding" + = gfm escape_once(truncate(commit.title, length: 50)) rescue "--broken encoding" diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml index d49f0382..7bae8db1 100644 --- a/app/views/events/_event.html.haml +++ b/app/views/events/_event.html.haml @@ -11,3 +11,7 @@ .event_feed = render "events/event_push", event: event + - elsif event.membership_changed? + .event_feed + = render "events/event_membership_changed", event: event + diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml index 4ef92749..aa1d28f2 100644 --- a/app/views/events/_event_last_push.html.haml +++ b/app/views/events/_event_last_push.html.haml @@ -2,12 +2,12 @@ .event_lp %div = image_tag gravatar_icon(event.author_email), class: "avatar" - %span Your pushed to + %span Your pushed to = event.ref_type = link_to project_commits_path(event.project, ref: event.ref_name) do %strong= truncate(event.ref_name, length: 28) at %strong= link_to event.project.name, event.project - = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn very_small primary" do + = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn very_small" do Create Merge Request diff --git a/app/views/events/_event_membership_changed.html.haml b/app/views/events/_event_membership_changed.html.haml new file mode 100644 index 00000000..464f24b3 --- /dev/null +++ b/app/views/events/_event_membership_changed.html.haml @@ -0,0 +1,9 @@ += image_tag gravatar_icon(event.author_email), class: "avatar" +%strong #{event.author_name} +%span.event_label{class: event.action_name}= event.action_name +project +%strong= link_to event.project_name, event.project +%span.cgray + = time_ago_in_words(event.created_at) + ago. + diff --git a/app/views/help/api.html.haml b/app/views/help/api.html.haml index 344f8ade..00085166 100644 --- a/app/views/help/api.html.haml +++ b/app/views/help/api.html.haml @@ -1,53 +1,96 @@ -%h3 API +%h3.page_title API .back_link = link_to help_path do ← to index -%hr - -%ol - %li - %a{href: "#README"} README - %li - %a{href: "#projects"} Projects - %li - %a{href: "#users"} Users - %li - %a{href: "#issues"} Issues - -.file_holder#README - .file_title - %i.icon-file - README - .file_content.wiki - = preserve do - = markdown File.read(Rails.root.join("doc", "api", "README.md")) - %br -.file_holder#projects - .file_title - %i.icon-file - Projects - .file_content.wiki - = preserve do - = markdown File.read(Rails.root.join("doc", "api", "projects.md")) +%ul.nav.nav-tabs.log-tabs + %li.active + = link_to "README", "#README", 'data-toggle' => 'tab' + %li + = link_to "Projects", "#projects", 'data-toggle' => 'tab' + %li + = link_to "Snippets", "#snippets", 'data-toggle' => 'tab' + %li + = link_to "Repositories", "#repositories", 'data-toggle' => 'tab' + %li + = link_to "Users", "#users", 'data-toggle' => 'tab' + %li + = link_to "Session", "#session", 'data-toggle' => 'tab' + %li + = link_to "Issues", "#issues", 'data-toggle' => 'tab' + %li + = link_to "Milestones", "#milestones", 'data-toggle' => 'tab' -%br +.tab-content + .tab-pane.active#README + .file_holder + .file_title + %i.icon-file + README + .file_content.wiki + = preserve do + = markdown File.read(Rails.root.join("doc", "api", "README.md")) -.file_holder#users - .file_title - %i.icon-file - Users - .file_content.wiki - = preserve do - = markdown File.read(Rails.root.join("doc", "api", "users.md")) + .tab-pane#projects + .file_holder + .file_title + %i.icon-file + Projects + .file_content.wiki + = preserve do + = markdown File.read(Rails.root.join("doc", "api", "projects.md")) -%br + .tab-pane#snippets + .file_holder + .file_title + %i.icon-file + Projects Snippets + .file_content.wiki + = preserve do + = markdown File.read(Rails.root.join("doc", "api", "snippets.md")) -.file_holder#issues - .file_title - %i.icon-file - Issues - .file_content.wiki - = preserve do - = markdown File.read(Rails.root.join("doc", "api", "issues.md")) + .tab-pane#repositories + .file_holder + .file_title + %i.icon-file + Projects + .file_content.wiki + = preserve do + = markdown File.read(Rails.root.join("doc", "api", "repositories.md")) + + .tab-pane#users + .file_holder + .file_title + %i.icon-file + Users + .file_content.wiki + = preserve do + = markdown File.read(Rails.root.join("doc", "api", "users.md")) + + .tab-pane#session + .file_holder + .file_title + %i.icon-file + Session + .file_content.wiki + = preserve do + = markdown File.read(Rails.root.join("doc", "api", "session.md")) + + .tab-pane#issues + .file_holder + .file_title + %i.icon-file + Issues + .file_content.wiki + = preserve do + = markdown File.read(Rails.root.join("doc", "api", "issues.md")) + + .tab-pane#milestones + .file_holder + .file_title + %i.icon-file + Milestones + .file_content.wiki + = preserve do + = markdown File.read(Rails.root.join("doc", "api", "milestones.md")) diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index 02549577..962f2175 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -30,7 +30,7 @@ %h5= link_to "API", help_api_path %li - %h5= link_to "Gitlab Markdown", help_markdown_path + %h5= link_to "GitLab Markdown", help_markdown_path %li %h5= link_to "SSH keys", help_ssh_path diff --git a/app/views/help/markdown.html.haml b/app/views/help/markdown.html.haml index 8d6fb2a5..aa608ed6 100644 --- a/app/views/help/markdown.html.haml +++ b/app/views/help/markdown.html.haml @@ -1,25 +1,129 @@ -- bash_lexer = Pygments::Lexer[:bash] -%h3.page_title Gitlab Markdown +%h3.page_title GitLab Flavored Markdown .back_link = link_to help_path do ← to index %hr -%p.slead We extend Markdown with some GITLAB specific syntax. It allows you to link to: +.row + .span8 + %p + For GitLab we developed something we call "GitLab Flavored Markdown" (GFM). + It extends the standard Markdown in a few significant ways adds some useful functionality. -%ul - %li issues (#123) - %li merge request (!123) - %li commits (1234567) - %li team members (@foo) - %li snippets ($123) + %p You can use GFM in: + %ul + %li commit messages + %li comments + %li wall posts + %li issues + %li merge requests + %li milestones + %li wiki pages -%p.slead in + .span4 + .alert.alert-info + %p + If you're not already familiar with Markdown, you should spend 15 minutes and go over the excellent + %strong= link_to "Markdown Syntax Guide", "http://daringfireball.net/projects/markdown/syntax" + at Daring Fireball. -%ul - %li commit messages - %li notes/comments/wall posts - %li issues - %li merge requests - %li milestones - %li wiki pages +.row + .span8 + %h3 Differences from traditional Markdown + + %h4 Newlines + + %p + The biggest difference that GFM introduces is in the handling of linebreaks. + With traditional Markdown you can hard wrap paragraphs of text and they will be combined into a single paragraph. We find this to be the cause of a huge number of unintentional formatting errors. + GFM treats newlines in paragraph-like content as real line breaks, which is probably what you intended. + + + %p The next paragraph contains two phrases separated by a single newline character: + %pre= "Roses are red\nViolets are blue" + %p becomes + = markdown "Roses are red\nViolets are blue" + + %h4 Multiple underscores in words + + %p + It is not reasonable to italicize just part of a word, especially when you're dealing with code and names often appear with multiple underscores. + Therefore, GFM ignores multiple underscores in words. + + %pre= "perform_complicated_task\ndo_this_and_do_that_and_another_thing" + %p becomes + = markdown "perform_complicated_task\ndo_this_and_do_that_and_another_thing" + + %h4 URL autolinking + + %p + GFM will autolink standard URLs you copy and paste into your text. + So if you want to link to a URL (instead of a textual link), you can simply put the URL in verbatim and it will be turned into a link to that URL. + + %h4 Fenced code blocks + + %p + Markdown converts text with four spaces at the front of each line to code blocks. + GFM supports that, but we also support fenced blocks. + Just wrap your code blocks in ``` and you won't need to indent manually to trigger a code block. + + %pre= %Q{```ruby\nrequire 'redcarpet'\nmarkdown = Redcarpet.new("Hello World!")\nputs markdown.to_html\n```} + %p becomes + = markdown %Q{```ruby\nrequire 'redcarpet'\nmarkdown = Redcarpet.new("Hello World!")\nputs markdown.to_html\n```} + + %h4 Emoji + +.row + .span8 + :ruby + puts markdown %Q{Sometimes you want to be :cool: and add some :sparkles: to your :speech_balloon:. Well we have a :gift: for you: + + :exclamation: You can use emoji anywhere GFM is supported. :sunglasses: + + You can use it to point out a :bug: or warn about :monkey:patches. And if someone improves your really :snail: code, send them a :bouquet: or some :candy:. People will :heart: you for that. + + If you are :new: to this, don't be :fearful:. You can easily join the emoji :circus_tent:. All you need to do is to :book: up on the supported codes. + } + + .span4 + .alert.alert-info + %p + Consult the + %strong= link_to "Emoji Cheat Sheet", "http://www.emoji-cheat-sheet.com/" + for a list of all supported emoji codes. + +.row + .span8 + %h4 Special GitLab references + + %p + GFM recognizes special references. + You can easily reference e.g. a team member, an issue or a commit within a project. + GFM will turn that reference into a link so you can navigate between them easily. + + %p GFM will recognize the following references: + %ul + %li + %code @foo + for team members + %li + %code #123 + for issues + %li + %code !123 + for merge request + %li + %code $123 + for snippets + %li + %code 1234567 + for commits + + -# this example will only be shown if the user has a project with at least one issue + - if @project = current_user.projects.first + - if issue = @project.issues.first + %p For example in your #{link_to @project.name, project_path(@project)} project, writing: + %pre= "This is related to ##{issue.id}. @#{current_user.name} is working on solving it." + %p becomes: + = markdown "This is related to ##{issue.id}. @#{current_user.name} is working on solving it." + - @project = nil # Prevent this from bubbling up to page title diff --git a/app/views/help/ssh.html.haml b/app/views/help/ssh.html.haml index 6a581204..3f082333 100644 --- a/app/views/help/ssh.html.haml +++ b/app/views/help/ssh.html.haml @@ -5,7 +5,7 @@ %hr %p.slead - SSH key allows you to establish a secure connection between your computer and Gitlab + SSH key allows you to establish a secure connection between your computer and GitLab %p.slead To generate a new SSH key just open your terminal and use code below. @@ -17,7 +17,7 @@ \# Generating public/private rsa key pair... %p.slead - Next just use code below to dump your public key and add to GITLAB SSH Keys + Next just use code below to dump your public key and add to GitLab SSH Keys %pre.dark cat ~/.ssh/id_rsa.pub diff --git a/app/views/help/system_hooks.html.haml b/app/views/help/system_hooks.html.haml index 9fc8cbab..736c818b 100644 --- a/app/views/help/system_hooks.html.haml +++ b/app/views/help/system_hooks.html.haml @@ -5,7 +5,7 @@ %hr %p.slead - Your Gitlab instance can perform HTTP POST request on next event: create_project, delete_project, create_user, delete_user, change_team_member. + Your GitLab instance can perform HTTP POST request on next event: create_project, delete_project, create_user, delete_user, change_team_member. %br System Hooks can be used for logging or change information in LDAP server. %br diff --git a/app/views/help/web_hooks.html.haml b/app/views/help/web_hooks.html.haml index 263eadf6..65036613 100644 --- a/app/views/help/web_hooks.html.haml +++ b/app/views/help/web_hooks.html.haml @@ -5,11 +5,11 @@ %hr %p.slead - Every Gitlab project can trigger a web server whenever the repo is pushed to. + Every GitLab project can trigger a web server whenever the repo is pushed to. %br Web Hooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server. %br - GITLAB will send POST request with commits information on every push. + GitLab will send POST request with commits information on every push. %h5 Hooks request example: = render "hooks/data_ex" diff --git a/app/views/help/workflow.html.haml b/app/views/help/workflow.html.haml index a3fe3b01..6062ca09 100644 --- a/app/views/help/workflow.html.haml +++ b/app/views/help/workflow.html.haml @@ -24,7 +24,7 @@ git commit -am "My feature is ready" %li - %p Push your branch to gitlabhq + %p Push your branch to GitLab .bash %pre.dark git push origin $feature_name diff --git a/app/views/hooks/_data_ex.html.erb b/app/views/hooks/_data_ex.html.erb index 8d3de3f0..7dd6b9e0 100644 --- a/app/views/hooks/_data_ex.html.erb +++ b/app/views/hooks/_data_ex.html.erb @@ -32,12 +32,12 @@ :timestamp => "2012-01-03T23:36:29+02:00", :url => "http://localhost/diaspora/commits/da1560886d...", :author => { - :name => "gitlab dev user", + :name => "GitLab dev user", :email => "gitlabdev@dv6700.(none)" } } ], - total_commits_count => 3 + total_commits_count => 4 } eos %> diff --git a/app/views/hooks/index.html.haml b/app/views/hooks/index.html.haml index 3d2a381e..1b59c8e8 100644 --- a/app/views/hooks/index.html.haml +++ b/app/views/hooks/index.html.haml @@ -26,11 +26,12 @@ Hooks %small (#{@hooks.count}) %br - %table.admin-table - %tr - %th URL - %th Method - %th + %table + %thead + %tr + %th URL + %th Method + %th - @hooks.each do |hook| %tr %td diff --git a/app/views/issues/_form.html.haml b/app/views/issues/_form.html.haml index 1b67eabd..813ecab2 100644 --- a/app/views/issues/_form.html.haml +++ b/app/views/issues/_form.html.haml @@ -18,12 +18,12 @@ = f.label :assignee_id do %i.icon-user Assign to - .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select a user" }) + .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select a user" }, {class: 'chosen'}) .issue_milestone = f.label :milestone_id do %i.icon-time Milestone - .input= f.select(:milestone_id, @project.milestones.active.all.collect {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }) + .input= f.select(:milestone_id, @project.milestones.active.all.collect {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }, {class: 'chosen'}) .issue_description .clearfix @@ -38,19 +38,20 @@ = f.label :description, "Details" .input = f.text_area :description, maxlength: 2000, class: "xxlarge", rows: 14 - %p.hint Markdown is enabled. + %p.hint Issues are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. .actions - if @issue.new_record? - = f.submit 'Submit new issue', class: "primary btn" + = f.submit 'Submit new issue', class: "btn save-btn" -else - = f.submit 'Save changes', class: "primary btn" + = f.submit 'Save changes', class: "save-btn btn" + - cancel_class = 'btn cancel-btn' - if request.xhr? - = link_to "Cancel", "#back", onclick: "backToIssues();", class: "btn" + = link_to "Cancel", "#back", onclick: "backToIssues();", class: cancel_class - else - if @issue.new_record? - = link_to "Cancel", project_issues_path(@project), class: "btn" + = link_to "Cancel", project_issues_path(@project), class: cancel_class - else - = link_to "Cancel", project_issue_path(@project, @issue), class: "btn" + = link_to "Cancel", project_issue_path(@project, @issue), class: cancel_class diff --git a/app/views/issues/_head.html.haml b/app/views/issues/_head.html.haml index 1f6e7d7f..8ebe3e05 100644 --- a/app/views/issues/_head.html.haml +++ b/app/views/issues/_head.html.haml @@ -5,6 +5,9 @@ %li{class: "#{'active' if current_page?(project_milestones_path(@project))}"} = link_to project_milestones_path(@project), class: "tab" do Milestones + %li{class: "#{'active' if current_page?(project_labels_path(@project))}"} + = link_to project_labels_path(@project), class: "tab" do + Labels %li.right %span.rss-icon = link_to project_issues_path(@project, :atom, { private_token: current_user.private_token }) do diff --git a/app/views/issues/_show.html.haml b/app/views/issues/_show.html.haml index 8500cd40..64401bdd 100644 --- a/app/views/issues/_show.html.haml +++ b/app/views/issues/_show.html.haml @@ -4,7 +4,7 @@ = check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue", disabled: !can?(current_user, :modify_issue, issue) .right - issue.labels.each do |label| - %span.label.label-issue.grouped + %span.label.label-tag.grouped %i.icon-tag = label.name - if issue.notes.any? @@ -34,5 +34,5 @@ - else   - - if issue.upvotes > 0 - %span.badge.badge-success= "+#{issue.upvotes}" + - if issue.votes_count > 0 + = render 'votes/votes_inline', votable: issue diff --git a/app/views/issues/edit.html.haml b/app/views/issues/edit.html.haml index 3c9877f8..b1bc3ba0 100644 --- a/app/views/issues/edit.html.haml +++ b/app/views/issues/edit.html.haml @@ -1,8 +1 @@ = render "form" - -:javascript - $(function(){ - $('select#issue_assignee_id').chosen(); - $('select#issue_milestone_id').chosen(); - }); - diff --git a/app/views/issues/index.html.haml b/app/views/issues/index.html.haml index a6836fd4..22c34baa 100644 --- a/app/views/issues/index.html.haml +++ b/app/views/issues/index.html.haml @@ -6,13 +6,13 @@ .right .span5 - if can? current_user, :write_issue, @project - = link_to new_project_issue_path(@project), class: "right btn small", title: "New Issue", remote: true do + = link_to new_project_issue_path(@project), class: "right btn", title: "New Issue", remote: true, id: "new_issue_link" do %i.icon-plus New Issue = form_tag search_project_issues_path(@project), method: :get, remote: true, id: "issue_search_form", class: :right do = hidden_field_tag :project_id, @project.id, { id: 'project_id' } = hidden_field_tag :status, params[:f] - = search_field_tag :issue_search, nil, { placeholder: 'Search', class: 'issue_search span3 right neib' } + = search_field_tag :issue_search, nil, { placeholder: 'Search', class: 'issue_search span3 right neib search-text-input' } .clearfix diff --git a/app/views/issues/new.html.haml b/app/views/issues/new.html.haml index 3c9877f8..b1bc3ba0 100644 --- a/app/views/issues/new.html.haml +++ b/app/views/issues/new.html.haml @@ -1,8 +1 @@ = render "form" - -:javascript - $(function(){ - $('select#issue_assignee_id').chosen(); - $('select#issue_milestone_id').chosen(); - }); - diff --git a/app/views/issues/show.html.haml b/app/views/issues/show.html.haml index dce8cf6a..da2aeac4 100644 --- a/app/views/issues/show.html.haml +++ b/app/views/issues/show.html.haml @@ -8,22 +8,22 @@ %span.right - if can?(current_user, :admin_project, @project) || @issue.author == current_user - if @issue.closed - = link_to 'Reopen', project_issue_path(@project, @issue, issue: {closed: false }, status_only: true), method: :put, class: "btn small" + = link_to 'Reopen', project_issue_path(@project, @issue, issue: {closed: false }, status_only: true), method: :put, class: "btn grouped success" - else - = link_to 'Close', project_issue_path(@project, @issue, issue: {closed: true }, status_only: true), method: :put, class: "btn small", title: "Close Issue" + = link_to 'Close', project_issue_path(@project, @issue, issue: {closed: true }, status_only: true), method: :put, class: "btn grouped danger", title: "Close Issue" - if can?(current_user, :admin_project, @project) || @issue.author == current_user - = link_to edit_project_issue_path(@project, @issue), class: "btn small" do + = link_to edit_project_issue_path(@project, @issue), class: "btn grouped" do %i.icon-edit Edit - %br - - if @issue.upvotes > 0 - .upvotes#upvotes= "+#{pluralize @issue.upvotes, 'upvote'}" +.right + .span3#votes= render 'votes/votes_block', votable: @issue .back_link = link_to project_issues_path(@project) do ← To issues list + .main_box .top_box_content %h4 @@ -31,7 +31,7 @@ .alert-message.error.status_info Closed - else .alert-message.success.status_info Open - = gfm @issue.title + = gfm escape_once(@issue.title) .middle_box_content %cite.cgray Created by @@ -61,4 +61,4 @@ = markdown @issue.description -.issue_notes#notes= render "notes/notes", tid: @issue.id, tt: "issue" +.issue_notes.voting_notes#notes= render "notes/notes_with_form", tid: @issue.id, tt: "issue" diff --git a/app/views/keys/_form.html.haml b/app/views/keys/_form.html.haml index 9c6e229b..26700803 100644 --- a/app/views/keys/_form.html.haml +++ b/app/views/keys/_form.html.haml @@ -19,6 +19,6 @@ .actions - = f.submit 'Save', class: "primary btn" - = link_to "Cancel", keys_path, class: "btn" + = f.submit 'Save', class: "btn save-btn" + = link_to "Cancel", keys_path, class: "btn cancel-btn" diff --git a/app/views/keys/index.html.haml b/app/views/keys/index.html.haml index 04e9e4cb..fd5a9dad 100644 --- a/app/views/keys/index.html.haml +++ b/app/views/keys/index.html.haml @@ -1,13 +1,13 @@ %h3.page_title SSH Keys - = link_to "Add new", new_key_path, class: "btn small right" + = link_to "Add new", new_key_path, class: "btn right" %hr -%p.slead - SSH key allows you to establish a secure connection between your computer and Gitlab +%p.slead + SSH key allows you to establish a secure connection between your computer and GitLab -%table#keys-table.admin-table +%table#keys-table %thead %tr %th Name @@ -15,7 +15,7 @@ %th - @keys.each do |key| = render(partial: 'show', locals: {key: key}) - - if @keys.blank? + - if @keys.blank? %tr %td{colspan: 3} %h3.nothing_here_message There are no SSH keys with access to your account. diff --git a/app/views/keys/show.html.haml b/app/views/keys/show.html.haml index ffd52b96..a8cba6c8 100644 --- a/app/views/keys/show.html.haml +++ b/app/views/keys/show.html.haml @@ -10,5 +10,5 @@ %hr %pre= @key.key -.actions +.right = link_to 'Remove', @key, confirm: 'Are you sure?', method: :delete, class: "btn danger delete-key" diff --git a/app/views/labels/_label.html.haml b/app/views/labels/_label.html.haml new file mode 100644 index 00000000..8a465a9e --- /dev/null +++ b/app/views/labels/_label.html.haml @@ -0,0 +1,9 @@ +%li.wll + %strong + %i.icon-tag + = label.name + .right + = link_to project_issues_path(label_name: label.name) do + %strong + = pluralize(label.count, 'issue') + = "»" diff --git a/app/views/labels/index.html.haml b/app/views/labels/index.html.haml new file mode 100644 index 00000000..4e41d375 --- /dev/null +++ b/app/views/labels/index.html.haml @@ -0,0 +1,14 @@ += render "issues/head" + +%h3.page_title + Labels +%br +%div.ui-box + %ul.unstyled.labels-table + - @labels.each do |label| + = render 'label', label: label + + - unless @labels.present? + %li + %h3.nothing_here_message Nothing to show here + diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml index d6247d36..f5e423a5 100644 --- a/app/views/layouts/_head_panel.html.haml +++ b/app/views/layouts/_head_panel.html.haml @@ -34,12 +34,4 @@ source: #{raw search_autocomplete_source}, select: function(event, ui) { location.href = ui.item.url } }); - - $(document).keypress(function(e) { - if($(e.target).is(":input")) return; - switch(e.which) { - case 115: focusSearch(); - e.preventDefault(); - } - }); }); diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml index a1938df4..7b79897b 100644 --- a/app/views/layouts/notify.html.haml +++ b/app/views/layouts/notify.html.haml @@ -2,7 +2,7 @@ %head %meta{content: "text/html; charset=utf-8", "http-equiv" => "Content-Type"} %title - gitlabhq + GitLab :css .header h1 {color: #BBBBBB !important; font: bold 32px Helvetica, Arial, sans-serif; margin: 0; padding: 0; line-height: 40px;} .header p {color: #c6c6c6; font: normal 12px Helvetica, Arial, sans-serif; margin: 0; padding: 0; line-height: 18px;} @@ -21,7 +21,7 @@ \  %td{align: "left", style: "padding: 18px 0 10px;", width: "580"} %h1{style: "color: #BBBBBB; font: normal 32px Helvetica, Arial, sans-serif; margin: 0; padding: 0; line-height: 40px;"} - gitlab + GITLAB - if @project | #{@project.name} %table{align: "center", bgcolor: "#fff", border: "0", cellpadding: "0", cellspacing: "0", style: "font-family: Helvetica, Arial, sans-serif; background: #fff;", width: "600"} diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml index b624415d..62c8db5b 100644 --- a/app/views/layouts/profile.html.haml +++ b/app/views/layouts/profile.html.haml @@ -9,20 +9,20 @@ %li.home{class: tab_class(:profile)} = link_to "Profile", profile_path - %li{class: tab_class(:password)} - = link_to "Password", profile_password_path + %li{class: tab_class(:account)} + = link_to "Account", profile_account_path %li{class: tab_class(:ssh_keys)} = link_to keys_path do SSH Keys %span.count= current_user.keys.count - %li{class: tab_class(:token)} - = link_to "Token", profile_token_path - %li{class: tab_class(:design)} = link_to "Design", profile_design_path + %li{class: tab_class(:history)} + = link_to "History", profile_history_path + .content = yield diff --git a/app/views/merge_requests/_form.html.haml b/app/views/merge_requests/_form.html.haml index b6c12397..96692c0f 100644 --- a/app/views/merge_requests/_form.html.haml +++ b/app/views/merge_requests/_form.html.haml @@ -9,27 +9,27 @@ %br .row - .span6 + .span5 .mr_branch_box %h5 From (Head Branch) .body .padded = f.label :source_branch, "From", class: "control-label" .controls - = f.select(:source_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, style: "width:250px") - .bottom_commit - .mr_source_commit + = f.select(:source_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, {class: 'chosen span3'}) + .mr_source_commit - .span6 + .span2 + %center= image_tag "merge.png", class: 'mr_direction_tip' + .span5 .mr_branch_box %h5 To (Base Branch) .body .padded = f.label :target_branch, "To", class: "control-label" .controls - = f.select(:target_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, style: "width:250px") - .bottom_commit - .mr_target_commit + = f.select(:target_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, {class: 'chosen span3'}) + .mr_target_commit %h4.cdark 2. Fill info @@ -43,30 +43,25 @@ = f.label :assignee_id do %i.icon-user Assign to - .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select user" }, style: "width:250px") + .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select user" }, {class: 'chosen span3'}) .control-group .form-actions - = f.submit 'Save', class: "btn-primary btn" + = f.submit 'Save', class: "btn save-btn" - if @merge_request.new_record? - = link_to project_merge_requests_path(@project), class: "btn" do + = link_to project_merge_requests_path(@project), class: "btn cancel-btn" do Cancel - else - = link_to project_merge_request_path(@project, @merge_request), class: "btn" do + = link_to project_merge_request_path(@project, @merge_request), class: "btn cancel-btn" do Cancel - - :javascript $(function(){ - $('select#merge_request_assignee_id').chosen(); - $('select#merge_request_source_branch').chosen(); - $('select#merge_request_target_branch').chosen(); + disableButtonIfEmptyField("#merge_request_title", ".save-btn"); var source_branch = $("#merge_request_source_branch"); var target_branch = $("#merge_request_target_branch"); - $.get("#{branch_from_project_merge_requests_path(@project)}", {ref: source_branch.val() }); $.get("#{branch_to_project_merge_requests_path(@project)}", {ref: target_branch.val() }); @@ -78,4 +73,3 @@ $.get("#{branch_to_project_merge_requests_path(@project)}", {ref: $(this).val() }); }); }); - diff --git a/app/views/merge_requests/_merge_request.html.haml b/app/views/merge_requests/_merge_request.html.haml index 74996090..9d94d670 100644 --- a/app/views/merge_requests/_merge_request.html.haml +++ b/app/views/merge_requests/_merge_request.html.haml @@ -23,5 +23,6 @@ authored by #{merge_request.author_name} = time_ago_in_words(merge_request.created_at) ago - - if merge_request.upvotes > 0 - %span.badge.badge-success= "+#{merge_request.upvotes}" + + - if merge_request.votes_count > 0 + = render 'votes/votes_inline', votable: merge_request diff --git a/app/views/merge_requests/_show.html.haml b/app/views/merge_requests/_show.html.haml index f1b3fa9f..f1d0c8aa 100644 --- a/app/views/merge_requests/_show.html.haml +++ b/app/views/merge_requests/_show.html.haml @@ -15,8 +15,8 @@ %i.icon-list-alt Diff -.merge_request_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" } - = render("notes/notes", tid: @merge_request.id, tt: "merge_request") +.merge_request_notes.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" } + = render("notes/notes_with_form", tid: @merge_request.id, tt: "merge_request") .merge-request-diffs = render "merge_requests/show/diffs" if @diffs .status diff --git a/app/views/merge_requests/diffs.html.haml b/app/views/merge_requests/diffs.html.haml index 176b19bc..a755491c 100644 --- a/app/views/merge_requests/diffs.html.haml +++ b/app/views/merge_requests/diffs.html.haml @@ -1,2 +1,6 @@ = render "show" +:javascript + $(function(){ + PerLineNotes.init(); + }); diff --git a/app/views/merge_requests/diffs.js.haml b/app/views/merge_requests/diffs.js.haml index b147e5be..98539985 100644 --- a/app/views/merge_requests/diffs.js.haml +++ b/app/views/merge_requests/diffs.js.haml @@ -1,4 +1,7 @@ :plain $(".merge-request-diffs").html("#{escape_javascript(render(partial: "merge_requests/show/diffs"))}"); + $(function(){ + PerLineNotes.init(); + }); diff --git a/app/views/merge_requests/index.html.haml b/app/views/merge_requests/index.html.haml index 4ad6e5c1..bbf35dc7 100644 --- a/app/views/merge_requests/index.html.haml +++ b/app/views/merge_requests/index.html.haml @@ -1,7 +1,7 @@ %h3.page_title Merge Requests - if can? current_user, :write_issue, @project - = link_to new_project_merge_request_path(@project), class: "right btn small", title: "New Merge Request" do + = link_to new_project_merge_request_path(@project), class: "right btn", title: "New Merge Request" do New Merge Request %br @@ -10,17 +10,17 @@ .ui-box .title %ul.nav.nav-pills - %li{class: ("active" if (params[:f] == "0" || !params[:f]))} - = link_to project_merge_requests_path(@project, f: 0) do + %li{class: ("active" if (params[:f] == 'open' || !params[:f]))} + = link_to project_merge_requests_path(@project, f: 'open') do Open - %li{class: ("active" if params[:f] == "2")} - = link_to project_merge_requests_path(@project, f: 2) do + %li{class: ("active" if params[:f] == "closed")} + = link_to project_merge_requests_path(@project, f: "closed") do Closed - %li{class: ("active" if params[:f] == "3")} - = link_to project_merge_requests_path(@project, f: 3) do + %li{class: ("active" if params[:f] == 'assigned-to-me')} + = link_to project_merge_requests_path(@project, f: 'assigned-to-me') do To Me - %li{class: ("active" if params[:f] == "1")} - = link_to project_merge_requests_path(@project, f: 1) do + %li{class: ("active" if params[:f] == 'all')} + = link_to project_merge_requests_path(@project, f: 'all') do All %ul.unstyled diff --git a/app/views/merge_requests/show.js.haml b/app/views/merge_requests/show.js.haml index 7a27b166..f44ccbb5 100644 --- a/app/views/merge_requests/show.js.haml +++ b/app/views/merge_requests/show.js.haml @@ -1,2 +1,2 @@ :plain - $(".merge-request-notes").html("#{escape_javascript(render("notes/notes", tid: @merge_request.id, tt: "merge_request"))}"); + $(".merge-request-notes").html("#{escape_javascript(render notes/notes_with_form", tid: @merge_request.id, tt: "merge_request")}"); diff --git a/app/views/merge_requests/show/_mr_box.html.haml b/app/views/merge_requests/show/_mr_box.html.haml index 81ab83f3..89c3110b 100644 --- a/app/views/merge_requests/show/_mr_box.html.haml +++ b/app/views/merge_requests/show/_mr_box.html.haml @@ -5,7 +5,7 @@ .alert-message.error.status_info Closed - else .alert-message.success.status_info Open - = gfm @merge_request.title + = gfm escape_once(@merge_request.title) .middle_box_content %div diff --git a/app/views/merge_requests/show/_mr_title.html.haml b/app/views/merge_requests/show/_mr_title.html.haml index 31fa0779..8708469c 100644 --- a/app/views/merge_requests/show/_mr_title.html.haml +++ b/app/views/merge_requests/show/_mr_title.html.haml @@ -1,9 +1,9 @@ %h3.page_title = "Merge Request ##{@merge_request.id}:"   - %span.pretty_label.branch= @merge_request.source_branch + %span.label_branch= @merge_request.source_branch → - %span.pretty_label.branch= @merge_request.target_branch + %span.label_branch= @merge_request.target_branch %span.right - if @merge_request.merged? @@ -23,10 +23,8 @@ %i.icon-edit Edit - %br - - if @merge_request.upvotes > 0 - .upvotes#upvotes= "+#{pluralize @merge_request.upvotes, 'upvote'}" - +.right + .span3#votes= render 'votes/votes_block', votable: @merge_request .back_link = link_to project_merge_requests_path(@project) do diff --git a/app/views/milestones/_form.html.haml b/app/views/milestones/_form.html.haml index 1cd08ac3..194eac77 100644 --- a/app/views/milestones/_form.html.haml +++ b/app/views/milestones/_form.html.haml @@ -22,7 +22,7 @@ = f.label :description, "Description", class: "control-label" .controls = f.text_area :description, maxlength: 2000, class: "input-xlarge", rows: 10 - %p.hint Markdown is enabled. + %p.hint Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. .span6 .control-group = f.label :due_date, "Due Date", class: "control-label" @@ -32,20 +32,16 @@ .form-actions - if @milestone.new_record? - = f.submit 'Create milestone', class: "primary btn" + = f.submit 'Create milestone', class: "save-btn btn" + = link_to "Cancel", project_milestones_path(@project), class: "btn cancel-btn" -else - = f.submit 'Save changes', class: "primary btn" + = f.submit 'Save changes', class: "save-btn btn" + = link_to "Cancel", project_milestone_path(@project, @milestone), class: "btn cancel-btn" - - if request.xhr? - = link_to "Cancel", "#back", onclick: "backToIssues();", class: "btn" - - else - - if @milestone.new_record? - = link_to "Cancel", project_milestones_path(@project), class: "btn" - - else - = link_to "Cancel", project_milestone_path(@project, @milestone), class: "btn" :javascript $(function() { + disableButtonIfEmptyField("#milestone_title", ".save-btn"); $( ".datepicker" ).datepicker({ dateFormat: "yy-mm-dd", onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } diff --git a/app/views/milestones/edit.html.haml b/app/views/milestones/edit.html.haml index af975a84..b1bc3ba0 100644 --- a/app/views/milestones/edit.html.haml +++ b/app/views/milestones/edit.html.haml @@ -1,7 +1 @@ = render "form" - -:javascript - $(function(){ - $('select#issue_assignee_id').chosen(); - }); - diff --git a/app/views/milestones/index.html.haml b/app/views/milestones/index.html.haml index ecb008dc..c5333b08 100644 --- a/app/views/milestones/index.html.haml +++ b/app/views/milestones/index.html.haml @@ -8,11 +8,11 @@ %div.ui-box .title %ul.nav.nav-pills - %li{class: ("active" if (params[:f] == "0" || !params[:f]))} - = link_to project_milestones_path(@project, f: 0) do + %li{class: ("active" if (params[:f] == "active" || !params[:f]))} + = link_to project_milestones_path(@project, f: "active") do Active - %li{class: ("active" if params[:f] == "1")} - = link_to project_milestones_path(@project, f: 1) do + %li{class: ("active" if params[:f] == "all")} + = link_to project_milestones_path(@project, f: "all") do All %ul.unstyled diff --git a/app/views/milestones/show.html.haml b/app/views/milestones/show.html.haml index b7da7b66..ba71ead7 100644 --- a/app/views/milestones/show.html.haml +++ b/app/views/milestones/show.html.haml @@ -21,7 +21,7 @@ .alert-message.error.status_info Closed - else .alert-message.success.status_info Open - = gfm @milestone.title + = gfm escape_once(@milestone.title) %small.right= @milestone.expires_at .middle_box_content @@ -42,7 +42,7 @@ .row .span6 - %table.admin-table + %table %thead %th Open Issues - @issues.each do |issue| @@ -56,7 +56,7 @@ = paginate @issues, theme: "gitlab" .span6 - %table.admin-table + %table %thead %th Participants - @users.each do |user| diff --git a/app/views/notes/_common_form.html.haml b/app/views/notes/_common_form.html.haml new file mode 100644 index 00000000..fc6e3c7e --- /dev/null +++ b/app/views/notes/_common_form.html.haml @@ -0,0 +1,39 @@ +.note-form-holder + = form_for [@project, @note], remote: "true", multipart: true do |f| + %h3.page_title Leave a comment + -if @note.errors.any? + .alert-message.block-message.error + - @note.errors.full_messages.each do |msg| + %div= msg + + = f.hidden_field :noteable_id + = f.hidden_field :noteable_type + = f.text_area :note, size: 255, class: 'note-text' + #preview-note.preview_note.hide + .hint + .right Comments are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. + .clearfix + + .row.note_advanced_opts + .span3 + = f.submit 'Add Comment', class: "btn success submit_note grouped", id: "submit_note" + = link_to 'Preview', preview_project_notes_path(@project), class: 'btn grouped', id: 'preview-link' + .span4.notify_opts + %h6.left Notify via email: + = label_tag :notify do + = check_box_tag :notify, 1, @note.noteable_type != "Commit" + %span Project team + + - if @note.notify_only_author?(current_user) + = label_tag :notify_author do + = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit" + %span Commit author + .span5.attachments + %h6.left Attachment: + %span.file_name File name... + + .input.input_file + %a.file_upload.btn.small Upload File + = f.file_field :attachment, class: "input-file" + %span.hint Any file less than 10 MB + diff --git a/app/views/notes/_create_common.js.haml b/app/views/notes/_create_common.js.haml deleted file mode 100644 index e9538902..00000000 --- a/app/views/notes/_create_common.js.haml +++ /dev/null @@ -1,11 +0,0 @@ -- if note.valid? - :plain - $("#new_note .error").remove(); - $('#new_note textarea').val(""); - $('#preview-link').text('Preview'); - $('#preview-note').hide(); $('#note_note').show(); - NoteList.prepend(#{note.id}, "#{escape_javascript(render partial: "notes/show", locals: {note: note})}"); -- else - :plain - $("#new_note").replaceWith("#{escape_javascript(render('form'))}"); - diff --git a/app/views/notes/_create_common_note.js.haml b/app/views/notes/_create_common_note.js.haml new file mode 100644 index 00000000..bbebc247 --- /dev/null +++ b/app/views/notes/_create_common_note.js.haml @@ -0,0 +1,13 @@ +- if note.valid? + :plain + $(".note-form-holder .error").remove(); + $('.note-form-holder textarea').val(""); + $('.note-form-holder #preview-link').text('Preview'); + $('.note-form-holder #preview-note').hide(); + $('.note-form-holder').show(); + NoteList.appendNewNote(#{note.id}, "#{escape_javascript(render "notes/note", note: note)}"); + +- else + :plain + $(".note-form-holder").replaceWith("#{escape_javascript(render 'form')}"); + diff --git a/app/views/notes/_create_line.js.haml b/app/views/notes/_create_line.js.haml deleted file mode 100644 index 13809bec..00000000 --- a/app/views/notes/_create_line.js.haml +++ /dev/null @@ -1,8 +0,0 @@ -- if note.valid? - :plain - $(".per_line_form").hide(); - $('#new_note textarea').val(""); - $("a.line_note_reply_link[line_code='#{note.line_code}']").closest("tr").remove(); - var trEl = $(".#{note.line_code}").parent(); - trEl.after("#{escape_javascript(render partial: "notes/per_line_show", locals: {note: note})}"); - trEl.after("#{escape_javascript(render partial: "notes/reply_button", locals: {line_code: note.line_code})}"); diff --git a/app/views/notes/_create_per_line_note.js.haml b/app/views/notes/_create_per_line_note.js.haml new file mode 100644 index 00000000..180960e3 --- /dev/null +++ b/app/views/notes/_create_per_line_note.js.haml @@ -0,0 +1,19 @@ +- if note.valid? + :plain + // hide and reset the form + $(".per_line_form").hide(); + $('.line-note-form-holder textarea').val(""); + + // find the reply button for this line + // (might not be there if this is the first note) + var trRpl = $("a.line_note_reply_link[data-line-code='#{note.line_code}']").closest("tr"); + if (trRpl.size() == 0) { + // find the commented line ... + var trEl = $(".#{note.line_code}").parent(); + // ... and insert the note and the reply button after it + trEl.after("#{escape_javascript(render "notes/per_line_reply_button", line_code: note.line_code)}"); + trEl.after("#{escape_javascript(render "notes/per_line_note", note: note)}"); + } else { + // instert new note before reply button + trRpl.before("#{escape_javascript(render "notes/per_line_note", note: note)}"); + } diff --git a/app/views/notes/_form.html.haml b/app/views/notes/_form.html.haml deleted file mode 100644 index dac026bd..00000000 --- a/app/views/notes/_form.html.haml +++ /dev/null @@ -1,38 +0,0 @@ -= form_for [@project, @note], remote: "true", multipart: true do |f| - %h3.page_title Leave a comment - -if @note.errors.any? - .alert-message.block-message.error - - @note.errors.full_messages.each do |msg| - %div= msg - - = f.hidden_field :noteable_id - = f.hidden_field :noteable_type - = f.text_area :note, size: 255 - #preview-note.well.hide - %p.hint - = link_to "Gitlab Markdown", help_markdown_path, target: '_blank' - is enabled. - = link_to 'Preview', preview_project_notes_path(@project), id: 'preview-link' - - .row.note_advanced_opts.hide - .span2 - = f.submit 'Add Comment', class: "btn primary submit_note", id: "submit_note" - .span4.notify_opts - %h6.left Notify via email: - = label_tag :notify do - = check_box_tag :notify, 1, @note.noteable_type != "Commit" - %span Project team - - - if @note.notify_only_author?(current_user) - = label_tag :notify_author do - = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit" - %span Commit author - .span6.attachments - %h6.left Attachment: - %span.file_name File name... - - .input.input_file - %a.file_upload.btn.small Upload File - = f.file_field :attachment, class: "input-file" - %span.hint Any file less than 10 MB - diff --git a/app/views/notes/_load.js.haml b/app/views/notes/_load.js.haml deleted file mode 100644 index c16a699a..00000000 --- a/app/views/notes/_load.js.haml +++ /dev/null @@ -1,17 +0,0 @@ -- unless @notes.blank? - - if params[:last_id] - :plain - NoteList.replace("#{escape_javascript(render(partial: 'notes/notes_list'))}"); - - - elsif params[:first_id] - :plain - NoteList.append(#{@notes.last.id}, "#{escape_javascript(render(partial: 'notes/notes_list'))}"); - - - else - :plain - NoteList.setContent(#{@notes.last.id}, #{@notes.first.id}, "#{escape_javascript(render(partial: 'notes/notes_list'))}"); - -- else - - if params[:first_id] - :plain - NoteList.append(#{params[:first_id]}, ""); diff --git a/app/views/notes/_show.html.haml b/app/views/notes/_note.html.haml similarity index 73% rename from app/views/notes/_show.html.haml rename to app/views/notes/_note.html.haml index 3412e4eb..5234e55d 100644 --- a/app/views/notes/_show.html.haml +++ b/app/views/notes/_note.html.haml @@ -1,4 +1,4 @@ -%li{id: dom_id(note), class: "note"} +%li{id: dom_id(note), class: "note #{note_vote_class(note)}"} = image_tag gravatar_icon(note.author.email), class: "avatar s32" %div.note-author %strong= note.author_name @@ -6,8 +6,16 @@ %cite.cgray = time_ago_in_words(note.updated_at) ago + - if note.upvote? + %span.label.label-success + %i.icon-thumbs-up + \+1 + - if note.downvote? + %span.label.label-error + %i.icon-thumbs-down + \-1 - if(note.author_id == current_user.id) || can?(current_user, :admin_note, @project) - = link_to [@project, note], confirm: 'Are you sure?', method: :delete, remote: true, class: "cred delete-note btn very_small" do + = link_to [@project, note], confirm: 'Are you sure?', method: :delete, remote: true, class: "cred delete-note btn very_small" do %i.icon-trash Remove diff --git a/app/views/notes/_notes.html.haml b/app/views/notes/_notes.html.haml index e692e746..adb5dfcb 100644 --- a/app/views/notes/_notes.html.haml +++ b/app/views/notes/_notes.html.haml @@ -1,13 +1,4 @@ -- if can? current_user, :write_note, @project - = render "notes/form" -.clear -%hr -%ul#new_notes_list -%ul#notes-list -.status +- @notes.each do |note| + - next unless note.author + = render "note", note: note - -:javascript - $(function(){ - NoteList.init("#{tid}", "#{tt}", "#{project_notes_path(@project)}"); - }); diff --git a/app/views/notes/_notes_list.html.haml b/app/views/notes/_notes_list.html.haml deleted file mode 100644 index 5673988d..00000000 --- a/app/views/notes/_notes_list.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -- @notes.each do |note| - - next unless note.author - = render partial: "notes/show", locals: {note: note} - diff --git a/app/views/notes/_notes_with_form.html.haml b/app/views/notes/_notes_with_form.html.haml new file mode 100644 index 00000000..53716c1d --- /dev/null +++ b/app/views/notes/_notes_with_form.html.haml @@ -0,0 +1,11 @@ +%ul#notes-list +%ul#new-notes-list +.notes-status + +- if can? current_user, :write_note, @project + = render "notes/common_form" + +:javascript + $(function(){ + NoteList.init("#{tid}", "#{tt}", "#{project_notes_path(@project)}"); + }); diff --git a/app/views/notes/_per_line_form.html.haml b/app/views/notes/_per_line_form.html.haml index afb0b30d..8e31b59e 100644 --- a/app/views/notes/_per_line_form.html.haml +++ b/app/views/notes/_per_line_form.html.haml @@ -1,33 +1,34 @@ %table{style: "display:none;"} %tr.per_line_form %td{colspan: 3 } - = form_for [@project, @note], remote: "true", multipart: true do |f| - %h3.page_title Leave a note - %div.span10 - -if @note.errors.any? - .alert-message.block-message.error - - @note.errors.full_messages.each do |msg| - %div= msg + .line-note-form-holder + = form_for [@project, @note], remote: "true", multipart: true do |f| + %h3.page_title Leave a note + %div.span10 + -if @note.errors.any? + .alert-message.block-message.error + - @note.errors.full_messages.each do |msg| + %div= msg - = f.hidden_field :noteable_id - = f.hidden_field :noteable_type - = f.hidden_field :line_code - = f.text_area :note, size: 255 - .note_actions - .buttons - = f.submit 'Add note', class: "btn primary submit_note", id: "submit_note" - = link_to "Cancel", "#", class: "btn hide-button" - .options - %h6.left Notify via email: - .labels - = label_tag :notify do - = check_box_tag :notify, 1, @note.noteable_type != "Commit" - %span Project team + = f.hidden_field :noteable_id + = f.hidden_field :noteable_type + = f.hidden_field :line_code + = f.text_area :note, size: 255, class: 'line-note-text' + .note_actions + .buttons + = f.submit 'Add note', class: "btn save-btn submit_note submit_inline_note", id: "submit_note" + = link_to "Cancel", "#", class: "btn hide-button" + .options + %h6.left Notify via email: + .labels + = label_tag :notify do + = check_box_tag :notify, 1, @note.noteable_type != "Commit" + %span Project team - - if @note.notify_only_author?(current_user) - = label_tag :notify_author do - = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit" - %span Commit author + - if @note.notify_only_author?(current_user) + = label_tag :notify_author do + = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit" + %span Commit author :javascript $(function(){ diff --git a/app/views/notes/_per_line_note.html.haml b/app/views/notes/_per_line_note.html.haml new file mode 100644 index 00000000..28bcd6e0 --- /dev/null +++ b/app/views/notes/_per_line_note.html.haml @@ -0,0 +1,5 @@ +%tr.line_notes_row + %td{colspan: 3} + %ul + = render "notes/note", note: note + diff --git a/app/views/notes/_per_line_note_link.html.haml b/app/views/notes/_per_line_note_link.html.haml new file mode 100644 index 00000000..72b59a59 --- /dev/null +++ b/app/views/notes/_per_line_note_link.html.haml @@ -0,0 +1 @@ += link_to "", "#", class: "line_note_link", data: { line_code: line_code }, title: "Add note for this line" diff --git a/app/views/notes/_per_line_notes_with_reply.html.haml b/app/views/notes/_per_line_notes_with_reply.html.haml new file mode 100644 index 00000000..1bcfc41f --- /dev/null +++ b/app/views/notes/_per_line_notes_with_reply.html.haml @@ -0,0 +1,3 @@ +- notes.each do |note| + = render "notes/per_line_note", note: note += render "notes/per_line_reply_button", line_code: notes.first.line_code diff --git a/app/views/notes/_per_line_reply_button.html.haml b/app/views/notes/_per_line_reply_button.html.haml new file mode 100644 index 00000000..42c737c7 --- /dev/null +++ b/app/views/notes/_per_line_reply_button.html.haml @@ -0,0 +1,4 @@ +%tr.line_notes_row.reply + %td{colspan: 3} + %i.icon-comment + = link_to "Reply", "#", class: "line_note_reply_link", data: { line_code: line_code }, title: "Add note for this line" diff --git a/app/views/notes/_per_line_show.html.haml b/app/views/notes/_per_line_show.html.haml deleted file mode 100644 index cf1769c0..00000000 --- a/app/views/notes/_per_line_show.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -%tr.line_notes_row - %td{colspan: 3} - %ul - = render partial: "notes/show", locals: {note: note} - diff --git a/app/views/notes/_reply_button.html.haml b/app/views/notes/_reply_button.html.haml deleted file mode 100644 index c981fb9f..00000000 --- a/app/views/notes/_reply_button.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -%tr.line_notes_row.reply - %td{colspan: 3} - %i.icon-comment - = link_to "Reply", "#", class: "line_note_reply_link", "line_code" => line_code, title: "Add note for this line" diff --git a/app/views/notes/_reversed_notes_with_form.html.haml b/app/views/notes/_reversed_notes_with_form.html.haml new file mode 100644 index 00000000..24d59924 --- /dev/null +++ b/app/views/notes/_reversed_notes_with_form.html.haml @@ -0,0 +1,11 @@ +- if can? current_user, :write_note, @project + = render "notes/common_form" + +%ul.reversed#new-notes-list +%ul.reversed#notes-list +.notes-status + +:javascript + $(function(){ + NoteList.init("#{tid}", "#{tt}", "#{project_notes_path(@project)}"); + }); diff --git a/app/views/notes/create.js.haml b/app/views/notes/create.js.haml index 8f631f38..03866591 100644 --- a/app/views/notes/create.js.haml +++ b/app/views/notes/create.js.haml @@ -1,7 +1,7 @@ - if @note.line_code - = render "create_line", note: @note + = render "create_per_line_note", note: @note - else - = render "create_common", note: @note + = render "create_common_note", note: @note -# Enable submit button :plain diff --git a/app/views/notes/index.js.haml b/app/views/notes/index.js.haml index ee31c0b8..3814dbd4 100644 --- a/app/views/notes/index.js.haml +++ b/app/views/notes/index.js.haml @@ -1 +1,17 @@ -= render "notes/load" +- unless @notes.blank? + - if loading_more_notes? + :plain + NoteList.appendMoreNotes(#{@notes.last.id}, "#{escape_javascript(render 'notes/notes')}"); + + - elsif loading_new_notes? + :plain + NoteList.replaceNewNotes("#{escape_javascript(render 'notes/notes')}"); + + - else + :plain + NoteList.setContent(#{@notes.first.id}, #{@notes.last.id}, "#{escape_javascript(render 'notes/notes')}"); + +- else + - if loading_more_notes? + :plain + NoteList.finishedLoadingMore(); diff --git a/app/views/notify/issue_status_changed_email.html.haml b/app/views/notify/issue_status_changed_email.html.haml new file mode 100644 index 00000000..59130f79 --- /dev/null +++ b/app/views/notify/issue_status_changed_email.html.haml @@ -0,0 +1,16 @@ +%td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"} + %table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"} + %tr + %td{style: "font-size: 1px; line-height: 1px;", width: "21"} + %td{align: "left", style: "padding: 20px 0 0;"} + %h2{style: "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "} + = "Issue was #{@issue_status} by #{@updated_by.name}" + %td{style: "font-size: 1px; line-height: 1px;", width: "21"} + %tr + %td{style: "font-size: 1px; line-height: 1px;", width: "21"} + %td{align: "left", style: "padding: 20px 0 0;"} + %h2{style: "color:#646464 !important; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "} + = "Issue ##{@issue.id}" + = link_to_gfm truncate(@issue.title, length: 45), project_issue_url(@issue.project, @issue), title: @issue.title + %br + diff --git a/app/views/notify/new_user_email.html.haml b/app/views/notify/new_user_email.html.haml index d96afc92..93bf7c50 100644 --- a/app/views/notify/new_user_email.html.haml +++ b/app/views/notify/new_user_email.html.haml @@ -6,7 +6,7 @@ %h2{style: "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "} Hi #{@user['name']}! %p{style: "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 20px; font-size: 12px;font-family: Helvetica, Arial, sans-serif; "} - Administrator created account for you. Now you are a member of company gitlab application. + Administrator created account for you. Now you are a member of company GitLab application. %td{style: "font-size: 1px; line-height: 1px;", width: "21"} %tr %td{style: "font-size: 1px; line-height: 1px;", width: "21"} diff --git a/app/views/notify/project_access_granted_email.html.haml b/app/views/notify/project_access_granted_email.html.haml new file mode 100644 index 00000000..154c2aaa --- /dev/null +++ b/app/views/notify/project_access_granted_email.html.haml @@ -0,0 +1,14 @@ +%td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"} + %table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"} + %tr + %td{style: "font-size: 1px; line-height: 1px;", width: "21"} + %td{align: "left", style: "padding: 20px 0 0;"} + %h2{style: "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "} + = "You got granted #{@users_project.project_access_human} access to project" + %td{style: "font-size: 1px; line-height: 1px;", width: "21"} + %tr + %td{style: "font-size: 1px; line-height: 1px;", width: "21"} + %td{align: "left", style: "padding: 20px 0 0;"} + %h2{style: "color:#646464 !important; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "} + = link_to_gfm truncate(@project.name, length: 45), project_url(@project), title: @project.name + %br diff --git a/app/views/profile/account.html.haml b/app/views/profile/account.html.haml new file mode 100644 index 00000000..6707a8ff --- /dev/null +++ b/app/views/profile/account.html.haml @@ -0,0 +1,57 @@ +- if Gitlab.config.omniauth_enabled? + %fieldset + %legend + %h3.page_title Social Accounts + .oauth_select_holder + %p.hint Tip: Click on icon to activate sigin with one of the following services + - User.omniauth_providers.each do |provider| + %span{class: oauth_active_class(provider) } + = link_to authbutton(provider, 32), omniauth_authorize_path(User, provider) + + +%fieldset + %legend + %h3.page_title + Private token + %span.cred.right + keep it in secret! + .padded + = form_for @user, url: profile_reset_private_token_path, method: :put do |f| + .data + %p.slead + Private token used to access application resources without authentication. + %br + It can be used for atom feed or API + %p.cgray + - if current_user.private_token + = text_field_tag "token", current_user.private_token, class: "xxlarge large_text" + = f.submit 'Reset', confirm: "Are you sure?", class: "btn primary btn-build-token" + - else + %span You don`t have one yet. Click generate to fix it. + = f.submit 'Generate', class: "btn success btn-build-token" + +%fieldset + %legend + %h3.page_title Password + = form_for @user, url: profile_password_path, method: :put do |f| + .padded + %p.slead After successful password update you will be redirected to login page where you should login with new password + -if @user.errors.any? + .alert-message.block-message.error + %ul + - @user.errors.full_messages.each do |msg| + %li= msg + + .clearfix + = f.label :password + .input= f.password_field :password + .clearfix + = f.label :password_confirmation + .input= f.password_field :password_confirmation + .actions + = f.submit 'Save', class: "btn save-btn" + + + + + diff --git a/app/views/profile/history.html.haml b/app/views/profile/history.html.haml new file mode 100644 index 00000000..aa7006c5 --- /dev/null +++ b/app/views/profile/history.html.haml @@ -0,0 +1,5 @@ +.profile_history + = render @events +%hr += paginate @events, theme: "gitlab" + diff --git a/app/views/profile/password.html.haml b/app/views/profile/password.html.haml deleted file mode 100644 index 257dacb1..00000000 --- a/app/views/profile/password.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -%h3.page_title Password -%hr -= form_for @user, url: profile_password_path, method: :put do |f| - .data - %p.slead After successful password update you will be redirected to login page where you should login with new password - -if @user.errors.any? - .alert-message.block-message.error - %ul - - @user.errors.full_messages.each do |msg| - %li= msg - - .clearfix - = f.label :password - .input= f.password_field :password - .clearfix - = f.label :password_confirmation - .input= f.password_field :password_confirmation - .actions - = f.submit 'Save', class: "btn primary" diff --git a/app/views/profile/show.html.haml b/app/views/profile/show.html.haml index 95cce2bb..7b625291 100644 --- a/app/views/profile/show.html.haml +++ b/app/views/profile/show.html.haml @@ -6,7 +6,6 @@ %small = @user.email - %hr = form_for @user, url: profile_update_path, method: :put, html: { class: "edit_user form-horizontal" } do |f| @@ -28,7 +27,23 @@ = f.text_field :email, class: "input-xlarge" %span.help-block We also use email for avatar detection. - %hr + .span5.right + %div.tips + %h6 Tips: + %ul + -unless Gitlab.config.disable_gravatar? + %li + %p.hint You can change your avatar at #{link_to "gravatar.com", "http://gravatar.com"} + + - if Gitlab.config.omniauth_enabled? && @user.provider? + %li + %p.hint + You can login through #{@user.provider.titleize}! + = link_to "click here to change", profile_account_path + + %hr + .row + .span7 .control-group = f.label :skype, class: "control-label" .controls= f.text_field :skype, class: "input-xlarge" @@ -44,26 +59,24 @@ = f.text_area :bio, rows: 6, class: "input-xlarge", maxlength: 250 %span.help-block Tell us about yourself in fewer than 250 characters. .span5.right + .ui-box.white + .ui-box-body + %h4 + Personal projects: + %small.right + %span= current_user.my_own_projects.count + of + %span= current_user.projects_limit + .progress + .bar{style: "width: #{current_user.projects_limit_percent}%;"} - %p.alert.alert-info - %strong Tip: - You can change your avatar at gravatar.com + .ui-box.white + .ui-box-body + %h4 + SSH public keys: + %strong.right= link_to current_user.keys.count, keys_path - %h4 - Personal projects: - %small.right - %span= current_user.my_own_projects.count - of - %span= current_user.projects_limit - .progress - .bar{style: "width: #{current_user.projects_limit_percent}%;"} - - %h4 - SSH public keys: - %small.right - %span= link_to current_user.keys.count, keys_path - - = link_to "Add Public Key", new_key_path, class: "btn small right" + = link_to "Add Public Key", new_key_path, class: "btn small" .form-actions - = f.submit 'Save', class: "btn-primary btn" + = f.submit 'Save', class: "btn save-btn" diff --git a/app/views/profile/token.html.haml b/app/views/profile/token.html.haml deleted file mode 100644 index 6c870c36..00000000 --- a/app/views/profile/token.html.haml +++ /dev/null @@ -1,23 +0,0 @@ -%h3.page_title - Private token - %span.cred.right - keep it in secret! -%hr -= form_for @user, url: profile_reset_private_token_path, method: :put do |f| - .data - %p.slead - Private token used to access application resources without authentication. - %br - It can be used for atom feed or API - %p.cgray - - if current_user.private_token - = text_field_tag "token", current_user.private_token, class: "xxlarge large_text" - - else - You don`t have one yet. Click generate to fix it. - .actions - - if current_user.private_token - = f.submit 'Reset', confirm: "Are you sure?", class: "btn" - - else - = f.submit 'Generate', class: "btn primary" - - diff --git a/app/views/projects/_clone_panel.html.haml b/app/views/projects/_clone_panel.html.haml new file mode 100644 index 00000000..20891610 --- /dev/null +++ b/app/views/projects/_clone_panel.html.haml @@ -0,0 +1,21 @@ +.project_clone_panel + .row + .span7 + .form-horizontal + .input-prepend.project_clone_holder + %button{class: "btn small active", :"data-clone" => @project.ssh_url_to_repo} SSH + %button{class: "btn small", :"data-clone" => @project.http_url_to_repo} HTTP + = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select span5" + .span4.right + .right + - unless @project.empty_repo? + - if can? current_user, :download_code, @project + = link_to archive_project_repository_path(@project), class: "btn small grouped" do + %i.icon-download-alt + Download + - if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project) + = link_to new_project_merge_request_path(@project), title: "New Merge Request", class: "btn small grouped" do + Merge Request + - if @project.issues_enabled && can?(current_user, :write_issue, @project) + = link_to new_project_issue_path(@project), title: "New Issue", class: "btn small grouped" do + Issue diff --git a/app/views/projects/_form.html.haml b/app/views/projects/_form.html.haml index ce66b2cf..8bdeda1c 100644 --- a/app/views/projects/_form.html.haml +++ b/app/views/projects/_form.html.haml @@ -10,9 +10,9 @@ .input = f.text_field :name, placeholder: "Example Project", class: "xxlarge" - %h5.page_title - .alert.alert-info - %h5 Advanced settings: + %hr + .adv_settings + %h6 Advanced settings: .clearfix = f.label :path do Path @@ -34,8 +34,9 @@ .input= f.select(:default_branch, @project.heads.map(&:name), {}, style: "width:210px;") - unless @project.new_record? - .alert.alert-info - %h5 Features: + %hr + .adv_settings + %h6 Features: .clearfix = f.label :issues_enabled, "Issues" @@ -56,7 +57,7 @@ %br .actions - = f.submit 'Save', class: "btn primary" + = f.submit 'Save', class: "btn save-btn" = link_to 'Cancel', @project, class: "btn" - unless @project.new_record? .right diff --git a/app/views/projects/_new_form.html.haml b/app/views/projects/_new_form.html.haml index 5104df83..e6d5e93f 100644 --- a/app/views/projects/_new_form.html.haml +++ b/app/views/projects/_new_form.html.haml @@ -7,11 +7,11 @@ Project name is .input = f.text_field :name, placeholder: "Example Project", class: "xxlarge" - = f.submit 'Create project', class: "btn primary" + = f.submit 'Create project', class: "btn primary project-submit" %hr - .alert.alert-info - %h5 Advanced settings: + %div.adv_settings + %h6 Advanced settings: .clearfix = f.label :path do Git Clone diff --git a/app/views/projects/_project_head.html.haml b/app/views/projects/_project_head.html.haml index ba64ee7f..4f38bef8 100644 --- a/app/views/projects/_project_head.html.haml +++ b/app/views/projects/_project_head.html.haml @@ -3,8 +3,8 @@ = link_to project_path(@project), class: "activities-tab tab" do %i.icon-home Show - %li{ class: " #{'active' if (controller.controller_name == "team_members") || current_page?(team_project_path(@project)) }" } - = link_to team_project_path(@project), class: "team-tab tab" do + %li{ class: " #{'active' if (controller.controller_name == "team_members") || current_page?(project_team_index_path(@project)) }" } + = link_to project_team_index_path(@project), class: "team-tab tab" do %i.icon-user Team %li{ class: "#{'active' if current_page?(files_project_path(@project)) }" } diff --git a/app/views/projects/_refs.html.haml b/app/views/projects/_refs.html.haml deleted file mode 100644 index 804b8523..00000000 --- a/app/views/projects/_refs.html.haml +++ /dev/null @@ -1,8 +0,0 @@ -= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do - = select_tag "ref", grouped_options_refs, onchange: "this.form.submit();", class: "project-refs-select" - = hidden_field_tag :destination, destination - -:javascript - $(function(){ - $('.project-refs-select').chosen(); - }) diff --git a/app/views/projects/_show.html.haml b/app/views/projects/_show.html.haml deleted file mode 100644 index e8a5b00d..00000000 --- a/app/views/projects/_show.html.haml +++ /dev/null @@ -1,23 +0,0 @@ -%h5.title - = @project.name -%br -%div - %a.btn.info{href: tree_project_ref_path(@project, @project.root_ref)} Browse code -   - %a.btn{href: project_commits_path(@project)} Commits - %strong.right - = link_to project_path(@project) do - Switch to project → -%br -.alert-message.block-message.warning - .input - .input-prepend - %span.add-on git clone - = text_field_tag :project_clone, @project.url_to_repo, class: "xlarge one_click_select git_clone_url" - -= simple_format @project.description -- unless @events.blank? - %h4.middle_title Recent Activity - .content_list= render @events - - diff --git a/app/views/projects/_team.html.haml b/app/views/projects/_team.html.haml deleted file mode 100644 index 175aea27..00000000 --- a/app/views/projects/_team.html.haml +++ /dev/null @@ -1,16 +0,0 @@ -%table.admin-table - %thead - %tr - %th User - %th Permissions - %tbody - - @project.users_projects.each do |up| - = render(partial: 'team_members/show', locals: {member: up}) - - -:javascript - $(function(){ - $('.repo-access-select, .project-access-select').live("change", function() { - $(this.form).submit(); - }); - }) diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 987d43ec..fdd537da 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -3,10 +3,10 @@ %h3.page_title Edit Project %hr = render "projects/form" -%div.ajax_loader.hide +%div.save-project-loader.hide %center - %div.padded= image_tag "ajax_loader.gif" - %h3.prepend-top Saving project & repository. Please wait... + = image_tag "ajax_loader.gif" + %h3 Saving project. Please wait a few minutes :javascript $(function(){ new Projects(); }); diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 907d5ef4..d9a151fc 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -1,4 +1,6 @@ = render 'shared/no_ssh' += render 'clone_panel' + %div.git-empty %h4 Git global setup: %pre.dark diff --git a/app/views/projects/files.html.haml b/app/views/projects/files.html.haml index 68d51df0..ce8ba876 100644 --- a/app/views/projects/files.html.haml +++ b/app/views/projects/files.html.haml @@ -1,6 +1,11 @@ = render "project_head" - unless @notes.empty? - %table.zebra-striped.borders + %table + %thead + %tr + %th File name + %th + - @notes.each do |note| %tr %td diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 703e558a..933cb671 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -3,10 +3,10 @@ New Project %hr = render 'new_form' -%div.ajax_loader.hide +%div.save-project-loader.hide %center - %div.padded= image_tag "ajax_loader.gif" - %h3.prepend-top Creating project & repository. Please wait a few minutes + = image_tag "ajax_loader.gif" + %h3 Creating project & repository. Please wait a few minutes :javascript $(function(){ new Projects(); }); diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index ebd2c8e4..21459da2 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -1,37 +1,4 @@ = render "project_head" - -.entry - .row - .span7 - .form-horizontal - .input-prepend.project_clone_holder - - %span.add-on git clone - = link_to "SSH", "#", class: "btn small active", :"data-clone" => @project.ssh_url_to_repo - = link_to "HTTP", "#", class: "btn small", :"data-clone" => @project.http_url_to_repo - = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select span5" - .span4.right - .right - - if can? current_user, :download_code, @project - = link_to archive_project_repository_path(@project), class: "btn small grouped" do - %i.icon-download-alt - Download - - if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project) - = link_to new_project_merge_request_path(@project), title: "New Merge Request", class: "btn small grouped" do - Merge Request - - if @project.issues_enabled && can?(current_user, :write_issue, @project) - = link_to new_project_issue_path(@project), title: "New Issue", class: "btn small grouped" do - Issue - += render 'clone_panel' = render "events/event_last_push", event: @last_push .content_list= render @events - -:javascript - $(function(){ - var link_sel = ".project_clone_holder a"; - $(link_sel).bind("click", function() { - $(link_sel).removeClass("active"); - $(this).addClass("active"); - $("#project_clone").val($(this).attr("data-clone")); - }) - }) diff --git a/app/views/projects/wall.html.haml b/app/views/projects/wall.html.haml index 97765d7a..591a8cd0 100644 --- a/app/views/projects/wall.html.haml +++ b/app/views/projects/wall.html.haml @@ -1,2 +1,2 @@ %div.wall_page - = render "notes/notes", tid: nil, tt: "wall" + = render "notes/reversed_notes_with_form", tid: nil, tt: "wall" diff --git a/app/views/protected_branches/index.html.haml b/app/views/protected_branches/index.html.haml index 2b93b0a8..43884de1 100644 --- a/app/views/protected_branches/index.html.haml +++ b/app/views/protected_branches/index.html.haml @@ -19,12 +19,12 @@ .entry.clearfix = f.label :name, "Branch" .span3 - = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , { include_blank: "-- Select branch" }, { class: "span3" }) + = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: "Select branch"}, {class: "chosen span3"})   = f.submit 'Protect', class: "primary btn" - unless @branches.empty? - %table.admin-table + %table %thead %tr %th Name @@ -46,6 +46,3 @@ %td - if can? current_user, :admin_project, @project = link_to 'Unprotect', [@project, branch], confirm: 'Are you sure?', method: :delete, class: "danger btn small" - -:javascript - $('select#protected_branch_name').chosen(); diff --git a/app/views/refs/_head.html.haml b/app/views/refs/_head.html.haml index 8825493a..3592f573 100644 --- a/app/views/refs/_head.html.haml +++ b/app/views/refs/_head.html.haml @@ -1,10 +1,11 @@ %ul.nav.nav-tabs %li - = form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form", remote: true do - = select_tag "ref", grouped_options_refs, onchange: "$(this.form).trigger('submit');", class: "project-refs-select" - = hidden_field_tag :destination, "tree" - = hidden_field_tag :path, params[:path] + = render partial: 'shared/ref_switcher', locals: {destination: 'tree', path: params[:path]} %li{class: "#{'active' if (controller.controller_name == "refs") }"} = link_to tree_project_ref_path(@project, @ref) do Source - + %li.right + .input-prepend.project_clone_holder + %button{class: "btn small active", :"data-clone" => @project.ssh_url_to_repo} SSH + %button{class: "btn small", :"data-clone" => @project.http_url_to_repo} HTTP + = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select span5" diff --git a/app/views/refs/_tree.html.haml b/app/views/refs/_tree.html.haml index c231c407..55078718 100644 --- a/app/views/refs/_tree.html.haml +++ b/app/views/refs/_tree.html.haml @@ -13,7 +13,7 @@ = render partial: "refs/tree_file", locals: { name: tree.name, content: tree.data, file: tree } - else - contents = tree.contents - %table#tree-slider.bordered-table.table{class: "table_#{@hex_path}" } + %table#tree-slider{class: "table_#{@hex_path}" } %thead %th Name %th Last Update @@ -43,25 +43,23 @@ %i.icon-file = content.name .file_content.wiki - - if content.name =~ /\.(md|markdown)$/i + - if gitlab_markdown?(content.name) = preserve do = markdown(content.data) - else - = simple_format(content.data) + = raw GitHub::Markup.render(content.name, content.data) :javascript $(function(){ - $('.project-refs-select').chosen(); - history.pushState({ path: this.path }, '', "#{@history_path}"); - - }); - - // Load last commit log for each file in tree - $(window).load(function(){ - ajaxGet('#{@logs_path}'); }); +- unless tree.is_blob? + :javascript + // Load last commit log for each file in tree + $(window).load(function(){ + ajaxGet('#{@logs_path}'); + }); - if params[:path] && request.xhr? :javascript diff --git a/app/views/refs/_tree_file.html.haml b/app/views/refs/_tree_file.html.haml index b5ed61bb..76173e24 100644 --- a/app/views/refs/_tree_file.html.haml +++ b/app/views/refs/_tree_file.html.haml @@ -2,17 +2,20 @@ .file_title %i.icon-file %span.file_name - = name + = name.force_encoding('utf-8') %small #{file.mode} %span.options = link_to "raw", blob_project_ref_path(@project, @ref, path: params[:path]), class: "btn very_small", target: "_blank" = link_to "history", project_commits_path(@project, path: params[:path], ref: @ref), class: "btn very_small" = link_to "blame", blame_file_project_ref_path(@project, @ref, path: params[:path]), class: "btn very_small" - if file.text? - - if name =~ /\.(md|markdown)$/i + - if gitlab_markdown?(name) .file_content.wiki = preserve do = markdown(file.data) + - elsif markup?(name) + .file_content.wiki + = raw GitHub::Markup.render(name, file.data) - else .file_content.code - unless file.empty? diff --git a/app/views/refs/_tree_item.html.haml b/app/views/refs/_tree_item.html.haml index 2e6bbf62..d4c4ee8d 100644 --- a/app/views/refs/_tree_item.html.haml +++ b/app/views/refs/_tree_item.html.haml @@ -2,7 +2,7 @@ %tr{ class: "tree-item #{tree_hex_class(content)}", url: tree_file_project_ref_path(@project, @ref, file) } %td.tree-item-file-name = tree_icon(content) - = link_to truncate(content.name, length: 40), tree_file_project_ref_path(@project, @ref || @commit.id, file), remote: :true + %strong= link_to truncate(content.name, length: 40), tree_file_project_ref_path(@project, @ref || @commit.id, file), remote: :true %td.tree_time_ago.cgray - if index == 1 %span.log_loading diff --git a/app/views/refs/blame.html.haml b/app/views/refs/blame.html.haml index 34478d4b..eb66f597 100644 --- a/app/views/refs/blame.html.haml +++ b/app/views/refs/blame.html.haml @@ -38,8 +38,3 @@ = preserve do %pre = Gitlab::Encode.utf8 lines.join("\n") - -:javascript - $(function(){ - $('.project-refs-select').chosen(); - }); diff --git a/app/views/refs/tree.js.haml b/app/views/refs/tree.js.haml index 2eccf8c1..92e90579 100644 --- a/app/views/refs/tree.js.haml +++ b/app/views/refs/tree.js.haml @@ -6,5 +6,5 @@ // Load last commit log for each file in tree $('#tree-slider').waitForImages(function() { - ajaxGet('#{@logs_path}'); + ajaxGet('#{@logs_path}'); }); diff --git a/app/views/repositories/_branch.html.haml b/app/views/repositories/_branch.html.haml index cf8558ec..64a633be 100644 --- a/app/views/repositories/_branch.html.haml +++ b/app/views/repositories/_branch.html.haml @@ -11,7 +11,7 @@ %code= commit.short_id = image_tag gravatar_icon(commit.author_email), class: "", width: 16 - = gfm truncate(commit.title, length: 40) + = gfm escape_once(truncate(commit.title, length: 40)) %span.update-author.right = time_ago_in_words(commit.committed_date) ago diff --git a/app/views/repositories/_feed.html.haml b/app/views/repositories/_feed.html.haml index ac4eb483..0c13551d 100644 --- a/app/views/repositories/_feed.html.haml +++ b/app/views/repositories/_feed.html.haml @@ -13,7 +13,7 @@ = link_to project_commits_path(@project, commit.id) do %code= commit.short_id = image_tag gravatar_icon(commit.author_email), class: "", width: 16 - = gfm truncate(commit.title, length: 40) + = gfm escape_once(truncate(commit.title, length: 40)) %td %span.right.cgray = time_ago_in_words(commit.committed_date) diff --git a/app/views/repositories/branches.html.haml b/app/views/repositories/branches.html.haml index 45004bdf..4c246c69 100644 --- a/app/views/repositories/branches.html.haml +++ b/app/views/repositories/branches.html.haml @@ -1,6 +1,6 @@ = render "repositories/branches_head" - unless @branches.empty? - %table.admin-table + %table %thead %tr %th Name diff --git a/app/views/repositories/show.html.haml b/app/views/repositories/show.html.haml index a09cdd62..fd0abac8 100644 --- a/app/views/repositories/show.html.haml +++ b/app/views/repositories/show.html.haml @@ -1,6 +1,6 @@ = render "branches_head" -%table.admin-table +%table %thead %tr %th Name diff --git a/app/views/repositories/tags.html.haml b/app/views/repositories/tags.html.haml index 7fc2c3bf..a4114586 100644 --- a/app/views/repositories/tags.html.haml +++ b/app/views/repositories/tags.html.haml @@ -1,6 +1,6 @@ = render "commits/head" - unless @tags.empty? - %table.admin-table + %table %thead %tr %th Name @@ -17,7 +17,7 @@ = link_to project_commit_path(@project, commit.id) do %code= commit.short_id = image_tag gravatar_icon(commit.author_email), class: "", width: 16 - = gfm truncate(commit.title, length: 40) + = gfm escape_once(truncate(commit.title, length: 40)) %td %span.update-author.right = time_ago_in_words(commit.committed_date) diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml index 9a0b4789..d85c24ec 100644 --- a/app/views/search/show.html.haml +++ b/app/views/search/show.html.haml @@ -3,8 +3,8 @@ = label_tag :search do %strong Looking for .input - = text_field_tag :search, params[:search], placeholder: "issue 143", class: "input-xxlarge", id: "dashboard_search" - = submit_tag 'Search', class: "btn btn-primary" + = search_field_tag :search, params[:search], placeholder: "issue 143", class: "input-xxlarge search-text-input", id: "dashboard_search" + = submit_tag 'Search', class: "btn primary wide" - if params[:search].present? %br %h3 @@ -14,9 +14,10 @@ .search_results .row .span6 - %table.admin-table - %tr - %th Projects + %table + %thead + %tr + %th Projects %tbody - @projects.each do |project| %tr @@ -31,9 +32,10 @@ %td %h4.nothing_here_message No Projects %br - %table.admin-table - %tr - %th Merge Requests + %table + %thead + %tr + %th Merge Requests %tbody - @merge_requests.each do |merge_request| %tr @@ -49,9 +51,10 @@ %td %h4.nothing_here_message No Merge Requests .span6 - %table.admin-table - %tr - %th Issues + %table + %thead + %tr + %th Issues %tbody - @issues.each do |issue| %tr diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml new file mode 100644 index 00000000..e0c89522 --- /dev/null +++ b/app/views/shared/_ref_switcher.html.haml @@ -0,0 +1,5 @@ += form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do + = select_tag "ref", grouped_options_refs, class: "project-refs-select chosen" + = hidden_field_tag :destination, destination + - if respond_to?(:path) + = hidden_field_tag :path, path diff --git a/app/views/snippets/_form.html.haml b/app/views/snippets/_form.html.haml index b8d8c098..e61e61a7 100644 --- a/app/views/snippets/_form.html.haml +++ b/app/views/snippets/_form.html.haml @@ -16,7 +16,7 @@ .input= f.text_field :file_name, placeholder: "example.rb" .clearfix = f.label "Lifetime" - .input= f.select :expires_at, lifetime_select_options, {}, style: "width:200px;" + .input= f.select :expires_at, lifetime_select_options, {}, {class: 'chosen span2'} .clearfix = f.label :content, "Code" .input= f.text_area :content, class: "span8" @@ -26,11 +26,3 @@ = link_to "Cancel", project_snippets_path(@project), class: " btn" - unless @snippet.new_record? .right= link_to 'Destroy', [@project, @snippet], confirm: 'Are you sure?', method: :delete, class: "btn right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}" - - - -:javascript - $(function(){ - $('select#snippet_expires_at').chosen(); - }); - diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml index 7229b587..515daec6 100644 --- a/app/views/snippets/index.html.haml +++ b/app/views/snippets/index.html.haml @@ -8,7 +8,7 @@ %br To add new snippet - click on button. -%table.admin-table +%table %thead %tr %th Title diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml index 0800b81d..4188a9f1 100644 --- a/app/views/snippets/show.html.haml +++ b/app/views/snippets/show.html.haml @@ -17,4 +17,4 @@ %div{class: current_user.dark_scheme ? "black" : ""} = raw @snippet.colorize(options: { linenos: 'True'}) -= render "notes/notes", tid: @snippet.id, tt: "snippet" += render "notes/notes_with_form", tid: @snippet.id, tt: "snippet" diff --git a/app/views/team_members/_form.html.haml b/app/views/team_members/_form.html.haml index 208794b9..92167138 100644 --- a/app/views/team_members/_form.html.haml +++ b/app/views/team_members/_form.html.haml @@ -1,4 +1,5 @@ -%h3= "New Team member" +%h3.page_title + = "New Team member(s)" %hr = form_for @team_member, as: :team_member, url: project_team_members_path(@project, @team_member) do |f| -if @team_member.errors.any? @@ -7,27 +8,16 @@ - @team_member.errors.full_messages.each do |msg| %li= msg + %h6 1. Choose people you want in the team .clearfix - = f.label :user_id, "Name" - .input= f.select(:user_id, User.not_in_project(@project).all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select user" }, { style: "width:300px" }) - + = f.label :user_ids, "People" + .input= select_tag(:user_ids, options_from_collection_for_select(User.not_in_project(@project).all, :id, :name), {data: {placeholder: "Select users"}, class: "chosen xxlarge", multiple: true}) + %h6 2. Set access level for them .clearfix = f.label :project_access, "Project Access" - .input= f.select :project_access, options_for_select(Project.access_options, @team_member.project_access), {}, class: "project-access-select" - + .input= select_tag :project_access, options_for_select(Project.access_options, @team_member.project_access), class: "project-access-select chosen" .actions - = f.submit 'Save', class: "btn primary" - = link_to "Cancel", team_project_path(@project), class: "btn" - -:css - form select { - width:300px; - } - -:javascript - $('select#team_member_user_id').chosen(); - $('select#team_member_project_access').chosen(); - //$('select#team_member_repo_access').chosen(); - //$('select#team_member_project_access').chosen(); + = f.submit 'Save', class: "btn save-btn" + = link_to "Cancel", project_team_index_path(@project), class: "btn cancel-btn" diff --git a/app/views/team_members/_show.html.haml b/app/views/team_members/_show.html.haml index 2dc4fb65..f68f8eb4 100644 --- a/app/views/team_members/_show.html.haml +++ b/app/views/team_members/_show.html.haml @@ -1,20 +1,26 @@ - user = member.user - allow_admin = can? current_user, :admin_project, @project %tr{id: dom_id(member), class: "team_member_row user_#{user.id}"} - %td - .right - - if @project.owner == user - %span.label Project Owner - - if user.blocked - %span.label Blocked - + %td.span6 = link_to project_team_member_path(@project, member), title: user.name, class: "dark" do = image_tag gravatar_icon(user.email, 40), class: "avatar s32" = link_to project_team_member_path(@project, member), title: user.name, class: "dark" do %strong= truncate(user.name, lenght: 40) - %br - %div.cgray= user.email + %br + %small.cgray= user.email - %td - = form_for(member, as: :team_member, url: project_team_member_path(@project, member)) do |f| - = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select", disabled: !allow_admin + %td.span5 + .right + - if current_user == user + %span.btn.disabled This is you! + - if @project.owner == user + %span.btn.disabled.success Owner + - elsif user.blocked + %span.btn.disabled.blocked Blocked + - elsif allow_admin + = link_to project_team_member_path(project_id: @project, id: member.id), confirm: remove_from_team_message(@project, member), method: :delete, class: "very_small btn danger" do + %i.icon-minus.icon-white + + - if allow_admin + = form_for(member, as: :team_member, url: project_team_member_path(@project, member)) do |f| + = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2" diff --git a/app/views/team_members/_team.html.haml b/app/views/team_members/_team.html.haml new file mode 100644 index 00000000..a0c88b59 --- /dev/null +++ b/app/views/team_members/_team.html.haml @@ -0,0 +1,18 @@ +- grouper_project_members(@project).each do |access, members| + %table + %thead + %tr + %th.span7 + = Project.access_options.key(access).pluralize + %th + %tbody + - members.each do |up| + = render(partial: 'team_members/show', locals: {member: up}) + + +:javascript + $(function(){ + $('.repo-access-select, .project-access-select').live("change", function() { + $(this.form).submit(); + }); + }) diff --git a/app/views/projects/team.html.haml b/app/views/team_members/index.html.haml similarity index 79% rename from app/views/projects/team.html.haml rename to app/views/team_members/index.html.haml index e8a825c7..b3b7b72a 100644 --- a/app/views/projects/team.html.haml +++ b/app/views/team_members/index.html.haml @@ -1,4 +1,4 @@ -= render "project_head" += render "projects/project_head" %h3.page_title Team Members %small (#{@project.users_projects.count}) @@ -10,6 +10,4 @@ Read more about project permissions %strong= link_to "here", help_permissions_path, class: "vlink" - -= render partial: "team", locals: {project: @project} - += render partial: "team_members/team", locals: {project: @project} diff --git a/app/views/team_members/show.html.haml b/app/views/team_members/show.html.haml index 6cb357cd..9d03cd2c 100644 --- a/app/views/team_members/show.html.haml +++ b/app/views/team_members/show.html.haml @@ -3,7 +3,7 @@ .team_member_show - if can? current_user, :admin_project, @project - = link_to 'Remove from team', project_team_member_path(project_id: @project, id: @team_member.id), confirm: 'Are you sure?', method: :delete, class: "right btn btn-danger" + = link_to 'Remove from team', project_team_member_path(project_id: @project, id: @team_member.id), confirm: 'Are you sure?', method: :delete, class: "right btn danger" .profile_avatar_holder = image_tag gravatar_icon(user.email, 60), class: "borders" %h3 @@ -14,12 +14,12 @@ %hr .back_link %br - = link_to team_project_path(@project), class: "" do + = link_to project_team_index_path(@project), class: "" do ← To team list %br .row .span6 - %table.no-borders + %table.lite %tr %td Email %td= mail_to user.email @@ -39,7 +39,7 @@ %td Bio %td= user.bio .span6 - %table.no-borders + %table.lite %tr %td Member since %td= @team_member.created_at.stamp("Aug 21, 2011") diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml new file mode 100644 index 00000000..bded53b2 --- /dev/null +++ b/app/views/votes/_votes_block.html.haml @@ -0,0 +1,6 @@ +.votes.votes-block + .progress + .bar.bar-success{style: "width: #{votable.upvotes_in_percent}%;"} + .bar.bar-danger{style: "width: #{votable.downvotes_in_percent}%;"} + .upvotes= "#{votable.upvotes} up" + .downvotes= "#{votable.downvotes} down" diff --git a/app/views/votes/_votes_inline.html.haml b/app/views/votes/_votes_inline.html.haml new file mode 100644 index 00000000..91bd200d --- /dev/null +++ b/app/views/votes/_votes_inline.html.haml @@ -0,0 +1,6 @@ +.votes.votes-inline + .upvotes= votable.upvotes + .progress + .bar.bar-success{style: "width: #{votable.upvotes_in_percent}%;"} + .bar.bar-danger{style: "width: #{votable.downvotes_in_percent}%;"} + .downvotes= votable.downvotes diff --git a/app/views/wikis/_form.html.haml b/app/views/wikis/_form.html.haml index 6b6411be..b05d0a78 100644 --- a/app/views/wikis/_form.html.haml +++ b/app/views/wikis/_form.html.haml @@ -14,13 +14,14 @@ .middle_box_content .input %span.cgray - Wiki content is parsed with #{link_to "Markdown", "http://en.wikipedia.org/wiki/Markdown"}. - To add link to new page you can just type + Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. + To link to a (new) page you can just type %code [Link Title](page-slug) + \. .bottom_box_content = f.label :content .input= f.text_area :content, class: 'span8' .actions - = f.submit 'Save', class: "primary btn" - = link_to "Cancel", project_wiki_path(@project, :index), class: "btn" + = f.submit 'Save', class: "save-btn btn" + = link_to "Cancel", project_wiki_path(@project, :index), class: "btn cancel-btn" diff --git a/app/views/wikis/history.html.haml b/app/views/wikis/history.html.haml index e31c5dc2..0a81817c 100644 --- a/app/views/wikis/history.html.haml +++ b/app/views/wikis/history.html.haml @@ -2,7 +2,7 @@ %span.cgray History for = @wikis.last.title %br -%table.admin-table +%table %thead %tr %th # diff --git a/app/views/wikis/pages.html.haml b/app/views/wikis/pages.html.haml index 2bfd0deb..7421d8f9 100644 --- a/app/views/wikis/pages.html.haml +++ b/app/views/wikis/pages.html.haml @@ -1,6 +1,6 @@ %h3.page_title All Pages %br -%table.admin-table +%table %thead %tr %th Title diff --git a/app/views/wikis/show.html.haml b/app/views/wikis/show.html.haml index fc235227..579ea1b3 100644 --- a/app/views/wikis/show.html.haml +++ b/app/views/wikis/show.html.haml @@ -21,4 +21,4 @@ Delete this page %hr -.wiki_notes#notes= render "notes/notes", tid: @wiki.id, tt: "wiki" +.wiki_notes#notes= render "notes/notes_with_form", tid: @wiki.id, tt: "wiki" diff --git a/config/application.rb b/config/application.rb index ecd88b15..ad41f196 100644 --- a/config/application.rb +++ b/config/application.rb @@ -23,7 +23,7 @@ module Gitlab # config.plugins = [ :exception_notification, :ssl_requirement, :all ] # Activate observers that should always be running. - config.active_record.observers = :mailer_observer, :activity_observer, :project_observer, :key_observer, :issue_observer, :user_observer, :system_hook_observer + config.active_record.observers = :mailer_observer, :activity_observer, :project_observer, :key_observer, :issue_observer, :user_observer, :system_hook_observer, :users_project_observer # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. diff --git a/config/cucumber.yml b/config/cucumber.yml deleted file mode 100644 index 19b288df..00000000 --- a/config/cucumber.yml +++ /dev/null @@ -1,8 +0,0 @@ -<% -rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : "" -rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}" -std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} --strict --tags ~@wip" -%> -default: <%= std_opts %> features -wip: --tags @wip:3 --wip features -rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip diff --git a/config/environment.rb b/config/environment.rb index c880a7ad..3b186a9d 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -3,5 +3,3 @@ require File.expand_path('../application', __FILE__) # Initialize the rails application Gitlab::Application.initialize! - -require File.join(Rails.root, "lib", "gitlab", "git_host") diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index be36ee6d..28323484 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -25,18 +25,55 @@ app: # backup_keep_time: 604800 # default: 0 (forever) (in seconds) # disable_gravatar: true # default: false - Disable user avatars from Gravatar.com + # -# 2. Advanced settings: +# 2. Auth settings +# ========================== +ldap: + enabled: false + host: '_your_ldap_server' + base: '_the_base_where_you_search_for_users' + port: 636 + uid: 'sAMAccountName' + method: 'ssl' # plain + bind_dn: '_the_full_dn_of_the_user_you_will_bind_with' + password: '_the_password_of_the_bind_user' + +omniauth: + # Enable ability for users + # to login via twitter, google .. + enabled: false + + # IMPORTANT! + # It allows user to login without having user account + allow_single_sign_on: false + block_auto_created_users: true + + # Auth providers + providers: + # - { name: 'google_oauth2', app_id: 'YOUR APP ID', + # app_secret: 'YOUR APP SECRET', + # args: { access_type: 'offline', approval_prompt: '' } } + # - { name: 'twitter', app_id: 'YOUR APP ID', + # app_secret: 'YOUR APP SECRET'} + # - { name: 'github', app_id: 'YOUR APP ID', + # app_secret: 'YOUR APP SECRET' } + + +# +# 3. Advanced settings: # ========================== # Git Hosting configuration git_host: admin_uri: git@localhost:gitolite-admin base_path: /home/git/repositories/ - # host: localhost + hooks_path: /home/git/.gitolite/hooks/ + gitolite_admin_key: gitlab git_user: git upload_pack: true receive_pack: true + # host: localhost # port: 22 # Git settings diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 8165d6c2..7a7ca43f 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -6,7 +6,7 @@ class Settings < Settingslogic self.web['protocol'] ||= web.https ? "https" : "http" end - def web_host + def web_host self.web['host'] ||= 'localhost' end @@ -14,11 +14,11 @@ class Settings < Settingslogic self.email['from'] ||= ("notify@" + web_host) end - def url + def url self['url'] ||= build_url - end + end - def web_port + def web_port if web.https web['port'] = 443 else @@ -36,7 +36,7 @@ class Settings < Settingslogic raw_url << web_host if web_custom_port? - raw_url << ":#{web_port}" + raw_url << ":#{web_port}" end raw_url @@ -66,6 +66,10 @@ class Settings < Settingslogic git_host['base_path'] || '/home/git/repositories/' end + def git_hooks_path + git_host['hooks_path'] || '/home/git/share/gitolite/hooks/' + end + def git_upload_pack if git_host['upload_pack'] != false true @@ -98,6 +102,10 @@ class Settings < Settingslogic git_host['admin_uri'] || 'git@localhost:gitolite-admin' end + def gitolite_admin_key + git_host['gitolite_admin_key'] || 'gitlab' + end + def default_projects_limit app['default_projects_limit'] || 10 end @@ -112,6 +120,22 @@ class Settings < Settingslogic app['backup_keep_time'] || 0 end + def ldap_enabled? + ldap && ldap['enabled'] + rescue Settingslogic::MissingSetting + false + end + + def omniauth_enabled? + omniauth && omniauth['enabled'] + rescue Settingslogic::MissingSetting + false + end + + def omniauth_providers + (omniauth_enabled? && omniauth['providers']) || [] + end + def disable_gravatar? app['disable_gravatar'] || false end diff --git a/config/initializers/5_backend.rb b/config/initializers/5_backend.rb new file mode 100644 index 00000000..85f747ac --- /dev/null +++ b/config/initializers/5_backend.rb @@ -0,0 +1,5 @@ +# GIT over HTTP +require Rails.root.join("lib", "gitlab", "backend", "grack_auth") + +# GITOLITE backend +require Rails.root.join("lib", "gitlab", "backend", "gitolite") diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 54011ba5..8f3cef5a 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -204,4 +204,21 @@ Devise.setup do |config| # manager.intercept_401 = false # manager.default_strategies(:scope => :user).unshift :some_external_strategy # end + + gl = Gitlab.config + + if gl.ldap_enabled? + config.omniauth :ldap, + :host => gl.ldap['host'], + :base => gl.ldap['base'], + :uid => gl.ldap['uid'], + :port => gl.ldap['port'], + :method => gl.ldap['method'], + :bind_dn => gl.ldap['bind_dn'], + :password => gl.ldap['password'] + end + + gl.omniauth_providers.each do |gl_provider| + config.omniauth gl_provider['name'].to_sym, gl_provider['app_id'], gl_provider['app_secret'] + end end diff --git a/config/initializers/kaminari_config.rb b/config/initializers/kaminari_config.rb new file mode 100644 index 00000000..3cbe9a05 --- /dev/null +++ b/config/initializers/kaminari_config.rb @@ -0,0 +1,10 @@ +Kaminari.configure do |config| + config.default_per_page = 20 + config.max_per_page = 100 + # config.window = 4 + # config.outer_window = 0 + # config.left = 0 + # config.right = 0 + # config.page_method_name = :page + # config.param_name = :page +end diff --git a/config/initializers/omniauth.rb.sample b/config/initializers/omniauth.rb.sample deleted file mode 100644 index 6e844efd..00000000 --- a/config/initializers/omniauth.rb.sample +++ /dev/null @@ -1,15 +0,0 @@ -# Copy this file to 'omniauth.rb' and configure it as necessary. -# The wiki has further details on configuring each provider. - -Devise.setup do |config| - # config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo' - - # config.omniauth :ldap, - # :host => 'YOUR_LDAP_SERVER', - # :base => 'THE_BASE_WHERE_YOU_SEARCH_FOR_USERS', - # :uid => 'sAMAccountName', - # :port => 389, - # :method => :plain, - # :bind_dn => 'THE_FULL_DN_OF_THE_USER_YOU_WILL_BIND_WITH', - # :password => 'THE_PASSWORD_OF_THE_BIND_USER' -end diff --git a/config/initializers/resque.rb b/config/initializers/resque.rb new file mode 100644 index 00000000..b333ceee --- /dev/null +++ b/config/initializers/resque.rb @@ -0,0 +1,8 @@ +rails_root = ENV['RAILS_ROOT'] || File.dirname(__FILE__) + '/../..' +rails_env = ENV['RAILS_ENV'] || 'development' +config_file = File.join(rails_root, 'config', 'resque.yml') + +if File.exists?(config_file) + resque_config = YAML.load_file(config_file) + Resque.redis = resque_config[rails_env] +end diff --git a/config/resque.yml.example b/config/resque.yml.example new file mode 100644 index 00000000..cd3d4874 --- /dev/null +++ b/config/resque.yml.example @@ -0,0 +1,3 @@ +development: localhost:6379 +test: localhost:6379 +production: redis.example.com:6379 diff --git a/config/routes.rb b/config/routes.rb index 97594d57..cfb9bdb9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -10,7 +10,7 @@ Gitlab::Application.routes.draw do # Optionally, enable Resque here require 'resque/server' - mount Resque::Server.new, at: '/info/resque' + mount Resque::Server => '/info/resque', as: 'resque' # Enable Grack support mount Grack::Bundle.new({ @@ -23,14 +23,14 @@ Gitlab::Application.routes.draw do # # Help # - get 'help' => 'help#index' - get 'help/permissions' => 'help#permissions' - get 'help/workflow' => 'help#workflow' - get 'help/api' => 'help#api' - get 'help/web_hooks' => 'help#web_hooks' + get 'help' => 'help#index' + get 'help/permissions' => 'help#permissions' + get 'help/workflow' => 'help#workflow' + get 'help/api' => 'help#api' + get 'help/web_hooks' => 'help#web_hooks' get 'help/system_hooks' => 'help#system_hooks' - get 'help/markdown' => 'help#markdown' - get 'help/ssh' => 'help#ssh' + get 'help/markdown' => 'help#markdown' + get 'help/ssh' => 'help#ssh' # # Admin Area @@ -43,23 +43,19 @@ Gitlab::Application.routes.draw do put :unblock end end - resources :projects, :constraints => { :id => /[^\/]+/ } do + resources :projects, constraints: { id: /[^\/]+/ } do member do get :team put :team_update end end - resources :team_members, :only => [:edit, :update, :destroy] - get 'mailer/preview_note' - get 'mailer/preview_user_new' - get 'mailer/preview_issue_new' - - resources :hooks, :only => [:index, :create, :destroy] do + resources :team_members, only: [:edit, :update, :destroy] + resources :hooks, only: [:index, :create, :destroy] do get :test end - resource :logs - resource :resque, :controller => 'resque' - root :to => "dashboard#index" + resource :logs, only: [:show] + resource :resque, controller: 'resque', only: [:show] + root to: "dashboard#index" end get "errors/githost" @@ -67,38 +63,39 @@ Gitlab::Application.routes.draw do # # Profile Area # - get "profile/password", :to => "profile#password" - put "profile/password", :to => "profile#password_update" - get "profile/token", :to => "profile#token" - put "profile/reset_private_token", :to => "profile#reset_private_token" - get "profile", :to => "profile#show" - get "profile/design", :to => "profile#design" - put "profile/update", :to => "profile#update" + get "profile/account" => "profile#account" + get "profile/history" => "profile#history" + put "profile/password" => "profile#password_update" + get "profile/token" => "profile#token" + put "profile/reset_private_token" => "profile#reset_private_token" + get "profile" => "profile#show" + get "profile/design" => "profile#design" + put "profile/update" => "profile#update" + resources :keys # # Dashboard Area # - get "dashboard", :to => "dashboard#index" - get "dashboard/issues", :to => "dashboard#issues" - get "dashboard/merge_requests", :to => "dashboard#merge_requests" + get "dashboard" => "dashboard#index" + get "dashboard/issues" => "dashboard#issues" + get "dashboard/merge_requests" => "dashboard#merge_requests" - resources :projects, :constraints => { :id => /[^\/]+/ }, :only => [:new, :create] + resources :projects, constraints: { id: /[^\/]+/ }, only: [:new, :create] - devise_for :users, :controllers => { :omniauth_callbacks => :omniauth_callbacks } + devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks } # # Project Area # - resources :projects, :constraints => { :id => /[^\/]+/ }, :except => [:new, :create, :index], :path => "/" do + resources :projects, constraints: { id: /[^\/]+/ }, except: [:new, :create, :index], path: "/" do member do - get "team" get "wall" get "graph" get "files" end - resources :wikis, :only => [:show, :edit, :destroy, :create] do + resources :wikis, only: [:show, :edit, :destroy, :create] do collection do get :pages end @@ -117,46 +114,45 @@ Gitlab::Application.routes.draw do end resources :deploy_keys - resources :protected_branches, :only => [:index, :create, :destroy] + resources :protected_branches, only: [:index, :create, :destroy] - resources :refs, :only => [], :path => "/" do + resources :refs, only: [], path: "/" do collection do get "switch" end member do - get "tree", :constraints => { :id => /[a-zA-Z.\/0-9_\-]+/ } - get "logs_tree", :constraints => { :id => /[a-zA-Z.\/0-9_\-]+/ } + get "tree", constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } + get "logs_tree", constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } get "blob", - :constraints => { - :id => /[a-zA-Z.0-9\/_\-]+/, - :path => /.*/ + constraints: { + id: /[a-zA-Z.0-9\/_\-]+/, + path: /.*/ } - # tree viewer get "tree/:path" => "refs#tree", - :as => :tree_file, - :constraints => { - :id => /[a-zA-Z.0-9\/_\-]+/, - :path => /.*/ + as: :tree_file, + constraints: { + id: /[a-zA-Z.0-9\/_\-]+/, + path: /.*/ } # tree viewer get "logs_tree/:path" => "refs#logs_tree", - :as => :logs_file, - :constraints => { - :id => /[a-zA-Z.0-9\/_\-]+/, - :path => /.*/ + as: :logs_file, + constraints: { + id: /[a-zA-Z.0-9\/_\-]+/, + path: /.*/ } # blame get "blame/:path" => "refs#blame", - :as => :blame_file, - :constraints => { - :id => /[a-zA-Z.0-9\/_\-]+/, - :path => /.*/ + as: :blame_file, + constraints: { + id: /[a-zA-Z.0-9\/_\-]+/, + path: /.*/ } end end @@ -181,7 +177,7 @@ Gitlab::Application.routes.draw do end end - resources :hooks, :only => [:index, :create, :destroy] do + resources :hooks, only: [:index, :create, :destroy] do member do get :test end @@ -195,20 +191,24 @@ Gitlab::Application.routes.draw do get :patch end end + resources :team, controller: 'team_members', only: [:index] resources :team_members resources :milestones + resources :labels, only: [:index] resources :issues do + collection do post :sort post :bulk_update get :search end end - resources :notes, :only => [:index, :create, :destroy] do + resources :notes, only: [:index, :create, :destroy] do collection do post :preview end end end - root :to => "dashboard#index" + + root to: "dashboard#index" end diff --git a/config/unicorn.rb.orig b/config/unicorn.rb.example similarity index 100% rename from config/unicorn.rb.orig rename to config/unicorn.rb.example diff --git a/db/fixtures/test/001_repo.rb b/db/fixtures/test/001_repo.rb index ebf005a1..67d4e7bf 100644 --- a/db/fixtures/test/001_repo.rb +++ b/db/fixtures/test/001_repo.rb @@ -1,15 +1,23 @@ -# create tmp dir if not exist -tmp_dir = File.join(Rails.root, "tmp") -Dir.mkdir(tmp_dir) unless File.exists?(tmp_dir) +require 'fileutils' -# Create dir for test repo -repo_dir = File.join(Rails.root, "tmp", "tests") -Dir.mkdir(repo_dir) unless File.exists?(repo_dir) +print "Unpacking seed repository..." -`cp spec/seed_project.tar.gz tmp/tests/` -Dir.chdir(repo_dir) -`tar -xf seed_project.tar.gz` -3.times do |i| -`cp -r gitlabhq/ gitlabhq_#{i}/` -puts "Unpacked seed repo - tmp/tests/gitlabhq_#{i}" +SEED_REPO = 'seed_project.tar.gz' +REPO_PATH = File.join(Rails.root, 'tmp', 'repositories') + +# Make whatever directories we need to make +FileUtils.mkdir_p(REPO_PATH) + +# Copy the archive to the repo path +FileUtils.cp(File.join(Rails.root, 'spec', SEED_REPO), REPO_PATH) + +# chdir to the repo path +FileUtils.cd(REPO_PATH) do + # Extract the archive + `tar -xf #{SEED_REPO}` + + # Remove the copy + FileUtils.rm(SEED_REPO) end + +puts ' done.' diff --git a/db/migrate/20120905043334_set_default_branch_default_to_nil.rb b/db/migrate/20120905043334_set_default_branch_default_to_nil.rb new file mode 100644 index 00000000..f5956fe8 --- /dev/null +++ b/db/migrate/20120905043334_set_default_branch_default_to_nil.rb @@ -0,0 +1,12 @@ +class SetDefaultBranchDefaultToNil < ActiveRecord::Migration + def up + # Set the default_branch to allow nil, and default it to nil + change_column_null(:projects, :default_branch, true) + change_column_default(:projects, :default_branch, nil) + end + + def down + change_column_null(:projects, :default_branch, false) + change_column_default(:projects, :default_branch, 'master') + end +end diff --git a/db/pkey.example b/db/pkey.example deleted file mode 100644 index ae045772..00000000 --- a/db/pkey.example +++ /dev/null @@ -1,3 +0,0 @@ -AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4 -596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4 -soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0= diff --git a/db/schema.rb b/db/schema.rb index 46461e44..00bb5523 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20120729131232) do +ActiveRecord::Schema.define(:version => 20120905043334) do create_table "events", :force => true do |t| t.string "target_type" @@ -98,16 +98,16 @@ ActiveRecord::Schema.define(:version => 20120729131232) do t.string "name" t.string "path" t.text "description" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.boolean "private_flag", :default => true, :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.boolean "private_flag", :default => true, :null => false t.string "code" t.integer "owner_id" - t.string "default_branch", :default => "master", :null => false - t.boolean "issues_enabled", :default => true, :null => false - t.boolean "wall_enabled", :default => true, :null => false - t.boolean "merge_requests_enabled", :default => true, :null => false - t.boolean "wiki_enabled", :default => true, :null => false + t.string "default_branch" + t.boolean "issues_enabled", :default => true, :null => false + t.boolean "wall_enabled", :default => true, :null => false + t.boolean "merge_requests_enabled", :default => true, :null => false + t.boolean "wiki_enabled", :default => true, :null => false end create_table "protected_branches", :force => true do |t| diff --git a/doc/api/README.md b/doc/api/README.md index e0111966..36a36f8f 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -1,10 +1,8 @@ -# Gitlab API +# GitLab API -All API requests require authentication. You need to pass `private_token` parameter to authenticate. +All API requests require authentication. You need to pass a `private_token` parameter to authenticate. You can find or reset your private token in your profile. -To get or reset your token visit your profile. - -If no or invalid `private_token` provided error message will be returned with status code 401: +If no, or an invalid, `private_token` is provided then an error message will be returned with status code 401: ```json { @@ -12,10 +10,9 @@ If no or invalid `private_token` provided error message will be returned with st } ``` -API requests should be prefixed with `api` and the API version. -API version is equal to Gitlab major version number and defined in `lib/api.rb`. +API requests should be prefixed with `api` and the API version. The API version is equal to the GitLab major version number, which is defined in `lib/api.rb`. -Example of valid API request: +Example of a valid API request: ``` GET http://example.com/api/v2/projects?private_token=QVy1PB7sTxfy4pqfZM1U @@ -23,8 +20,19 @@ GET http://example.com/api/v2/projects?private_token=QVy1PB7sTxfy4pqfZM1U The API uses JSON to serialize data. You don't need to specify `.json` at the end of API URL. +#### Pagination + +When listing resources you can pass the following parameters: + ++ `page` (default: `1`) - page number ++ `per_page` (default: `20`, max: `100`) - how many items to list per page + ## Contents + [Users](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/users.md) ++ [Session](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/session.md) + [Projects](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/projects.md) ++ [Snippets](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/snippets.md) ++ [Repositories](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/repositories.md) + [Issues](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/issues.md) ++ [Milestones](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/milestones.md) diff --git a/doc/api/milestones.md b/doc/api/milestones.md new file mode 100644 index 00000000..f68d8eb7 --- /dev/null +++ b/doc/api/milestones.md @@ -0,0 +1,57 @@ +## List project milestones + +Get a list of project milestones. + +``` +GET /projects/:id/milestones +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project + +## Single milestone + +Get a single project milestone. + +``` +GET /projects/:id/milestones/:milestone_id +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `milestone_id` (required) - The ID of a project milestone + +## New milestone + +Create a new project milestone. + +``` +POST /projects/:id/milestones +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `milestone_id` (required) - The ID of a project milestone ++ `title` (required) - The title of an milestone ++ `description` (optional) - The description of the milestone ++ `due_date` (optional) - The due date of the milestone + +## Edit milestone + +Update an existing project milestone. + +``` +PUT /projects/:id/milestones/:milestone_id +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `milestone_id` (required) - The ID of a project milestone ++ `title` (optional) - The title of a milestone ++ `description` (optional) - The description of a milestone ++ `due_date` (optional) - The due date of the milestone ++ `closed` (optional) - The status of the milestone diff --git a/doc/api/projects.md b/doc/api/projects.md index ead31003..d06a41c2 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -1,6 +1,6 @@ ## List projects -Get a list of authenticated user's projects. +Get a list of projects owned by the authenticated user. ``` GET /projects @@ -55,7 +55,7 @@ GET /projects ## Single project -Get an authenticated user's project. +Get a specific project, identified by project ID, which is owned by the authentication user. ``` GET /projects/:id @@ -89,235 +89,153 @@ Parameters: } ``` -## Project repository branches +## Create project -Get a list of project repository branches sorted by name alphabetically. +Create new project owned by user ``` -GET /projects/:id/repository/branches +POST /projects +``` + +Parameters: + ++ `name` (required) - new project name ++ `code` (optional) - new project code, uses project name if not set ++ `path` (optional) - new project path, uses project name if not set ++ `description` (optional) - short project description ++ `default_branch` (optional) - 'master' by default ++ `issues_enabled` (optional) - enabled by default ++ `wall_enabled` (optional) - enabled by default ++ `merge_requests_enabled` (optional) - enabled by default ++ `wiki_enabled` (optional) - enabled by default + +Will return created project with status `201 Created` on success, or `404 Not +found` on fail. + +## List project team members + +Get a list of project team members. + +``` +GET /projects/:id/members ``` Parameters: + `id` (required) - The ID or code name of a project -```json -[ - { - "name": "master", - "commit": { - "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c", - "parents": [ - { - "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8" - } - ], - "tree": "46e82de44b1061621357f24c05515327f2795a95", - "message": "add projects API", - "author": { - "name": "John Smith", - "email": "john@example.com" - }, - "committer": { - "name": "John Smith", - "email": "john@example.com" - }, - "authored_date": "2012-06-27T05:51:39-07:00", - "committed_date": "2012-06-28T03:44:20-07:00" - } - } -] -``` +## Get project team member -Get a single project repository branch. +Get a project team member. ``` -GET /projects/:id/repository/branches/:branch +GET /projects/:id/members/:user_id ``` Parameters: + `id` (required) - The ID or code name of a project -+ `branch` (required) - The name of the branch ++ `user_id` (required) - The ID of a user ```json { - "name": "master", - "commit": { - "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c", - "parents": [ - { - "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8" - } - ], - "tree": "46e82de44b1061621357f24c05515327f2795a95", - "message": "add projects API", - "author": { - "name": "John Smith", - "email": "john@example.com" - }, - "committer": { - "name": "John Smith", - "email": "john@example.com" - }, - "authored_date": "2012-06-27T05:51:39-07:00", - "committed_date": "2012-06-28T03:44:20-07:00" - } -} -``` -## Project repository tags - -Get a list of project repository tags sorted by name in reverse alphabetical order. - -``` -GET /projects/:id/repository/tags -``` - -Parameters: - -+ `id` (required) - The ID or code name of a project - -```json -[ - { - "name": "v1.0.0", - "commit": { - "id": "2695effb5807a22ff3d138d593fd856244e155e7", - "parents": [ - - ], - "tree": "38017f2f189336fe4497e9d230c5bb1bf873f08d", - "message": "Initial commit", - "author": { - "name": "John Smith", - "email": "john@example.com" - }, - "committer": { - "name": "Jack Smith", - "email": "jack@example.com" - }, - "authored_date": "2012-05-28T04:42:42-07:00", - "committed_date": "2012-05-28T04:42:42-07:00" - } - } -] -``` - -# Project Snippets - -## List snippets - -Not implemented. - -## Single snippet - -Get a project snippet. - -``` -GET /projects/:id/snippets/:snippet_id -``` - -Parameters: - -+ `id` (required) - The ID or code name of a project -+ `snippet_id` (required) - The ID of a project's snippet - -```json -{ "id": 1, - "title": "test", - "file_name": "add.rb", - "author": { - "id": 1, - "email": "john@example.com", - "name": "John Smith", - "blocked": false, - "created_at": "2012-05-23T08:00:58Z" - }, - "expires_at": null, - "updated_at": "2012-06-28T10:52:04Z", - "created_at": "2012-06-28T10:52:04Z" + "email": "john@example.com", + "name": "John Smith", + "blocked": false, + "created_at": "2012-05-23T08:00:58Z", + "access_level": 40 } ``` -## Snippet content +## Add project team member -Get a raw project snippet. +Add a user to a project team. ``` -GET /projects/:id/snippets/:snippet_id/raw +POST /projects/:id/members ``` Parameters: + `id` (required) - The ID or code name of a project -+ `snippet_id` (required) - The ID of a project's snippet ++ `user_id` (required) - The ID of a user to add ++ `access_level` (required) - Project access level -## New snippet +Will return status `201 Created` on success, or `404 Not found` on fail. -Create a new project snippet. +## Edit project team member + +Update project team member to specified access level. ``` -POST /projects/:id/snippets +PUT /projects/:id/members/:user_id ``` Parameters: + `id` (required) - The ID or code name of a project -+ `title` (required) - The title of a snippet -+ `file_name` (required) - The name of a snippet file -+ `lifetime` (optional) - The expiration date of a snippet -+ `code` (required) - The content of a snippet ++ `user_id` (required) - The ID of a team member ++ `access_level` (required) - Project access level -Will return created snippet with status `201 Created` on success, or `404 Not found` on fail. +Will return status `200 OK` on success, or `404 Not found` on fail. -## Edit snippet +## Remove project team member -Update an existing project snippet. +Removes user from project team. ``` -PUT /projects/:id/snippets/:snippet_id +DELETE /projects/:id/members/:user_id ``` Parameters: + `id` (required) - The ID or code name of a project -+ `snippet_id` (required) - The ID of a project's snippet -+ `title` (optional) - The title of a snippet -+ `file_name` (optional) - The name of a snippet file -+ `lifetime` (optional) - The expiration date of a snippet -+ `code` (optional) - The content of a snippet - -Will return updated snippet with status `200 OK` on success, or `404 Not found` on fail. - -## Delete snippet - -Delete existing project snippet. - -``` -DELETE /projects/:id/snippets/:snippet_id -``` - -Parameters: - -+ `id` (required) - The ID or code name of a project -+ `snippet_id` (required) - The ID of a project's snippet ++ `user_id` (required) - The ID of a team member Status code `200` will be returned on success. -## Raw blob content +## Get project hooks -Get the raw file contents for a file. +Get hooks for project ``` -GET /projects/:id/repository/commits/:sha/blob +GET /projects/:id/hooks ``` Parameters: + `id` (required) - The ID or code name of a project -+ `sha` (required) - The commit or branch name -+ `filepath` (required) - The path the file -Will return the raw file contents. +Will return hooks with status `200 OK` on success, or `404 Not found` on fail. + +## Add project hook + +Add hook to project + +``` +POST /projects/:id/hooks +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `url` (required) - The hook URL + +Will return status `201 Created` on success, or `404 Not found` on fail. + +## Delete project hook + +Delete hook from project + +``` +DELETE /projects/:id/hooks +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `hook_id` (required) - The ID of hook to delete + +Will return status `200 OK` on success, or `404 Not found` on fail. diff --git a/doc/api/repositories.md b/doc/api/repositories.md new file mode 100644 index 00000000..487ad9b2 --- /dev/null +++ b/doc/api/repositories.md @@ -0,0 +1,166 @@ +## Project repository branches + +Get a list of repository branches from a project, sorted by name alphabetically. + +``` +GET /projects/:id/repository/branches +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project + +```json +[ + { + "name": "master", + "commit": { + "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c", + "parents": [ + { + "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8" + } + ], + "tree": "46e82de44b1061621357f24c05515327f2795a95", + "message": "add projects API", + "author": { + "name": "John Smith", + "email": "john@example.com" + }, + "committer": { + "name": "John Smith", + "email": "john@example.com" + }, + "authored_date": "2012-06-27T05:51:39-07:00", + "committed_date": "2012-06-28T03:44:20-07:00" + } + } +] +``` + +## Project repository branch + +Get a single project repository branch. + +``` +GET /projects/:id/repository/branches/:branch +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `branch` (required) - The name of the branch + +```json +{ + "name": "master", + "commit": { + "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c", + "parents": [ + { + "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8" + } + ], + "tree": "46e82de44b1061621357f24c05515327f2795a95", + "message": "add projects API", + "author": { + "name": "John Smith", + "email": "john@example.com" + }, + "committer": { + "name": "John Smith", + "email": "john@example.com" + }, + "authored_date": "2012-06-27T05:51:39-07:00", + "committed_date": "2012-06-28T03:44:20-07:00" + } +} +``` + +## Project repository tags + +Get a list of repository tags from a project, sorted by name in reverse alphabetical order. + +``` +GET /projects/:id/repository/tags +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project + +```json +[ + { + "name": "v1.0.0", + "commit": { + "id": "2695effb5807a22ff3d138d593fd856244e155e7", + "parents": [ + + ], + "tree": "38017f2f189336fe4497e9d230c5bb1bf873f08d", + "message": "Initial commit", + "author": { + "name": "John Smith", + "email": "john@example.com" + }, + "committer": { + "name": "Jack Smith", + "email": "jack@example.com" + }, + "authored_date": "2012-05-28T04:42:42-07:00", + "committed_date": "2012-05-28T04:42:42-07:00" + } + } +] +``` + +## Project repository commits + +Get a list of repository commits in a project. + +``` +GET /projects/:id/repository/commits +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `ref_name` (optional) - The name of a repository branch or tag + +```json +[ + { + "id": "ed899a2f4b50b4370feeea94676502b42383c746", + "short_id": "ed899a2f4b5", + "title": "Replace sanitize with escape once", + "author_name": "Dmitriy Zaporozhets", + "author_email": "dzaporozhets@sphereconsultinginc.com", + "created_at": "2012-09-20T11:50:22+03:00" + }, + { + "id": "6104942438c14ec7bd21c6cd5bd995272b3faff6", + "short_id": "6104942438c", + "title": "Sanitize for network graph", + "author_name": "randx", + "author_email": "dmitriy.zaporozhets@gmail.com", + "created_at": "2012-09-20T09:06:12+03:00" + } +] +``` + +## Raw blob content + +Get the raw file contents for a file. + +``` +GET /projects/:id/repository/commits/:sha/blob +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `sha` (required) - The commit or branch name ++ `filepath` (required) - The path the file + +Will return the raw file contents. diff --git a/doc/api/session.md b/doc/api/session.md new file mode 100644 index 00000000..9fdbeb43 --- /dev/null +++ b/doc/api/session.md @@ -0,0 +1,22 @@ +Login to get private token + +``` +POST /session +``` + +Parameters: + ++ `email` (required) - The email of user ++ `password` (required) - Valid password + + +```json +{ + "id": 1, + "email": "john@example.com", + "name": "John Smith", + "private_token": "dd34asd13as", + "created_at": "2012-05-23T08:00:58Z", + "blocked": true +} +``` diff --git a/doc/api/snippets.md b/doc/api/snippets.md new file mode 100644 index 00000000..0cd29ce5 --- /dev/null +++ b/doc/api/snippets.md @@ -0,0 +1,100 @@ +## List snippets + +Not implemented. + +## Single snippet + +Get a project snippet. + +``` +GET /projects/:id/snippets/:snippet_id +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `snippet_id` (required) - The ID of a project's snippet + +```json +{ + "id": 1, + "title": "test", + "file_name": "add.rb", + "author": { + "id": 1, + "email": "john@example.com", + "name": "John Smith", + "blocked": false, + "created_at": "2012-05-23T08:00:58Z" + }, + "expires_at": null, + "updated_at": "2012-06-28T10:52:04Z", + "created_at": "2012-06-28T10:52:04Z" +} +``` + +## Snippet content + +Get a raw project snippet. + +``` +GET /projects/:id/snippets/:snippet_id/raw +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `snippet_id` (required) - The ID of a project's snippet + +## New snippet + +Create a new project snippet. + +``` +POST /projects/:id/snippets +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `title` (required) - The title of a snippet ++ `file_name` (required) - The name of a snippet file ++ `lifetime` (optional) - The expiration date of a snippet ++ `code` (required) - The content of a snippet + +Will return created snippet with status `201 Created` on success, or `404 Not found` on fail. + +## Edit snippet + +Update an existing project snippet. + +``` +PUT /projects/:id/snippets/:snippet_id +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `snippet_id` (required) - The ID of a project's snippet ++ `title` (optional) - The title of a snippet ++ `file_name` (optional) - The name of a snippet file ++ `lifetime` (optional) - The expiration date of a snippet ++ `code` (optional) - The content of a snippet + +Will return updated snippet with status `200 OK` on success, or `404 Not found` on fail. + +## Delete snippet + +Delete existing project snippet. + +``` +DELETE /projects/:id/snippets/:snippet_id +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `snippet_id` (required) - The ID of a project's snippet + +Status code `200` will be returned on success. + diff --git a/doc/api/users.md b/doc/api/users.md index b9b04dc5..4f806b14 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -88,3 +88,81 @@ GET /user "theme_id": 1 } ``` + +## List SSH keys + +Get a list of currently authenticated user's SSH keys. + +``` +GET /user/keys +``` + +```json +[ + { + "id": 1, + "title" : "Public key" + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4 + 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4 + soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", + }, + { + "id": 3, + "title" : "Another Public key" + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4 + 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4 + soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" + } +] +``` + +## Single SSH key + +Get a single key. + +``` +GET /user/keys/:id +``` + +Parameters: + ++ `id` (required) - The ID of an SSH key + +```json +{ + "id": 1, + "title" : "Public key" + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4 + 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4 + soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" +} +``` +## Add SSH key + +Create new key owned by currently authenticated user + +``` +POST /user/keys +``` + +Parameters: + ++ `title` (required) - new SSH Key's title ++ `key` (required) - new SSH key + +Will return created key with status `201 Created` on success, or `404 Not +found` on fail. + +## Delete SSH key + +Delete key owned by currently authenticated user + +``` +DELETE /user/keys/:id +``` + +Parameters: + ++ `id` (required) - SSH key ID + +Will return `200 OK` on success, or `404 Not Found` on fail. diff --git a/doc/debian_ubuntu.sh b/doc/debian_ubuntu.sh deleted file mode 100644 index a0b4710b..00000000 --- a/doc/debian_ubuntu.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/sh - -sudo apt-get update -sudo apt-get upgrade - -sudo apt-get install -y git git-core wget curl gcc checkinstall libxml2-dev libxslt-dev sqlite3 libsqlite3-dev libcurl4-openssl-dev libreadline-gplv2-dev libc6-dev libssl-dev libmysql++-dev make build-essential zlib1g-dev libicu-dev redis-server openssh-server python-dev python-pip libyaml-dev postfix - -wget http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.3-p194.tar.gz -tar xfvz ruby-1.9.3-p194.tar.gz -cd ruby-1.9.3-p194 -./configure -make -sudo make install - -sudo adduser \ - --system \ - --shell /bin/sh \ - --gecos 'git version control' \ - --group \ - --disabled-password \ - --home /home/git \ - git - -sudo adduser --disabled-login --gecos 'gitlab system' gitlab - -sudo usermod -a -G git gitlab - -sudo -H -u gitlab ssh-keygen -q -N '' -t rsa -f /home/gitlab/.ssh/id_rsa - -cd /home/git -sudo -H -u git git clone git://github.com/gitlabhq/gitolite /home/git/gitolite - -sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; /home/git/gitolite/src/gl-system-install" -sudo cp /home/gitlab/.ssh/id_rsa.pub /home/git/gitlab.pub -sudo chmod 777 /home/git/gitlab.pub - -sudo -u git -H sed -i 's/0077/0007/g' /home/git/share/gitolite/conf/example.gitolite.rc -sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; gl-setup -q /home/git/gitlab.pub" - -sudo chmod -R g+rwX /home/git/repositories/ -sudo chown -R git:git /home/git/repositories/ - -sudo -u gitlab -H git clone git@localhost:gitolite-admin.git /tmp/gitolite-admin -sudo rm -rf /tmp/gitolite-admin diff --git a/doc/development.md b/doc/development.md new file mode 100644 index 00000000..67bcb8e1 --- /dev/null +++ b/doc/development.md @@ -0,0 +1,45 @@ +## Development tips: + +### Start application in development mode + +#### 1. Via foreman + + bundle exec foreman -p 3000 + +#### 2. Via gitlab cli + + ./gitlab start + +#### 3. Manually + + bundle exec rails s + bundle exec rake environment resque:work QUEUE=* VVERBOSE=1 + + +### Run tests: + +#### 1. Packages + + # ubuntu + sudo apt-get install libqt4-dev libqtwebkit-dev + sudo apt-get install xvfb + + # Mac + brew install qt + brew install xvfb + +#### 2. DB & seeds + + bundle exec rake db:setup RAILS_ENV=test + bundle exec rake db:seed_fu RAILS_ENV=test + +### 3. Run Tests + + # All in one + bundle exec rake gitlab:test + + # Rspec + bundle exec rake spec + + # Spinach + bundle exec rake spinach diff --git a/doc/installation.md b/doc/installation.md index 2da63ae8..865cde3c 100644 --- a/doc/installation.md +++ b/doc/installation.md @@ -20,7 +20,7 @@ You might have some luck using these, but no guarantees: - MacOS X - FreeBSD -Gitlab does **not** run on Windows and we have no plans of making Gitlab compatible. +GitLab does **not** run on Windows and we have no plans of making GitLab compatible. ## This installation guide created for Debian/Ubuntu and properly tested. @@ -28,21 +28,21 @@ The installation consists of 6 steps: 1. Install packages / dependencies 2. Install ruby -3. Install gitolite -4. Install and configure Gitlab. +3. Install Gitolite +4. Install and configure GitLab. 5. Start the web front-end 6. Start a Resque worker (for background processing) ### IMPORTANT -Please make sure you have followed all the steps below before posting to the mailinglist with installation and configuration questions. +Please make sure you have followed all the steps below before posting to the mailing list with installation and configuration questions. -Only create a Github Issue if you want a specific part of this installation guide updated. +Only create a GitHub Issue if you want a specific part of this installation guide updated. Also read the [Read this before you submit an issue](https://github.com/gitlabhq/gitlabhq/wiki/Read-this-before-you-submit-an-issue) wiki page. > - - - -> First 3 steps can be easily skipped with simply install script: +> The first 3 steps of this guide can be easily skipped by executing an install script: > > # Install curl and sudo > apt-get install curl sudo @@ -61,7 +61,11 @@ Also read the [Read this before you submit an issue](https://github.com/gitlabhq # 1. Install packages -*Keep in mind that `sudo` is not installed for debian by default. You should install it with as root:* **apt-get update && apt-get upgrade && apt-get install sudo** +*Keep in mind that `sudo` is not installed on Debian by default. You should install it as root:* + + apt-get update && apt-get upgrade && apt-get install sudo + +Now install the required packages: sudo apt-get update sudo apt-get upgrade @@ -71,7 +75,7 @@ Also read the [Read this before you submit an issue](https://github.com/gitlabhq # If you want to use MySQL: sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev -# 2. Install ruby +# 2. Install Ruby wget http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.3-p194.tar.gz tar xfvz ruby-1.9.3-p194.tar.gz @@ -80,7 +84,7 @@ Also read the [Read this before you submit an issue](https://github.com/gitlabhq make sudo make install -# 3. Install gitolite +# 3. Install Gitolite Create user for git: @@ -93,12 +97,12 @@ Create user for git: --home /home/git \ git -Create user for gitlab: +Create user for GitLab: # ubuntu/debian sudo adduser --disabled-login --gecos 'gitlab system' gitlab -Add your user to git group: +Add your user to the `git` group: sudo usermod -a -G git gitlab @@ -106,20 +110,23 @@ Generate key: sudo -H -u gitlab ssh-keygen -q -N '' -t rsa -f /home/gitlab/.ssh/id_rsa -Get gitolite source code: +Clone GitLab's fork of the Gitolite source code: cd /home/git - sudo -H -u git git clone git://github.com/gitlabhq/gitolite /home/git/gitolite + sudo -H -u git git clone -b gl-v304 https://github.com/gitlabhq/gitolite.git /home/git/gitolite Setup: + cd /home/git + sudo -u git -H mkdir bin sudo -u git sh -c 'echo -e "PATH=\$PATH:/home/git/bin\nexport PATH" >> /home/git/.profile' - sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; /home/git/gitolite/src/gl-system-install" + sudo -u git sh -c 'gitolite/install -ln /home/git/bin' + sudo cp /home/gitlab/.ssh/id_rsa.pub /home/git/gitlab.pub sudo chmod 0444 /home/git/gitlab.pub - sudo -u git -H sed -i 's/0077/0007/g' /home/git/share/gitolite/conf/example.gitolite.rc - sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; gl-setup -q /home/git/gitlab.pub" + sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; gitolite setup -pk /home/git/gitlab.pub" + sudo -u git -H sed -i 's/0077/0007/g' /home/git/.gitolite.rc Permissions: @@ -135,23 +142,30 @@ Permissions: # if succeed you can remove it sudo rm -rf /tmp/gitolite-admin -**IMPORTANT! If you cant clone `gitolite-admin` repository - DONT PROCEED INSTALLATION** +**IMPORTANT! If you can't clone `gitolite-admin` repository - DO NOT PROCEED WITH INSTALLATION** +Check the [Trouble Shooting Guide](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide) +and ensure you have followed all of the above steps carefully. -# 4. Install gitlab and configuration. Check status configuration. +# 4. Clone GitLab source and install prerequisites sudo gem install charlock_holmes --version '0.6.8' sudo pip install pygments sudo gem install bundler cd /home/gitlab - sudo -H -u gitlab git clone -b stable git://github.com/gitlabhq/gitlabhq.git gitlab - cd gitlab - sudo -u gitlab mkdir tmp + # Get gitlab code. Use this for stable setup + sudo -H -u gitlab git clone -b stable https://github.com/gitlabhq/gitlabhq.git gitlab + + # Skip this for stable setup. + # Master branch (recent changes, less stable) + sudo -H -u gitlab git clone -b master https://github.com/gitlabhq/gitlabhq.git gitlab + + cd gitlab # Rename config files sudo -u gitlab cp config/gitlab.yml.example config/gitlab.yml -#### Select db you want to use +#### Select the database you want to use # SQLite sudo -u gitlab cp config/database.yml.sqlite config/database.yml @@ -163,7 +177,7 @@ Permissions: # Login to MySQL $ mysql -u root -p - # Create the gitlabhq production database + # Create the GitLab production database mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`; # Create the MySQL User change $password to a real password @@ -179,14 +193,16 @@ Permissions: sudo -u gitlab -H bundle install --without development test --deployment -#### Setup DB +#### Setup database sudo -u gitlab bundle exec rake gitlab:app:setup RAILS_ENV=production -#### Setup gitlab hooks +#### Setup GitLab hooks - sudo cp ./lib/hooks/post-receive /home/git/share/gitolite/hooks/common/post-receive - sudo chown git:git /home/git/share/gitolite/hooks/common/post-receive + sudo cp ./lib/hooks/post-receive /home/git/.gitolite/hooks/common/post-receive + sudo chown git:git /home/git/.gitolite/hooks/common/post-receive + +#### Check application status Checking status: @@ -208,9 +224,9 @@ Checking status: UMASK for .gitolite.rc is 0007? ............YES /home/git/share/gitolite/hooks/common/post-receive exists? ............YES -If you got all YES - congrats! You can go to next step. +If you got all YES - congratulations! You can go to the next step. -# 5. Server up +# 5. Start the web server Application can be started with next command: @@ -225,33 +241,41 @@ You can login via web using admin generated with setup: admin@local.host 5iveL!fe -# 6. Run resque process (for processing queue). +# 6. Run Resque process (for processing job queue). # Manually sudo -u gitlab bundle exec rake environment resque:work QUEUE=* RAILS_ENV=production BACKGROUND=yes - # Gitlab start script + # GitLab start script sudo -u gitlab ./resque.sh # if you run this as root /home/gitlab/gitlab/tmp/pids/resque_worker.pid will be owned by root # causing the resque worker not to start via init script on next boot/service restart +## Customizing Resque's Redis connection + +If you'd like Resque to connect to a Redis server on a non-standard port or on +a different host, you can configure its connection string in the +**config/resque.yml** file: + + production: redis.example.com:6379 + **Ok - we have a working application now. ** -**But keep going - there are some thing that should be done ** +**But keep going - there are some things that should be done ** # Nginx && Unicorn -### Install Nginx - - sudo apt-get install nginx - -## Unicorn +## 1. Unicorn cd /home/gitlab/gitlab - sudo -u gitlab cp config/unicorn.rb.orig config/unicorn.rb + sudo -u gitlab cp config/unicorn.rb.example config/unicorn.rb sudo -u gitlab bundle exec unicorn_rails -c config/unicorn.rb -E production -D -Add GitLab to nginx sites & change with your host specific settings +## 2. Nginx + # Install first + sudo apt-get install nginx + + # Add GitLab to nginx sites & change with your host specific settings sudo wget https://raw.github.com/gitlabhq/gitlab-recipes/master/nginx/gitlab -P /etc/nginx/sites-available/ sudo ln -s /etc/nginx/sites-available/gitlab /etc/nginx/sites-enabled/gitlab @@ -260,22 +284,20 @@ Add GitLab to nginx sites & change with your host specific settings # of the host serving GitLab. sudo vim /etc/nginx/sites-enabled/gitlab -Restart nginx: - + # Restart nginx: /etc/init.d/nginx restart +## 3. Init script + Create init script in /etc/init.d/gitlab: sudo wget https://raw.github.com/gitlabhq/gitlab-recipes/master/init.d/gitlab -P /etc/init.d/ - -Adding permission: - sudo chmod +x /etc/init.d/gitlab -Gitlab autostart: +GitLab autostart: sudo update-rc.d gitlab defaults -Now you can start/restart/stop gitlab like: +Now you can start/restart/stop GitLab like: sudo /etc/init.d/gitlab restart diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature index a8c2205c..9756bc7f 100644 --- a/features/dashboard/dashboard.feature +++ b/features/dashboard/dashboard.feature @@ -1,9 +1,9 @@ Feature: Dashboard - Background: - Given I signin as a user + Background: + Given I sign in as a user And I own project "Shop" And project "Shop" has push event - And I visit dashboard page + And I visit dashboard page Scenario: I should see projects list Then I should see "New Project" link @@ -15,4 +15,13 @@ Feature: Dashboard And I click "Create Merge Request" link Then I see prefilled new Merge Request page + Scenario: I should see User joined Project event + Given user with name "John Doe" joined project "Shop" + When I visit dashboard page + Then I should see "John Doe joined project Shop" event + Scenario: I should see User left Project event + Given user with name "John Doe" joined project "Shop" + And user with name "John Doe" left project "Shop" + When I visit dashboard page + Then I should see "John Doe left project Shop" event diff --git a/features/dashboard/issues.feature b/features/dashboard/issues.feature index c3361bb3..895b89aa 100644 --- a/features/dashboard/issues.feature +++ b/features/dashboard/issues.feature @@ -1,8 +1,8 @@ Feature: Dashboard Issues - Background: - Given I signin as a user + Background: + Given I sign in as a user And I have assigned issues - And I visit dashboard issues page + And I visit dashboard issues page Scenario: I should see issues list Then I should see issues assigned to me diff --git a/features/dashboard/merge_requests.feature b/features/dashboard/merge_requests.feature index 90b8749c..cad65b0d 100644 --- a/features/dashboard/merge_requests.feature +++ b/features/dashboard/merge_requests.feature @@ -1,8 +1,8 @@ -Feature: Dashboard MR - Background: - Given I signin as a user +Feature: Dashboard Merge Requests + Background: + Given I sign in as a user And I have authored merge requests - And I visit dashboard merge requests page + And I visit dashboard merge requests page Scenario: I should see projects list Then I should see my merge requests diff --git a/features/dashboard/search.feature b/features/dashboard/search.feature index f053fe86..91d870f4 100644 --- a/features/dashboard/search.feature +++ b/features/dashboard/search.feature @@ -1,11 +1,9 @@ Feature: Dashboard Search - Background: - Given I signin as a user + Background: + Given I sign in as a user And I own project "Shop" - And I visit dashboard search page + And I visit dashboard search page - Scenario: I should see project i'm looking for + Scenario: I should see project I am looking for Given I search for "Sho" Then I should see "Shop" project link - - diff --git a/features/profile/profile.feature b/features/profile/profile.feature index afda4b55..134cabb5 100644 --- a/features/profile/profile.feature +++ b/features/profile/profile.feature @@ -1,6 +1,6 @@ Feature: Profile - Background: - Given I signin as a user + Background: + Given I sign in as a user Scenario: I look at my profile Given I visit profile page @@ -12,11 +12,11 @@ Feature: Profile And I should see new contact info Scenario: I change my password - Given I visit profile password page + Given I visit profile account page Then I change my password And I should be redirected to sign in page Scenario: I reset my token - Given I visit profile token page + Given I visit profile account page Then I reset my token And I should see new token diff --git a/features/profile/ssh_keys.feature b/features/profile/ssh_keys.feature index c3a92f30..018d124e 100644 --- a/features/profile/ssh_keys.feature +++ b/features/profile/ssh_keys.feature @@ -1,13 +1,10 @@ -Feature: SSH Keys - Background: - Given I signin as a user - And I have ssh keys: - | title | - | Work | - | Home | +Feature: Profile SSH Keys + Background: + Given I sign in as a user + And I have ssh key "ssh-rsa Work" And I visit profile keys page - Scenario: I should see SSH keys + Scenario: I should see ssh keys Then I should see my ssh keys Scenario: Add new ssh key diff --git a/features/projects/commits/branches.feature b/features/project/commits/branches.feature similarity index 66% rename from features/projects/commits/branches.feature rename to features/project/commits/branches.feature index 74575c51..4fa4dc26 100644 --- a/features/projects/commits/branches.feature +++ b/features/project/commits/branches.feature @@ -1,6 +1,6 @@ -Feature: Browse branches - Background: - Given I signin as a user +Feature: Project Browse branches + Background: + Given I sign in as a user And I own project "Shop" And project "Shop" has protected branches Given I visit project branches page @@ -16,8 +16,11 @@ Feature: Browse branches Given I click link "Protected" Then I should see "Shop" protected branches list - Scenario: I can download project by branch + # @wip + # Scenario: I can download project by branch - Scenario: I can view protected branches + # @wip + # Scenario: I can view protected branches - Scenario: I can manage protected branches + # @wip + # Scenario: I can manage protected branches diff --git a/features/projects/commits/commit_comments.feature b/features/project/commits/commit_comments.feature similarity index 72% rename from features/projects/commits/commit_comments.feature rename to features/project/commits/commit_comments.feature index 9bd56d29..5acf541a 100644 --- a/features/projects/commits/commit_comments.feature +++ b/features/project/commits/commit_comments.feature @@ -1,6 +1,6 @@ -Feature: Comment commit - Background: - Given I signin as a user +Feature: Project Comment commit + Background: + Given I sign in as a user And I own project "Shop" Given I visit project commit page diff --git a/features/projects/commits/commits.feature b/features/project/commits/commits.feature similarity index 82% rename from features/projects/commits/commits.feature rename to features/project/commits/commits.feature index 69d39d78..53de6e6a 100644 --- a/features/projects/commits/commits.feature +++ b/features/project/commits/commits.feature @@ -1,6 +1,6 @@ -Feature: Browse commits - Background: - Given I signin as a user +Feature: Project Browse commits + Background: + Given I sign in as a user And I own project "Shop" Given I visit project commits page @@ -18,5 +18,4 @@ Feature: Browse commits Scenario: I compare refs Given I visit compare refs page And I fill compare fields with refs - And I see compared refs - + And I see compared refs diff --git a/features/projects/commits/tags.feature b/features/project/commits/tags.feature similarity index 53% rename from features/projects/commits/tags.feature rename to features/project/commits/tags.feature index f7899fc3..1ac0f8bf 100644 --- a/features/projects/commits/tags.feature +++ b/features/project/commits/tags.feature @@ -1,10 +1,11 @@ -Feature: Browse tags - Background: - Given I signin as a user +Feature: Project Browse tags + Background: + Given I sign in as a user And I own project "Shop" Given I visit project tags page Scenario: I can see all git tags Then I should see "Shop" all tags list - Scenario: I can download project by tag + # @wip + # Scenario: I can download project by tag diff --git a/features/projects/create_project.feature b/features/project/create_project.feature similarity index 91% rename from features/projects/create_project.feature rename to features/project/create_project.feature index 42d25b3f..b7cdfdb8 100644 --- a/features/projects/create_project.feature +++ b/features/project/create_project.feature @@ -4,7 +4,7 @@ Feature: Create Project Should be able to create a new one Scenario: User create a project - Given I signin as a user + Given I sign in as a user When I visit new project page And fill project form with valid data Then I should see project page diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature new file mode 100644 index 00000000..596e8bd7 --- /dev/null +++ b/features/project/issues/issues.feature @@ -0,0 +1,81 @@ +Feature: Project Issues + Background: + Given I sign in as a user + And I own project "Shop" + And project "Shop" have "Release 0.4" open issue + And project "Shop" have "Release 0.3" closed issue + And I visit project "Shop" issues page + + Scenario: I should see open issues + Given I should see "Release 0.4" in issues + And I should not see "Release 0.3" in issues + + Scenario: I should see closed issues + Given I click link "Closed" + Then I should see "Release 0.3" in issues + And I should not see "Release 0.4" in issues + + Scenario: I should see all issues + Given I click link "All" + Then I should see "Release 0.3" in issues + And I should see "Release 0.4" in issues + + Scenario: I visit issue page + Given I click link "Release 0.4" + Then I should see issue "Release 0.4" + + @javascript + Scenario: I submit new unassigned issue + Given I click link "New Issue" + And I submit new issue "500 error on profile" + Given I click link "500 error on profile" + Then I should see issue "500 error on profile" + + @javascript + Scenario: I comment issue + Given I visit issue page "Release 0.4" + And I leave a comment like "XML attached" + Then I should see comment "XML attached" + + @javascript + Scenario: I search issue + Given I fill in issue search with "Release" + Then I should see "Release 0.4" in issues + And I should not see "Release 0.3" in issues + + @javascript + Scenario: I search issue that not exist + Given I fill in issue search with "Bug" + Then I should not see "Release 0.4" in issues + And I should not see "Release 0.3" in issues + + + @javascript + Scenario: I search all issues + Given I click link "All" + And I fill in issue search with "0.3" + Then I should see "Release 0.3" in issues + And I should not see "Release 0.4" in issues + + @javascript + Scenario: I clear search + Given I click link "All" + And I fill in issue search with "Something" + And I fill in issue search with "" + Then I should see "Release 0.4" in issues + And I should see "Release 0.3" in issues + + @javascript + Scenario: I create Issue with pre-selected milestone + Given project "Shop" has milestone "v2.2" + And project "Shop" has milestone "v3.0" + And I visit project "Shop" issues page + When I select milestone "v3.0" + And I click link "New Issue" + Then I should see selected milestone with title "v3.0" + + @javascript + Scenario: I create Issue with pre-selected assignee + When I select first assignee from "Shop" project + And I click link "New Issue" + Then I should see first assignee from "Shop" as selected assignee diff --git a/features/project/issues/labels.feature b/features/project/issues/labels.feature new file mode 100644 index 00000000..e601a41b --- /dev/null +++ b/features/project/issues/labels.feature @@ -0,0 +1,10 @@ +Feature: Project Labels + Background: + Given I sign in as a user + And I own project "Shop" + And project "Shop" have issues tags: "bug", "feature" + Given I visit project "Shop" labels page + + Scenario: I should see active milestones + Then I should see label "bug" + And I should see label "feature" diff --git a/features/projects/issues/milestones.feature b/features/project/issues/milestones.feature similarity index 79% rename from features/projects/issues/milestones.feature rename to features/project/issues/milestones.feature index d78096a4..a57f67d6 100644 --- a/features/projects/issues/milestones.feature +++ b/features/project/issues/milestones.feature @@ -1,9 +1,9 @@ -Feature: Milestones +Feature: Project Milestones Background: - Given I signin as a user + Given I sign in as a user And I own project "Shop" And project "Shop" has milestone "v2.2" - Given I visit project "Shop" milestones page + Given I visit project "Shop" milestones page Scenario: I should see active milestones Then I should see milestone "v2.2" diff --git a/features/projects/merge_requests.feature b/features/project/merge_requests.feature similarity index 92% rename from features/projects/merge_requests.feature rename to features/project/merge_requests.feature index 54b6ccde..80f00986 100644 --- a/features/projects/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -1,10 +1,10 @@ -Feature: Merge Requests +Feature: Project Merge Requests Background: - Given I signin as a user + Given I sign in as a user And I own project "Shop" And project "Shop" have "Bug NS-04" open merge request And project "Shop" have "Feature NS-03" closed merge request - And I visit project "Shop" merge requests page + And I visit project "Shop" merge requests page Scenario: I should see open merge requests Then I should see "Bug NS-04" in merge requests diff --git a/features/projects/network.feature b/features/project/network.feature similarity index 81% rename from features/projects/network.feature rename to features/project/network.feature index 61c05eb3..31ce5ad3 100644 --- a/features/projects/network.feature +++ b/features/project/network.feature @@ -1,10 +1,9 @@ -@javascript Feature: Project Network Graph - Background: - Given I signin as a user + Given I sign in as a user And I own project "Shop" And I visit project "Shop" network page + @javascript Scenario: I should see project network Then page should have network graph diff --git a/features/project/project.feature b/features/project/project.feature new file mode 100644 index 00000000..1c9f201d --- /dev/null +++ b/features/project/project.feature @@ -0,0 +1,14 @@ +Feature: Projects + Background: + Given I signin as a user + And I own project "Shop" + And I visit project "Shop" page + + # @wip + # Scenario: I should see project activity + + # @wip + # Scenario: I edit project + + # @wip + # Scenario: I visit attachments diff --git a/features/projects/source/browse_files.feature b/features/project/source/browse_files.feature similarity index 71% rename from features/projects/source/browse_files.feature rename to features/project/source/browse_files.feature index 04aebc19..b12b0ee3 100644 --- a/features/projects/source/browse_files.feature +++ b/features/project/source/browse_files.feature @@ -1,6 +1,6 @@ -Feature: Browse git repo - Background: - Given I signin as a user +Feature: Project Browse files + Background: + Given I sign in as a user And I own project "Shop" Given I visit project source page @@ -12,12 +12,10 @@ Feature: Browse git repo Then I should see files from repository for "8470d70" Scenario: I browse file content - Given I click on file from repo + Given I click on "Gemfile" file in repo Then I should see it content Scenario: I browse raw file - Given I visit blob file from repo - And I click on raw button + Given I visit blob file from repo + And I click link "raw" Then I should see raw file content - - diff --git a/features/project/source/git_blame.feature b/features/project/source/git_blame.feature new file mode 100644 index 00000000..93ed20a8 --- /dev/null +++ b/features/project/source/git_blame.feature @@ -0,0 +1,10 @@ +Feature: Project Browse git repo + Background: + Given I sign in as a user + And I own project "Shop" + Given I visit project source page + + Scenario: I blame file + Given I click on "Gemfile" file in repo + And I click blame button + Then I should see git file blame diff --git a/features/projects/team_management.feature b/features/project/team_management.feature similarity index 78% rename from features/projects/team_management.feature rename to features/project/team_management.feature index b5b485e2..ae0c459f 100644 --- a/features/projects/team_management.feature +++ b/features/project/team_management.feature @@ -1,11 +1,11 @@ Feature: Project Team management - Background: - Given I signin as a user + Background: + Given I sign in as a user And I own project "Shop" - And gitlab user "Mike" - And gitlab user "Sam" + And gitlab user "Mike" + And gitlab user "Sam" And "Sam" is "Shop" developer - And I visit project "Shop" team page + And I visit project "Shop" team page Scenario: See all team members Then I should be able to see myself in team @@ -20,7 +20,7 @@ Feature: Project Team management Scenario: Update user access Given I should see "Sam" in team list as "Developer" And I change "Sam" role to "Reporter" - Then I visit project "Shop" team page + Then I visit project "Shop" team page And I should see "Sam" in team list as "Reporter" Scenario: View team member profile @@ -30,6 +30,5 @@ Feature: Project Team management Scenario: Cancel team member Given I click link "Sam" And I click link "Remove from team" - Then I visit project "Shop" team page + Then I visit project "Shop" team page And I should not see "Sam" in team list - diff --git a/features/projects/wall.feature b/features/project/wall.feature similarity index 63% rename from features/projects/wall.feature rename to features/project/wall.feature index ed675e2c..c38d046a 100644 --- a/features/projects/wall.feature +++ b/features/project/wall.feature @@ -1,17 +1,16 @@ -@javascript Feature: Project Wall In order to use Project Wall - A user - Should be able to read & write messages + A user should be able to read and write messages Background: - Given I signin as a user + Given I sign in as a user And I own project "Shop" - And I visit project "Shop" wall page + And I visit project "Shop" wall page + @javascript Scenario: Write comment Given I write new comment "my special test message" Then I should see project wall note "my special test message" - Then I visit project "Shop" wall page + Then I visit project "Shop" wall page And I should see project wall note "my special test message" diff --git a/features/projects/wiki.feature b/features/project/wiki.feature similarity index 83% rename from features/projects/wiki.feature rename to features/project/wiki.feature index 4441ada2..51370565 100644 --- a/features/projects/wiki.feature +++ b/features/project/wiki.feature @@ -1,6 +1,6 @@ -Feature: Wiki - Background: - Given I signin as a user +Feature: Project Wiki + Background: + Given I sign in as a user And I own project "Shop" Given I visit project wiki page diff --git a/features/projects/issues/issues.feature b/features/projects/issues/issues.feature deleted file mode 100644 index 180710cf..00000000 --- a/features/projects/issues/issues.feature +++ /dev/null @@ -1,38 +0,0 @@ -Feature: Issues - Background: - Given I signin as a user - And I own project "Shop" - And project "Shop" have "Release 0.4" open issue - And project "Shop" have "Release 0.3" closed issue - And I visit project "Shop" issues page - - Scenario: I should see open issues - Given I should see "Release 0.4" in issues - And I should not see "Release 0.3" in issues - - Scenario: I should see closed issues - Given I click link "Closed" - Then I should see "Release 0.3" in issues - And I should not see "Release 0.4" in issues - - Scenario: I should see all issues - Given I click link "All" - Then I should see "Release 0.3" in issues - And I should see "Release 0.4" in issues - - Scenario: I visit issue page - Given I click link "Release 0.4" - Then I should see issue "Release 0.4" - - @javascript - Scenario: I submit new unassigned issue - Given I click link "New Issue" - And I submit new issue "500 error on profile" - Given I click link "500 error on profile" - Then I should see issue "500 error on profile" - - @javascript - Scenario: I comment issue - Given I visit issue page "Release 0.4" - And I leave a comment like "XML attached" - Then I should see comment "XML attached" diff --git a/features/projects/project.feature b/features/projects/project.feature deleted file mode 100644 index 895a928f..00000000 --- a/features/projects/project.feature +++ /dev/null @@ -1,11 +0,0 @@ -Feature: Project - Background: - Given I signin as a user - And I own project "Shop" - And I visit project "Shop" page - - Scenario: I should see project activity - - Scenario: I edit project - - Scenario: I visit attachments diff --git a/features/projects/snippets.feature b/features/projects/snippets.feature deleted file mode 100644 index e69de29b..00000000 diff --git a/features/projects/source/git_blame.feature b/features/projects/source/git_blame.feature deleted file mode 100644 index 6aa6be47..00000000 --- a/features/projects/source/git_blame.feature +++ /dev/null @@ -1,10 +0,0 @@ -Feature: Browse git repo - Background: - Given I signin as a user - And I own project "Shop" - Given I visit project source page - - Scenario: I blame file - Given I click on file from repo - And I click blame button - Then I should see git file blame diff --git a/features/projects/web_hooks.feature b/features/projects/web_hooks.feature deleted file mode 100644 index e69de29b..00000000 diff --git a/features/step_definitions/dashboard_steps.rb b/features/step_definitions/dashboard_steps.rb deleted file mode 100644 index 1eec7619..00000000 --- a/features/step_definitions/dashboard_steps.rb +++ /dev/null @@ -1,127 +0,0 @@ -Given /^I visit dashboard page$/ do - visit dashboard_path -end - -Then /^I should see "(.*?)" link$/ do |arg1| - page.should have_link(arg1) -end - -Then /^I should see "(.*?)" project link$/ do |arg1| - page.should have_link(arg1) -end - -Then /^I should see project "(.*?)" activity feed$/ do |arg1| - project = Project.find_by_name(arg1) - page.should have_content "#{@user.name} pushed new branch new_design at #{project.name}" -end - -Given /^project "(.*?)" has push event$/ do |arg1| - @project = Project.find_by_name(arg1) - - data = { - :before => "0000000000000000000000000000000000000000", - :after => "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e", - :ref => "refs/heads/new_design", - :user_id => @user.id, - :user_name => @user.name, - :repository => { - :name => @project.name, - :url => "localhost/rubinius", - :description => "", - :homepage => "localhost/rubinius", - :private => true - } - } - - @event = Event.create( - :project => @project, - :action => Event::Pushed, - :data => data, - :author_id => @user.id - ) -end - -Then /^I should see last push widget$/ do - page.should have_content "Your pushed to branch new_design" - page.should have_link "Create Merge Request" -end - -Then /^I click "(.*?)" link$/ do |arg1| - click_link arg1 #Create Merge Request" -end - -Then /^I see prefilled new Merge Request page$/ do - current_path.should == new_project_merge_request_path(@project) - find("#merge_request_source_branch").value.should == "new_design" - find("#merge_request_target_branch").value.should == "master" - find("#merge_request_title").value.should == "New Design" -end - -Given /^I visit dashboard search page$/ do - visit search_path -end - -Given /^I search for "(.*?)"$/ do |arg1| - fill_in "dashboard_search", :with => arg1 - click_button "Search" -end - -Given /^I visit dashboard issues page$/ do - visit dashboard_issues_path -end - -Then /^I should see issues assigned to me$/ do - issues = @user.issues - issues.each do |issue| - page.should have_content(issue.title[0..10]) - page.should have_content(issue.project.name) - end -end - -Given /^I visit dashboard merge requests page$/ do - visit dashboard_merge_requests_path -end - -Then /^I should see my merge requests$/ do - merge_requests = @user.merge_requests - merge_requests.each do |mr| - page.should have_content(mr.title[0..10]) - page.should have_content(mr.project.name) - end -end - -Given /^I have assigned issues$/ do - project = Factory :project - project.add_access(@user, :read, :write) - - issue1 = Factory :issue, - :author => @user, - :assignee => @user, - :project => project - - issue2 = Factory :issue, - :author => @user, - :assignee => @user, - :project => project -end - -Given /^I have authored merge requests$/ do - project1 = Factory :project, - :path => "project1", - :code => "gitlabhq_1" - - project2 = Factory :project, - :path => "project2", - :code => "gitlabhq_2" - - project1.add_access(@user, :read, :write) - project2.add_access(@user, :read, :write) - - merge_request1 = Factory :merge_request, - :author => @user, - :project => project1 - - merge_request2 = Factory :merge_request, - :author => @user, - :project => project2 -end diff --git a/features/step_definitions/profile/profile_keys_steps.rb b/features/step_definitions/profile/profile_keys_steps.rb deleted file mode 100644 index 5ab7e048..00000000 --- a/features/step_definitions/profile/profile_keys_steps.rb +++ /dev/null @@ -1,34 +0,0 @@ -Given /^I visit profile keys page$/ do - visit keys_path -end - -Then /^I should see my ssh keys$/ do - @user.keys.each do |key| - page.should have_content(key.title) - end -end - -Given /^I have ssh keys:$/ do |table| - table.hashes.each do |row| - Factory :key, :user => @user, :title => row[:title], :key => "jfKLJDFKSFJSHFJ#{row[:title]}" - end -end - -Given /^I submit new ssh key "(.*?)"$/ do |arg1| - fill_in "key_title", :with => arg1 - fill_in "key_key", :with => "publickey234=" - click_button "Save" -end - -Then /^I should see new ssh key "(.*?)"$/ do |arg1| - key = Key.find_by_title(arg1) - page.should have_content(key.title) - page.should have_content(key.key) - current_path.should == key_path(key) -end - -Then /^I should not see "(.*?)" ssh key$/ do |arg1| - within "#keys-table" do - page.should_not have_content(arg1) - end -end diff --git a/features/step_definitions/profile/profile_steps.rb b/features/step_definitions/profile/profile_steps.rb deleted file mode 100644 index 4661139c..00000000 --- a/features/step_definitions/profile/profile_steps.rb +++ /dev/null @@ -1,51 +0,0 @@ -Given /^I visit profile page$/ do - visit profile_path -end - -Then /^I should see my profile info$/ do - page.should have_content "Profile" - page.should have_content @user.name - page.should have_content @user.email -end - -Given /^I visit profile password page$/ do - visit profile_password_path -end - -Then /^I change my password$/ do - fill_in "user_password", :with => "222333" - fill_in "user_password_confirmation", :with => "222333" - click_button "Save" -end - -Then /^I should be redirected to sign in page$/ do - current_path.should == new_user_session_path -end - -Given /^I visit profile token page$/ do - visit profile_token_path -end - -Then /^I reset my token$/ do - @old_token = @user.private_token - click_button "Reset" -end - -Then /^I should see new token$/ do - find("#token").value.should_not == @old_token - find("#token").value.should == @user.reload.private_token -end - -Then /^I change my contact info$/ do - fill_in "user_skype", :with => "testskype" - fill_in "user_linkedin", :with => "testlinkedin" - fill_in "user_twitter", :with => "testtwitter" - click_button "Save" - @user.reload -end - -Then /^I should see new contact info$/ do - @user.skype.should == 'testskype' - @user.linkedin.should == 'testlinkedin' - @user.twitter.should == 'testtwitter' -end diff --git a/features/step_definitions/project/browse_code_steps.rb b/features/step_definitions/project/browse_code_steps.rb deleted file mode 100644 index 7f9001bb..00000000 --- a/features/step_definitions/project/browse_code_steps.rb +++ /dev/null @@ -1,50 +0,0 @@ -Given /^I visit project source page$/ do - visit tree_project_ref_path(@project, @project.root_ref) -end - -Then /^I should see files from repository$/ do - page.should have_content("app") - page.should have_content("History") - page.should have_content("Gemfile") -end - -Given /^I visit project source page for "(.*?)"$/ do |arg1| - visit tree_project_ref_path(@project, arg1) -end - -Then /^I should see files from repository for "(.*?)"$/ do |arg1| - current_path.should == tree_project_ref_path(@project, arg1) - page.should have_content("app") - page.should have_content("History") - page.should have_content("Gemfile") -end - -Given /^I click on file from repo$/ do - click_link "Gemfile" -end - -Then /^I should see it content$/ do - page.should have_content("rubygems.org") -end - -Given /^I click on raw button$/ do - click_link "raw" -end - -Given /^I visit blob file from repo$/ do - visit tree_project_ref_path(@project, ValidCommit::ID, :path => ValidCommit::BLOB_FILE_PATH) -end - -Then /^I should see raw file content$/ do - page.source.should == ValidCommit::BLOB_FILE -end - -Given /^I click blame button$/ do - click_link "blame" -end - -Then /^I should see git file blame$/ do - page.should have_content("rubygems.org") - page.should have_content("Dmitriy Zaporozhets") - page.should have_content("bc3735004cb Moving to rails 3.2") -end diff --git a/features/step_definitions/project/project_commits_steps.rb b/features/step_definitions/project/project_commits_steps.rb deleted file mode 100644 index 35fcb4d1..00000000 --- a/features/step_definitions/project/project_commits_steps.rb +++ /dev/null @@ -1,88 +0,0 @@ -Given /^I visit project commits page$/ do - visit project_commits_path(@project) -end - -Then /^I see project commits$/ do - current_path.should == project_commits_path(@project) - - commit = @project.commit - page.should have_content(@project.name) - page.should have_content(commit.message) - page.should have_content(commit.id.to_s[0..5]) -end - -Given /^I click atom feed link$/ do - click_link "Feed" -end - -Then /^I see commits atom feed$/ do - commit = CommitDecorator.decorate(@project.commit) - page.response_headers['Content-Type'].should have_content("application/atom+xml") - page.body.should have_selector("title", :text => "Recent commits to #{@project.name}") - page.body.should have_selector("author email", :text => commit.author_email) - page.body.should have_selector("entry summary", :text => commit.description) -end - -Given /^I click on commit link$/ do - visit project_commit_path(@project, ValidCommit::ID) -end - -Then /^I see commit info$/ do - page.should have_content ValidCommit::MESSAGE - page.should have_content "Showing 1 changed file" -end - -Given /^I visit compare refs page$/ do - visit compare_project_commits_path(@project) -end - -Given /^I fill compare fields with refs$/ do - fill_in "from", :with => "master" - fill_in "to", :with => "stable" - click_button "Compare" -end - -Given /^I see compared refs$/ do - page.should have_content "Commits (27)" - page.should have_content "Compare View" - page.should have_content "Showing 73 changed files" -end - -Given /^I visit project branches page$/ do - visit branches_project_repository_path(@project) -end - -Given /^I visit project commit page$/ do - visit project_commit_path(@project, ValidCommit::ID) -end - -Given /^I visit project tags page$/ do - visit tags_project_repository_path(@project) -end - -Then /^I should see "(.*?)" recent branches list$/ do |arg1| - page.should have_content("Branches") - page.should have_content("master") -end - -Then /^I should see "(.*?)" all branches list$/ do |arg1| - page.should have_content("Branches") - page.should have_content("master") -end - -Then /^I should see "(.*?)" all tags list$/ do |arg1| - page.should have_content("Tags") - page.should have_content("v1.2.1") -end - -Then /^I should see "(.*?)" protected branches list$/ do |arg1| - within "table" do - page.should have_content "stable" - page.should_not have_content "master" - end -end - -Given /^project "(.*?)" has protected branches$/ do |arg1| - project = Project.find_by_name(arg1) - project.protected_branches.create(:name => "stable") -end diff --git a/features/step_definitions/project/project_issues_steps.rb b/features/step_definitions/project/project_issues_steps.rb deleted file mode 100644 index 00a1721f..00000000 --- a/features/step_definitions/project/project_issues_steps.rb +++ /dev/null @@ -1,38 +0,0 @@ -Given /^project "(.*?)" have "(.*?)" open issue$/ do |arg1, arg2| - project = Project.find_by_name(arg1) - Factory.create(:issue, :title => arg2, :project => project, :author => project.users.first) -end - -Given /^project "(.*?)" have "(.*?)" closed issue$/ do |arg1, arg2| - project = Project.find_by_name(arg1) - Factory.create(:issue, :title => arg2, :project => project, :author => project.users.first, :closed => true) -end - -Given /^I visit project "(.*?)" issues page$/ do |arg1| - visit project_issues_path(Project.find_by_name(arg1)) -end - -Given /^I should see "(.*?)" in issues$/ do |arg1| - page.should have_content arg1 -end - -Given /^I should not see "(.*?)" in issues$/ do |arg1| - page.should_not have_content arg1 -end - -Then /^I should see issue "(.*?)"$/ do |arg1| - issue = Issue.find_by_title(arg1) - page.should have_content issue.title - page.should have_content issue.author_name - page.should have_content issue.project.name -end - -Given /^I visit issue page "(.*?)"$/ do |arg1| - issue = Issue.find_by_title(arg1) - visit project_issue_path(issue.project, issue) -end - -Given /^I submit new issue "(.*?)"$/ do |arg1| - fill_in "issue_title", :with => arg1 - click_button "Submit new issue" -end diff --git a/features/step_definitions/project/project_merge_requests_steps.rb b/features/step_definitions/project/project_merge_requests_steps.rb deleted file mode 100644 index 2bdb967d..00000000 --- a/features/step_definitions/project/project_merge_requests_steps.rb +++ /dev/null @@ -1,47 +0,0 @@ -Given /^project "(.*?)" have "(.*?)" open merge request$/ do |arg1, arg2| - project = Project.find_by_name(arg1) - Factory.create(:merge_request, :title => arg2, :project => project, :author => project.users.first) -end - -Given /^project "(.*?)" have "(.*?)" closed merge request$/ do |arg1, arg2| - project = Project.find_by_name(arg1) - Factory.create(:merge_request, :title => arg2, :project => project, :author => project.users.first, :closed => true) -end - -Given /^I visit project "(.*?)" merge requests page$/ do |arg1| - visit project_merge_requests_path(Project.find_by_name(arg1)) -end - -Then /^I should see "(.*?)" in merge requests$/ do |arg1| - page.should have_content arg1 -end - -Then /^I should not see "(.*?)" in merge requests$/ do |arg1| - page.should_not have_content arg1 -end - -Then /^I should see merge request "(.*?)"$/ do |arg1| - merge_request = MergeRequest.find_by_title(arg1) - page.should have_content(merge_request.title[0..10]) - page.should have_content(merge_request.target_branch) - page.should have_content(merge_request.source_branch) -end - -Given /^I submit new merge request "(.*?)"$/ do |arg1| - fill_in "merge_request_title", :with => arg1 - select "master", :from => "merge_request_source_branch" - select "stable", :from => "merge_request_target_branch" - click_button "Save" -end - -Given /^I visit merge request page "(.*?)"$/ do |arg1| - mr = MergeRequest.find_by_title(arg1) - visit project_merge_request_path(mr.project, mr) -end - -Then /^I should see closed merge request "(.*?)"$/ do |arg1| - mr = MergeRequest.find_by_title(arg1) - mr.closed.should be_true - page.should have_content "Closed by" -end - diff --git a/features/step_definitions/project/project_milestones_steps.rb b/features/step_definitions/project/project_milestones_steps.rb deleted file mode 100644 index 6749773e..00000000 --- a/features/step_definitions/project/project_milestones_steps.rb +++ /dev/null @@ -1,38 +0,0 @@ -Given /^project "(.*?)" has milestone "(.*?)"$/ do |arg1, arg2| - project = Project.find_by_name(arg1) - - milestone = Factory :milestone, - :title => arg2, - :project => project - - 3.times do |i| - issue = Factory :issue, - :project => project, - :milestone => milestone - end -end - -Given /^I visit project "(.*?)" milestones page$/ do |arg1| - @project = Project.find_by_name(arg1) - visit project_milestones_path(@project) -end - -Then /^I should see active milestones$/ do - milestone = @project.milestones.first - page.should have_content(milestone.title[0..10]) - page.should have_content(milestone.expires_at) - page.should have_content("Browse Issues") -end - -Then /^I should see milestone "(.*?)"$/ do |arg1| - milestone = @project.milestones.find_by_title(arg1) - page.should have_content(milestone.title[0..10]) - page.should have_content(milestone.expires_at) - page.should have_content("Browse Issues") -end - -Given /^I submit new milestone "(.*?)"$/ do |arg1| - fill_in "milestone_title", :with => arg1 - click_button "Create milestone" -end - diff --git a/features/step_definitions/project/project_team_steps.rb b/features/step_definitions/project/project_team_steps.rb deleted file mode 100644 index f0bab29a..00000000 --- a/features/step_definitions/project/project_team_steps.rb +++ /dev/null @@ -1,63 +0,0 @@ -Given /^gitlab user "(.*?)"$/ do |arg1| - Factory :user, :name => arg1 -end - -Given /^"(.*?)" is "(.*?)" developer$/ do |arg1, arg2| - user = User.find_by_name(arg1) - project = Project.find_by_name(arg2) - project.add_access(user, :write) -end - -Given /^I visit project "(.*?)" team page$/ do |arg1| - visit team_project_path(Project.find_by_name(arg1)) -end - -Then /^I should be able to see myself in team$/ do - page.should have_content(@user.name) - page.should have_content(@user.email) -end - -Then /^I should see "(.*?)" in team list$/ do |arg1| - user = User.find_by_name(arg1) - page.should have_content(user.name) - page.should have_content(user.email) -end - -Given /^I click link "(.*?)"$/ do |arg1| - click_link arg1 -end - -Given /^I select "(.*?)" as "(.*?)"$/ do |arg1, arg2| - user = User.find_by_name(arg1) - within "#new_team_member" do - select user.name, :from => "team_member_user_id" - select arg2, :from => "team_member_project_access" - end - click_button "Save" -end - -Then /^I should see "(.*?)" in team list as "(.*?)"$/ do |arg1, arg2| - user = User.find_by_name(arg1) - role_id = find(".user_#{user.id} #team_member_project_access").value - role_id.should == UsersProject.access_roles[arg2].to_s -end - -Given /^I change "(.*?)" role to "(.*?)"$/ do |arg1, arg2| - user = User.find_by_name(arg1) - within ".user_#{user.id}" do - select arg2, :from => "team_member_project_access" - end -end - -Then /^I should see "(.*?)" team profile$/ do |arg1| - user = User.find_by_name(arg1) - page.should have_content(user.name) - page.should have_content(user.email) - page.should have_content("To team list") -end - -Then /^I should not see "(.*?)" in team list$/ do |arg1| - user = User.find_by_name(arg1) - page.should_not have_content(user.name) - page.should_not have_content(user.email) -end diff --git a/features/step_definitions/project/project_wiki_steps.rb b/features/step_definitions/project/project_wiki_steps.rb deleted file mode 100644 index 10de38d9..00000000 --- a/features/step_definitions/project/project_wiki_steps.rb +++ /dev/null @@ -1,18 +0,0 @@ -Given /^I visit project wiki page$/ do - visit project_wiki_path(@project, :index) -end - -Given /^I create Wiki page$/ do - fill_in "Title", :with => 'Test title' - fill_in "Content", :with => '[link test](test)' - click_on "Save" -end - -Then /^I should see newly created wiki page$/ do - page.should have_content("Test title") - page.should have_content("link test") - - click_link "link test" - - page.should have_content("Editing page") -end diff --git a/features/step_definitions/project/projects_steps.rb b/features/step_definitions/project/projects_steps.rb deleted file mode 100644 index 3ff08d58..00000000 --- a/features/step_definitions/project/projects_steps.rb +++ /dev/null @@ -1,87 +0,0 @@ -include LoginMacros - -Given /^I signin as a user$/ do - login_as :user -end - -When /^I visit new project page$/ do - visit new_project_path -end - -When /^fill project form with valid data$/ do - fill_in 'project_name', :with => 'NewProject' - fill_in 'project_code', :with => 'NPR' - fill_in 'project_path', :with => 'newproject' - click_button "Create project" -end - -Then /^I should see project page$/ do - current_path.should == project_path(Project.last) - page.should have_content('NewProject') -end - -Then /^I should see empty project instuctions$/ do - page.should have_content("git init") - page.should have_content("git remote") - page.should have_content(Project.last.url_to_repo) -end - -Given /^I own project "(.*?)"$/ do |arg1| - @project = Factory :project, :name => arg1 - @project.add_access(@user, :admin) -end - -Given /^I visit project "(.*?)" wall page$/ do |arg1| - project = Project.find_by_name(arg1) - visit wall_project_path(project) -end - -Then /^I should see project wall note "(.*?)"$/ do |arg1| - page.should have_content arg1 -end - -Given /^project "(.*?)" has comment "(.*?)"$/ do |arg1, arg2| - project = Project.find_by_name(arg1) - project.notes.create(:note => arg1, :author => project.users.first) -end - -Given /^I write new comment "(.*?)"$/ do |arg1| - fill_in "note_note", :with => arg1 - click_button "Add Comment" -end - -Given /^I visit project "(.*?)" page$/ do |arg1| - project = Project.find_by_name(arg1) - visit project_path(project) -end - -Given /^I visit project "(.*?)" network page$/ do |arg1| - project = Project.find_by_name(arg1) - - # Stub out find_all to speed this up (10 commits vs. 650) - commits = Grit::Commit.find_all(project.repo, nil, {max_count: 10}) - Grit::Commit.stub(:find_all).and_return(commits) - - visit graph_project_path(project) -end - -Given /^show me page$/ do - save_and_open_page -end - -Given /^page should have network graph$/ do - page.should have_content "Project Network Graph" - within ".graph" do - page.should have_content "master" - page.should have_content "scss_refactor..." - end -end - -Given /^I leave a comment like "(.*?)"$/ do |arg1| - fill_in "note_note", :with => arg1 - click_button "Add Comment" -end - -Then /^I should see comment "(.*?)"$/ do |arg1| - page.should have_content(arg1) -end diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb new file mode 100644 index 00000000..6c603bbe --- /dev/null +++ b/features/steps/dashboard/dashboard.rb @@ -0,0 +1,92 @@ +class Dashboard < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + + Then 'I should see "New Project" link' do + page.should have_link "New Project" + end + + Then 'I should see "Shop" project link' do + page.should have_link "Shop" + end + + Then 'I should see project "Shop" activity feed' do + project = Project.find_by_name("Shop") + page.should have_content "#{@user.name} pushed new branch new_design at #{project.name}" + end + + Then 'I should see last push widget' do + page.should have_content "Your pushed to branch new_design" + page.should have_link "Create Merge Request" + end + + And 'I click "Create Merge Request" link' do + click_link "Create Merge Request" + end + + Then 'I see prefilled new Merge Request page' do + current_path.should == new_project_merge_request_path(@project) + find("#merge_request_source_branch").value.should == "new_design" + find("#merge_request_target_branch").value.should == "master" + find("#merge_request_title").value.should == "New Design" + end + + Given 'user with name "John Doe" joined project "Shop"' do + user = Factory.create(:user, {name: "John Doe"}) + project = Project.find_by_name "Shop" + Event.create( + project: project, + author_id: user.id, + action: Event::Joined + ) + end + + Then 'I should see "John Doe joined project Shop" event' do + page.should have_content "John Doe joined project Shop" + end + + And 'user with name "John Doe" left project "Shop"' do + user = User.find_by_name "John Doe" + project = Project.find_by_name "Shop" + Event.create( + project: project, + author_id: user.id, + action: Event::Left + ) + end + + Then 'I should see "John Doe left project Shop" event' do + page.should have_content "John Doe left project Shop" + end + + And 'I own project "Shop"' do + @project = Factory :project, :name => 'Shop' + @project.add_access(@user, :admin) + end + + And 'project "Shop" has push event' do + @project = Project.find_by_name("Shop") + + data = { + :before => "0000000000000000000000000000000000000000", + :after => "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e", + :ref => "refs/heads/new_design", + :user_id => @user.id, + :user_name => @user.name, + :repository => { + :name => @project.name, + :url => "localhost/rubinius", + :description => "", + :homepage => "localhost/rubinius", + :private => true + } + } + + @event = Event.create( + :project => @project, + :action => Event::Pushed, + :data => data, + :author_id => @user.id + ) + end +end diff --git a/features/steps/dashboard/dashboard_issues.rb b/features/steps/dashboard/dashboard_issues.rb new file mode 100644 index 00000000..9368782b --- /dev/null +++ b/features/steps/dashboard/dashboard_issues.rb @@ -0,0 +1,19 @@ +class DashboardIssues < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + + Then 'I should see issues assigned to me' do + issues = @user.issues + issues.each do |issue| + page.should have_content(issue.title[0..10]) + page.should have_content(issue.project.name) + end + end + + And 'I have assigned issues' do + project = Factory :project + project.add_access(@user, :read, :write) + + 2.times { Factory :issue, :author => @user, :assignee => @user, :project => project } + end +end diff --git a/features/steps/dashboard/dashboard_merge_requests.rb b/features/steps/dashboard/dashboard_merge_requests.rb new file mode 100644 index 00000000..fc339e75 --- /dev/null +++ b/features/steps/dashboard/dashboard_merge_requests.rb @@ -0,0 +1,23 @@ +class DashboardMergeRequests < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + + Then 'I should see my merge requests' do + merge_requests = @user.merge_requests + merge_requests.each do |mr| + page.should have_content(mr.title[0..10]) + page.should have_content(mr.project.name) + end + end + + And 'I have authored merge requests' do + project1 = Factory :project + project2 = Factory :project + + project1.add_access(@user, :read, :write) + project2.add_access(@user, :read, :write) + + merge_request1 = Factory :merge_request, :author => @user, :project => project1 + merge_request2 = Factory :merge_request, :author => @user, :project => project2 + end +end diff --git a/features/steps/dashboard/dashboard_search.rb b/features/steps/dashboard/dashboard_search.rb new file mode 100644 index 00000000..e3585898 --- /dev/null +++ b/features/steps/dashboard/dashboard_search.rb @@ -0,0 +1,18 @@ +class DashboardSearch < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + + Given 'I search for "Sho"' do + fill_in "dashboard_search", :with => "Sho" + click_button "Search" + end + + Then 'I should see "Shop" project link' do + page.should have_link "Shop" + end + + And 'I own project "Shop"' do + @project = Factory :project, :name => "Shop" + @project.add_access(@user, :admin) + end +end diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb new file mode 100644 index 00000000..d3261a16 --- /dev/null +++ b/features/steps/profile/profile.rb @@ -0,0 +1,44 @@ +class Profile < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + + Then 'I should see my profile info' do + page.should have_content "Profile" + page.should have_content @user.name + page.should have_content @user.email + end + + Then 'I change my contact info' do + fill_in "user_skype", :with => "testskype" + fill_in "user_linkedin", :with => "testlinkedin" + fill_in "user_twitter", :with => "testtwitter" + click_button "Save" + @user.reload + end + + And 'I should see new contact info' do + @user.skype.should == 'testskype' + @user.linkedin.should == 'testlinkedin' + @user.twitter.should == 'testtwitter' + end + + Then 'I change my password' do + fill_in "user_password", :with => "222333" + fill_in "user_password_confirmation", :with => "222333" + click_button "Save" + end + + And 'I should be redirected to sign in page' do + current_path.should == new_user_session_path + end + + Then 'I reset my token' do + @old_token = @user.private_token + click_button "Reset" + end + + And 'I should see new token' do + find("#token").value.should_not == @old_token + find("#token").value.should == @user.reload.private_token + end +end diff --git a/features/steps/profile/profile_ssh_keys.rb b/features/steps/profile/profile_ssh_keys.rb new file mode 100644 index 00000000..96df2d73 --- /dev/null +++ b/features/steps/profile/profile_ssh_keys.rb @@ -0,0 +1,48 @@ +class ProfileSshKeys < Spinach::FeatureSteps + include SharedAuthentication + + Then 'I should see my ssh keys' do + @user.keys.each do |key| + page.should have_content(key.title) + end + end + + Given 'I click link "Add new"' do + click_link "Add new" + end + + And 'I submit new ssh key "Laptop"' do + fill_in "key_title", :with => "Laptop" + fill_in "key_key", :with => "ssh-rsa publickey234=" + click_button "Save" + end + + Then 'I should see new ssh key "Laptop"' do + key = Key.find_by_title("Laptop") + page.should have_content(key.title) + page.should have_content(key.key) + current_path.should == key_path(key) + end + + Given 'I click link "Work"' do + click_link "Work" + end + + And 'I click link "Remove"' do + click_link "Remove" + end + + Then 'I visit profile keys page' do + visit keys_path + end + + And 'I should not see "Work" ssh key' do + within "#keys-table" do + page.should_not have_content "Work" + end + end + + And 'I have ssh key "ssh-rsa Work"' do + Factory :key, :user => @user, :title => "ssh-rsa Work", :key => "jfKLJDFKSFJSHFJssh-rsa Work" + end +end diff --git a/features/steps/project/create_project.rb b/features/steps/project/create_project.rb new file mode 100644 index 00000000..6d2ca3f9 --- /dev/null +++ b/features/steps/project/create_project.rb @@ -0,0 +1,22 @@ +class CreateProject < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + + And 'fill project form with valid data' do + fill_in 'project_name', :with => 'NewProject' + fill_in 'project_code', :with => 'NPR' + fill_in 'project_path', :with => 'newproject' + click_button "Create project" + end + + Then 'I should see project page' do + current_path.should == project_path(Project.last) + page.should have_content "NewProject" + end + + And 'I should see empty project instuctions' do + page.should have_content "git init" + page.should have_content "git remote" + page.should have_content Project.last.url_to_repo + end +end diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb new file mode 100644 index 00000000..f33f12eb --- /dev/null +++ b/features/steps/project/project.rb @@ -0,0 +1,5 @@ +class Projects < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths +end diff --git a/features/steps/project/project_browse_branches.rb b/features/steps/project/project_browse_branches.rb new file mode 100644 index 00000000..2f6e185d --- /dev/null +++ b/features/steps/project/project_browse_branches.rb @@ -0,0 +1,35 @@ +class ProjectBrowseBranches < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + Then 'I should see "Shop" recent branches list' do + page.should have_content "Branches" + page.should have_content "master" + end + + Given 'I click link "All"' do + click_link "All" + end + + Then 'I should see "Shop" all branches list' do + page.should have_content "Branches" + page.should have_content "master" + end + + Given 'I click link "Protected"' do + click_link "Protected" + end + + Then 'I should see "Shop" protected branches list' do + within "table" do + page.should have_content "stable" + page.should_not have_content "master" + end + end + + And 'project "Shop" has protected branches' do + project = Project.find_by_name("Shop") + project.protected_branches.create(:name => "stable") + end +end diff --git a/features/steps/project/project_browse_commits.rb b/features/steps/project/project_browse_commits.rb new file mode 100644 index 00000000..01479987 --- /dev/null +++ b/features/steps/project/project_browse_commits.rb @@ -0,0 +1,47 @@ +class ProjectBrowseCommits < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + Then 'I see project commits' do + current_path.should == project_commits_path(@project) + + commit = @project.commit + page.should have_content(@project.name) + page.should have_content(commit.message) + page.should have_content(commit.id.to_s[0..5]) + end + + Given 'I click atom feed link' do + click_link "Feed" + end + + Then 'I see commits atom feed' do + commit = CommitDecorator.decorate(@project.commit) + page.response_headers['Content-Type'].should have_content("application/atom+xml") + page.body.should have_selector("title", :text => "Recent commits to #{@project.name}") + page.body.should have_selector("author email", :text => commit.author_email) + page.body.should have_selector("entry summary", :text => commit.description) + end + + Given 'I click on commit link' do + visit project_commit_path(@project, ValidCommit::ID) + end + + Then 'I see commit info' do + page.should have_content ValidCommit::MESSAGE + page.should have_content "Showing 1 changed file" + end + + And 'I fill compare fields with refs' do + fill_in "from", :with => "master" + fill_in "to", :with => "stable" + click_button "Compare" + end + + And 'I see compared refs' do + page.should have_content "Commits (27)" + page.should have_content "Compare View" + page.should have_content "Showing 73 changed files" + end +end diff --git a/features/steps/project/project_browse_files.rb b/features/steps/project/project_browse_files.rb new file mode 100644 index 00000000..67c553ce --- /dev/null +++ b/features/steps/project/project_browse_files.rb @@ -0,0 +1,34 @@ +class ProjectBrowseFiles < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + Then 'I should see files from repository' do + page.should have_content "app" + page.should have_content "History" + page.should have_content "Gemfile" + end + + Then 'I should see files from repository for "8470d70"' do + current_path.should == tree_project_ref_path(@project, "8470d70") + page.should have_content "app" + page.should have_content "History" + page.should have_content "Gemfile" + end + + Given 'I click on "Gemfile" file in repo' do + click_link "Gemfile" + end + + Then 'I should see it content' do + page.should have_content "rubygems.org" + end + + And 'I click link "raw"' do + click_link "raw" + end + + Then 'I should see raw file content' do + page.source.should == ValidCommit::BLOB_FILE + end +end diff --git a/features/steps/project/project_browse_git_repo.rb b/features/steps/project/project_browse_git_repo.rb new file mode 100644 index 00000000..e966f407 --- /dev/null +++ b/features/steps/project/project_browse_git_repo.rb @@ -0,0 +1,19 @@ +class ProjectBrowseGitRepo < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + Given 'I click on "Gemfile" file in repo' do + click_link "Gemfile" + end + + And 'I click blame button' do + click_link "blame" + end + + Then 'I should see git file blame' do + page.should have_content "rubygems.org" + page.should have_content "Dmitriy Zaporozhets" + page.should have_content "bc3735004cb Moving to rails 3.2" + end +end diff --git a/features/steps/project/project_browse_tags.rb b/features/steps/project/project_browse_tags.rb new file mode 100644 index 00000000..0cbfa0d8 --- /dev/null +++ b/features/steps/project/project_browse_tags.rb @@ -0,0 +1,10 @@ +class ProjectBrowseTags < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + Then 'I should see "Shop" all tags list' do + page.should have_content "Tags" + page.should have_content "v1.2.1" + end +end diff --git a/features/steps/project/project_comment_commit.rb b/features/steps/project/project_comment_commit.rb new file mode 100644 index 00000000..cb8385e1 --- /dev/null +++ b/features/steps/project/project_comment_commit.rb @@ -0,0 +1,6 @@ +class ProjectCommentCommit < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedNote + include SharedPaths +end diff --git a/features/steps/project/project_issues.rb b/features/steps/project/project_issues.rb new file mode 100644 index 00000000..64af2449 --- /dev/null +++ b/features/steps/project/project_issues.rb @@ -0,0 +1,134 @@ +class ProjectIssues < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedNote + include SharedPaths + + Given 'I should see "Release 0.4" in issues' do + page.should have_content "Release 0.4" + end + + And 'I should not see "Release 0.3" in issues' do + page.should_not have_content "Release 0.3" + end + + Given 'I click link "Closed"' do + click_link "Closed" + end + + Then 'I should see "Release 0.3" in issues' do + page.should have_content "Release 0.3" + end + + And 'I should not see "Release 0.4" in issues' do + page.should_not have_content "Release 0.4" + end + + Given 'I click link "All"' do + click_link "All" + end + + Given 'I click link "Release 0.4"' do + click_link "Release 0.4" + end + + Then 'I should see issue "Release 0.4"' do + page.should have_content "Release 0.4" + end + + Given 'I click link "New Issue"' do + click_link "New Issue" + end + + And 'I submit new issue "500 error on profile"' do + fill_in "issue_title", :with => "500 error on profile" + click_button "Submit new issue" + end + + Given 'I click link "500 error on profile"' do + click_link "500 error on profile" + end + + Then 'I should see issue "500 error on profile"' do + issue = Issue.find_by_title("500 error on profile") + page.should have_content issue.title + page.should have_content issue.author_name + page.should have_content issue.project.name + end + + Given 'I fill in issue search with "Release"' do + fill_in 'issue_search', with: "Release" + end + + Given 'I fill in issue search with "Bug"' do + fill_in 'issue_search', with: "Bug" + end + + And 'I fill in issue search with "0.3"' do + fill_in 'issue_search', with: "0.3" + end + + And 'I fill in issue search with "Something"' do + fill_in 'issue_search', with: "Something" + end + + And 'I fill in issue search with ""' do + page.execute_script("$('.issue_search').val('').keyup();"); + fill_in 'issue_search', with: "" + end + + Given 'project "Shop" has milestone "v2.2"' do + project = Project.find_by_name("Shop") + milestone = Factory :milestone, :title => "v2.2", :project => project + + 3.times { Factory :issue, :project => project, :milestone => milestone } + end + + And 'project "Shop" has milestone "v3.0"' do + project = Project.find_by_name("Shop") + milestone = Factory :milestone, :title => "v3.0", :project => project + + 3.times { Factory :issue, :project => project, :milestone => milestone } + end + + When 'I select milestone "v3.0"' do + select "v3.0", from: "milestone_id" + end + + Then 'I should see selected milestone with title "v3.0"' do + issues_milestone_selector = "#issue_milestone_id_chzn/a" + wait_until { page.has_content?("Details") } + page.find(issues_milestone_selector).should have_content("v3.0") + end + + When 'I select first assignee from "Shop" project' do + project = Project.find_by_name "Shop" + first_assignee = project.users.first + select first_assignee.name, from: "assignee_id" + end + + Then 'I should see first assignee from "Shop" as selected assignee' do + issues_assignee_selector = "#issue_assignee_id_chzn/a" + wait_until { page.has_content?("Details") } + project = Project.find_by_name "Shop" + assignee_name = project.users.first.name + page.find(issues_assignee_selector).should have_content(assignee_name) + end + + And 'project "Shop" have "Release 0.4" open issue' do + project = Project.find_by_name("Shop") + Factory.create(:issue, + :title => "Release 0.4", + :project => project, + :author => project.users.first) + end + + And 'project "Shop" have "Release 0.3" closed issue' do + project = Project.find_by_name("Shop") + Factory.create(:issue, + :title => "Release 0.3", + :project => project, + :author => project.users.first, + :closed => true) + end +end diff --git a/features/steps/project/project_labels.rb b/features/steps/project/project_labels.rb new file mode 100644 index 00000000..1a347bf3 --- /dev/null +++ b/features/steps/project/project_labels.rb @@ -0,0 +1,24 @@ +class ProjectLabels < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + Then 'I should see label "bug"' do + within ".labels-table" do + page.should have_content "bug" + end + end + + And 'I should see label "feature"' do + within ".labels-table" do + page.should have_content "feature" + end + end + + And 'project "Shop" have issues tags: "bug", "feature"' do + project = Project.find_by_name("Shop") + ['bug', 'feature'].each do |label| + Factory :issue, project: project, label_list: label + end + end +end diff --git a/features/steps/project/project_merge_requests.rb b/features/steps/project/project_merge_requests.rb new file mode 100644 index 00000000..80e83906 --- /dev/null +++ b/features/steps/project/project_merge_requests.rb @@ -0,0 +1,80 @@ +class ProjectMergeRequests < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedNote + include SharedPaths + + Then 'I should see "Bug NS-04" in merge requests' do + page.should have_content "Bug NS-04" + end + + And 'I should not see "Feature NS-03" in merge requests' do + page.should_not have_content "Feature NS-03" + end + + Given 'I click link "Closed"' do + click_link "Closed" + end + + Then 'I should see "Feature NS-03" in merge requests' do + page.should have_content "Feature NS-03" + end + + And 'I should not see "Bug NS-04" in merge requests' do + page.should_not have_content "Bug NS-04" + end + + Given 'I click link "All"' do + click_link "All" + end + + Given 'I click link "Bug NS-04"' do + click_link "Bug NS-04" + end + + Then 'I should see merge request "Bug NS-04"' do + page.should have_content "Bug NS-04" + end + + And 'I click link "Close"' do + click_link "Close" + end + + Then 'I should see closed merge request "Bug NS-04"' do + mr = MergeRequest.find_by_title("Bug NS-04") + mr.closed.should be_true + page.should have_content "Closed by" + end + + Given 'I click link "New Merge Request"' do + click_link "New Merge Request" + end + + And 'I submit new merge request "Wiki Feature"' do + fill_in "merge_request_title", :with => "Wiki Feature" + select "master", :from => "merge_request_source_branch" + select "stable", :from => "merge_request_target_branch" + click_button "Save" + end + + Then 'I should see merge request "Wiki Feature"' do + page.should have_content "Wiki Feature" + end + + And 'project "Shop" have "Bug NS-04" open merge request' do + project = Project.find_by_name("Shop") + Factory.create(:merge_request, + :title => "Bug NS-04", + :project => project, + :author => project.users.first) + end + + And 'project "Shop" have "Feature NS-03" closed merge request' do + project = Project.find_by_name("Shop") + Factory.create(:merge_request, + :title => "Feature NS-03", + :project => project, + :author => project.users.first, + :closed => true) + end +end diff --git a/features/steps/project/project_milestones.rb b/features/steps/project/project_milestones.rb new file mode 100644 index 00000000..83ed6859 --- /dev/null +++ b/features/steps/project/project_milestones.rb @@ -0,0 +1,39 @@ +class ProjectMilestones < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + Then 'I should see milestone "v2.2"' do + milestone = @project.milestones.find_by_title("v2.2") + page.should have_content(milestone.title[0..10]) + page.should have_content(milestone.expires_at) + page.should have_content("Browse Issues") + end + + Given 'I click link "v2.2"' do + click_link "v2.2" + end + + Given 'I click link "New Milestone"' do + click_link "New Milestone" + end + + And 'I submit new milestone "v2.3"' do + fill_in "milestone_title", :with => "v2.3" + click_button "Create milestone" + end + + Then 'I should see milestone "v2.3"' do + milestone = @project.milestones.find_by_title("v2.3") + page.should have_content(milestone.title[0..10]) + page.should have_content(milestone.expires_at) + page.should have_content("Browse Issues") + end + + And 'project "Shop" has milestone "v2.2"' do + project = Project.find_by_name("Shop") + milestone = Factory :milestone, :title => "v2.2", :project => project + + 3.times { Factory :issue, :project => project, :milestone => milestone } + end +end diff --git a/features/steps/project/project_network_graph.rb b/features/steps/project/project_network_graph.rb new file mode 100644 index 00000000..f34a81a4 --- /dev/null +++ b/features/steps/project/project_network_graph.rb @@ -0,0 +1,22 @@ +class ProjectNetworkGraph < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + + Then 'page should have network graph' do + page.should have_content "Project Network Graph" + within ".graph" do + page.should have_content "master" + page.should have_content "scss_refactor..." + end + end + + And 'I visit project "Shop" network page' do + project = Project.find_by_name("Shop") + + # Stub out find_all to speed this up (10 commits vs. 650) + commits = Grit::Commit.find_all(project.repo, nil, {max_count: 10}) + Grit::Commit.stub(:find_all).and_return(commits) + + visit graph_project_path(project) + end +end diff --git a/features/steps/project/project_team_management.rb b/features/steps/project/project_team_management.rb new file mode 100644 index 00000000..7beca257 --- /dev/null +++ b/features/steps/project/project_team_management.rb @@ -0,0 +1,89 @@ +class ProjectTeamManagement < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + Then 'I should be able to see myself in team' do + page.should have_content(@user.name) + page.should have_content(@user.email) + end + + And 'I should see "Sam" in team list' do + user = User.find_by_name("Sam") + page.should have_content(user.name) + page.should have_content(user.email) + end + + Given 'I click link "New Team Member"' do + click_link "New Team Member" + end + + And 'I select "Mike" as "Reporter"' do + user = User.find_by_name("Mike") + within "#new_team_member" do + select user.name, :from => "user_ids" + select "Reporter", :from => "project_access" + end + click_button "Save" + end + + Then 'I should see "Mike" in team list as "Reporter"' do + user = User.find_by_name("Mike") + role_id = find(".user_#{user.id} #team_member_project_access").value + role_id.should == UsersProject.access_roles["Reporter"].to_s + end + + Given 'I should see "Sam" in team list as "Developer"' do + user = User.find_by_name("Sam") + role_id = find(".user_#{user.id} #team_member_project_access").value + role_id.should == UsersProject.access_roles["Developer"].to_s + end + + And 'I change "Sam" role to "Reporter"' do + user = User.find_by_name("Sam") + within ".user_#{user.id}" do + select "Reporter", :from => "team_member_project_access" + end + end + + And 'I should see "Sam" in team list as "Reporter"' do + user = User.find_by_name("Sam") + role_id = find(".user_#{user.id} #team_member_project_access").value + role_id.should == UsersProject.access_roles["Reporter"].to_s + end + + Given 'I click link "Sam"' do + click_link "Sam" + end + + Then 'I should see "Sam" team profile' do + user = User.find_by_name("Sam") + page.should have_content(user.name) + page.should have_content(user.email) + page.should have_content("To team list") + end + + And 'I click link "Remove from team"' do + click_link "Remove from team" + end + + And 'I should not see "Sam" in team list' do + user = User.find_by_name("Sam") + page.should_not have_content(user.name) + page.should_not have_content(user.email) + end + + And 'gitlab user "Mike"' do + Factory :user, :name => "Mike" + end + + And 'gitlab user "Sam"' do + Factory :user, :name => "Sam" + end + + And '"Sam" is "Shop" developer' do + user = User.find_by_name("Sam") + project = Project.find_by_name("Shop") + project.add_access(user, :write) + end +end diff --git a/features/steps/project/project_wall.rb b/features/steps/project/project_wall.rb new file mode 100644 index 00000000..ba9d3533 --- /dev/null +++ b/features/steps/project/project_wall.rb @@ -0,0 +1,6 @@ +class ProjectWall < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedNote + include SharedPaths +end diff --git a/features/steps/project/project_wiki.rb b/features/steps/project/project_wiki.rb new file mode 100644 index 00000000..902e9ce1 --- /dev/null +++ b/features/steps/project/project_wiki.rb @@ -0,0 +1,20 @@ +class ProjectWiki < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedNote + include SharedPaths + + Given 'I create Wiki page' do + fill_in "Title", :with => 'Test title' + fill_in "Content", :with => '[link test](test)' + click_on "Save" + end + + Then 'I should see newly created wiki page' do + page.should have_content "Test title" + page.should have_content "link test" + + click_link "link test" + page.should have_content "Editing page" + end +end diff --git a/features/steps/shared/authentication.rb b/features/steps/shared/authentication.rb new file mode 100644 index 00000000..77d9839f --- /dev/null +++ b/features/steps/shared/authentication.rb @@ -0,0 +1,10 @@ +require Rails.root.join('spec', 'support', 'login_helpers') + +module SharedAuthentication + include Spinach::DSL + include LoginHelpers + + Given 'I sign in as a user' do + login_as :user + end +end diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb new file mode 100644 index 00000000..923e69b6 --- /dev/null +++ b/features/steps/shared/note.rb @@ -0,0 +1,21 @@ +module SharedNote + include Spinach::DSL + + Given 'I leave a comment like "XML attached"' do + fill_in "note_note", :with => "XML attached" + click_button "Add Comment" + end + + Then 'I should see comment "XML attached"' do + page.should have_content "XML attached" + end + + Given 'I write new comment "my special test message"' do + fill_in "note_note", :with => "my special test message" + click_button "Add Comment" + end + + Then 'I should see project wall note "my special test message"' do + page.should have_content "my special test message" + end +end diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb new file mode 100644 index 00000000..93ad0219 --- /dev/null +++ b/features/steps/shared/paths.rb @@ -0,0 +1,112 @@ +module SharedPaths + include Spinach::DSL + + And 'I visit dashboard search page' do + visit search_path + end + + And 'I visit dashboard merge requests page' do + visit dashboard_merge_requests_path + end + + And 'I visit dashboard issues page' do + visit dashboard_issues_path + end + + When 'I visit dashboard page' do + visit dashboard_path + end + + Given 'I visit profile page' do + visit profile_path + end + + Given 'I visit profile account page' do + visit profile_account_path + end + + Given 'I visit profile token page' do + visit profile_token_path + end + + When 'I visit new project page' do + visit new_project_path + end + + And 'I visit project "Shop" page' do + project = Project.find_by_name("Shop") + visit project_path(project) + end + + Given 'I visit project branches page' do + visit branches_project_repository_path(@project) + end + + Given 'I visit compare refs page' do + visit compare_project_commits_path(@project) + end + + Given 'I visit project commits page' do + visit project_commits_path(@project) + end + + Given 'I visit project source page' do + visit tree_project_ref_path(@project, @project.root_ref) + end + + Given 'I visit blob file from repo' do + visit tree_project_ref_path(@project, ValidCommit::ID, :path => ValidCommit::BLOB_FILE_PATH) + end + + Given 'I visit project source page for "8470d70"' do + visit tree_project_ref_path(@project, "8470d70") + end + + Given 'I visit project tags page' do + visit tags_project_repository_path(@project) + end + + Given 'I visit project commit page' do + visit project_commit_path(@project, ValidCommit::ID) + end + + And 'I visit project "Shop" issues page' do + visit project_issues_path(Project.find_by_name("Shop")) + end + + Given 'I visit issue page "Release 0.4"' do + issue = Issue.find_by_title("Release 0.4") + visit project_issue_path(issue.project, issue) + end + + Given 'I visit project "Shop" labels page' do + visit project_labels_path(Project.find_by_name("Shop")) + end + + Given 'I visit merge request page "Bug NS-04"' do + mr = MergeRequest.find_by_title("Bug NS-04") + visit project_merge_request_path(mr.project, mr) + end + + And 'I visit project "Shop" merge requests page' do + visit project_merge_requests_path(Project.find_by_name("Shop")) + end + + Given 'I visit project "Shop" milestones page' do + @project = Project.find_by_name("Shop") + visit project_milestones_path(@project) + end + + Then 'I visit project "Shop" team page' do + visit project_team_index_path(Project.find_by_name("Shop")) + end + + Then 'I visit project "Shop" wall page' do + project = Project.find_by_name("Shop") + visit wall_project_path(project) + end + + Given 'I visit project wiki page' do + visit project_wiki_path(@project, :index) + end +end diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb new file mode 100644 index 00000000..9b64ca59 --- /dev/null +++ b/features/steps/shared/project.rb @@ -0,0 +1,8 @@ +module SharedProject + include Spinach::DSL + + And 'I own project "Shop"' do + @project = Factory :project, :name => "Shop" + @project.add_access(@user, :admin) + end +end diff --git a/features/support/env.rb b/features/support/env.rb index b69a5fe7..9c6cef07 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -1,50 +1,27 @@ -unless ENV['CI'] - require 'simplecov' - SimpleCov.start 'rails' +ENV['RAILS_ENV'] = 'test' +require './config/environment' + +require 'rspec' +require 'database_cleaner' +require 'spinach/capybara' + +%w(gitolite_stub stubbed_repository valid_commit).each do |f| + require Rails.root.join('spec', 'support', f) end -require 'cucumber/rails' -require 'webmock/cucumber' +Dir["#{Rails.root}/features/steps/shared/*.rb"].each {|file| require file} + +include GitoliteStub + WebMock.allow_net_connect! - -require Rails.root.join 'spec/monkeypatch' -require Rails.root.join 'spec/factories' -require Rails.root.join 'spec/support/login' -require Rails.root.join 'spec/support/valid_commit' - -Capybara.default_selector = :css Capybara.javascript_driver = :webkit -# By default, any exception happening in your Rails application will bubble up -# to Cucumber so that your scenario will fail. This is a different from how -# your application behaves in the production environment, where an error page will -# be rendered instead. -# -# Sometimes we want to override this default behaviour and allow Rails to rescue -# exceptions and display an error page (just like when the app is running in production). -# Typical scenarios where you want to do this is when you test your error pages. -# There are two ways to allow Rails to rescue exceptions: -# -# 1) Tag your scenario (or feature) with @allow-rescue -# -# 2) Set the value below to true. Beware that doing this globally is not -# recommended as it will mask a lot of errors for you! -# -ActionController::Base.allow_rescue = false +DatabaseCleaner.strategy = :truncation +Spinach.hooks.before_scenario { DatabaseCleaner.start } +Spinach.hooks.after_scenario { DatabaseCleaner.clean } -# Remove/comment out the lines below if your app doesn't have a database. -# For some databases (like MongoDB and CouchDB) you may need to use :truncation instead. -begin - DatabaseCleaner.strategy = :transaction -rescue NameError - raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it." +Spinach.hooks.before_run do + RSpec::Mocks::setup self + + stub_gitolite! end - -Cucumber::Rails::Database.javascript_strategy = :truncation - -require 'headless' - -headless = Headless.new -headless.start - -require 'cucumber/rspec/doubles' diff --git a/gitlab b/gitlab new file mode 100755 index 00000000..acafb3f1 --- /dev/null +++ b/gitlab @@ -0,0 +1,75 @@ +#!/usr/bin/env ruby + +class GitlabCli + def initialize + @path = File.dirname(__FILE__) + @command = ARGV.shift + @mode = ARGV.shift + end + + def execute + case @command + when 'start' then start + when 'stop' then stop + else + puts "-- Usage gitlab start production or gitlab stop development" + end + end + + private + + def start + case @mode + when 'production'; + system(unicorn_start_cmd) + system(resque_start_cmd) + else + system(rails_start_cmd) + system(resque_dev_start_cmd) + end + end + + def stop + case @mode + when 'production'; + system(unicorn_stop_cmd) + else + system(rails_stop_cmd) + end + system(resque_stop_cmd) + end + + def rails_start_cmd + "bundle exec rails s -d" + end + + def rails_stop_cmd + pid = File.join(@path, "tmp/pids/server.pid") + "kill -QUIT `cat #{pid}`" + end + + def unicorn_start_cmd + unicorn_conf = File.join(@path, "config/unicorn.rb") + "bundle exec unicorn_rails -c #{unicorn_conf} -E production -D" + end + + def unicorn_stop_cmd + pid = File.join(@path, "/tmp/pids/unicorn.pid") + "kill -QUIT `cat #{pid}`" + end + + def resque_dev_start_cmd + "./resque_dev.sh > /dev/null 2>&1" + end + + def resque_start_cmd + "./resque.sh > /dev/null 2>&1" + end + + def resque_stop_cmd + pid = File.join(@path, "tmp/pids/resque_worker.pid") + "kill -QUIT `cat #{pid}`" + end +end + +GitlabCli.new.execute diff --git a/lib/api.rb b/lib/api.rb index 3ff3b383..2890a8cc 100644 --- a/lib/api.rb +++ b/lib/api.rb @@ -16,5 +16,7 @@ module Gitlab mount Users mount Projects mount Issues + mount Milestones + mount Session end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 96ccd87a..ee693de6 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -9,6 +9,14 @@ module Gitlab expose :id, :email, :name, :blocked, :created_at end + class UserLogin < UserBasic + expose :private_token + end + + class Hook < Grape::Entity + expose :id, :url + end + class Project < Grape::Entity expose :id, :code, :name, :description, :path, :default_branch expose :owner, using: Entities::UserBasic @@ -16,10 +24,20 @@ module Gitlab expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :created_at end + class ProjectMember < UserBasic + expose :project_access, :as => :access_level do |user, options| + options[:project].users_projects.find_by_user_id(user.id).project_access + end + end + class RepoObject < Grape::Entity expose :name, :commit end + class RepoCommit < Grape::Entity + expose :id, :short_id, :title, :author_name, :author_email, :created_at + end + class ProjectSnippet < Grape::Entity expose :id, :title, :file_name expose :author, using: Entities::UserBasic @@ -27,7 +45,9 @@ module Gitlab end class Milestone < Grape::Entity - expose :id, :title, :description, :due_date, :closed, :updated_at, :created_at + expose :id + expose (:project_id) {|milestone| milestone.project.id} + expose :title, :description, :due_date, :closed, :updated_at, :created_at end class Issue < Grape::Entity @@ -39,5 +59,9 @@ module Gitlab expose :assignee, :author, using: Entities::UserBasic expose :closed, :updated_at, :created_at end + + class SSHKey < Grape::Entity + expose :id, :title, :key + end end end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index c1ea0566..14390545 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -8,14 +8,67 @@ module Gitlab if @project ||= current_user.projects.find_by_id(params[:id]) || current_user.projects.find_by_code(params[:id]) else - error!({'message' => '404 Not found'}, 404) + not_found! end @project end + def paginate(object) + object.page(params[:page]).per(params[:per_page].to_i) + end + def authenticate! - error!({'message' => '401 Unauthorized'}, 401) unless current_user + unauthorized! unless current_user + end + + def authorize! action, subject + unless abilities.allowed?(current_user, action, subject) + forbidden! + end + end + + def attributes_for_keys(keys) + attrs = {} + keys.each do |key| + attrs[key] = params[key] if params[key].present? + end + attrs + end + + # error helpers + + def forbidden! + render_api_error!('403 Forbidden', 403) + end + + def not_found!(resource = nil) + message = ["404"] + message << resource if resource + message << "Not Found" + render_api_error!(message.join(' '), 404) + end + + def unauthorized! + render_api_error!('401 Unauthorized', 401) + end + + def not_allowed! + render_api_error!('Method Not Allowed', 405) + end + + def render_api_error!(message, status) + error!({'message' => message}, status) + end + + private + + def abilities + @abilities ||= begin + abilities = Six.new + abilities << Ability + abilities + end end end end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 2abc20ad..4ee2d11f 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -9,7 +9,7 @@ module Gitlab # Example Request: # GET /issues get do - present current_user.issues, with: Entities::Issue + present paginate(current_user.issues), with: Entities::Issue end end @@ -21,7 +21,7 @@ module Gitlab # Example Request: # GET /projects/:id/issues get ":id/issues" do - present user_project.issues, with: Entities::Issue + present paginate(user_project.issues), with: Entities::Issue end # Get a single project issue @@ -48,19 +48,14 @@ module Gitlab # Example Request: # POST /projects/:id/issues post ":id/issues" do - @issue = user_project.issues.new( - title: params[:title], - description: params[:description], - assignee_id: params[:assignee_id], - milestone_id: params[:milestone_id], - label_list: params[:labels] - ) + attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id] + attrs[:label_list] = params[:labels] if params[:labels].present? + @issue = user_project.issues.new attrs @issue.author = current_user - if @issue.save present @issue, with: Entities::Issue else - error!({'message' => '404 Not found'}, 404) + not_found! end end @@ -79,23 +74,18 @@ module Gitlab # PUT /projects/:id/issues/:issue_id put ":id/issues/:issue_id" do @issue = user_project.issues.find(params[:issue_id]) - parameters = { - title: (params[:title] || @issue.title), - description: (params[:description] || @issue.description), - assignee_id: (params[:assignee_id] || @issue.assignee_id), - milestone_id: (params[:milestone_id] || @issue.milestone_id), - label_list: (params[:labels] || @issue.label_list), - closed: (params[:closed] || @issue.closed) - } + authorize! :modify_issue, @issue - if @issue.update_attributes(parameters) + attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :closed] + attrs[:label_list] = params[:labels] if params[:labels].present? + if @issue.update_attributes attrs present @issue, with: Entities::Issue else - error!({'message' => '404 Not found'}, 404) + not_found! end end - # Delete a project issue + # Delete a project issue (deprecated) # # Parameters: # id (required) - The ID or code name of a project @@ -103,8 +93,7 @@ module Gitlab # Example Request: # DELETE /projects/:id/issues/:issue_id delete ":id/issues/:issue_id" do - @issue = user_project.issues.find(params[:issue_id]) - @issue.destroy + not_allowed! end end end diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb new file mode 100644 index 00000000..f55dfd04 --- /dev/null +++ b/lib/api/milestones.rb @@ -0,0 +1,78 @@ +module Gitlab + # Milestones API + class Milestones < Grape::API + before { authenticate! } + + resource :projects do + # Get a list of project milestones + # + # Parameters: + # id (required) - The ID or code name of a project + # Example Request: + # GET /projects/:id/milestones + get ":id/milestones" do + authorize! :read_milestone, user_project + + present paginate(user_project.milestones), with: Entities::Milestone + end + + # Get a single project milestone + # + # Parameters: + # id (required) - The ID or code name of a project + # milestone_id (required) - The ID of a project milestone + # Example Request: + # GET /projects/:id/milestones/:milestone_id + get ":id/milestones/:milestone_id" do + authorize! :read_milestone, user_project + + @milestone = user_project.milestones.find(params[:milestone_id]) + present @milestone, with: Entities::Milestone + end + + # Create a new project milestone + # + # Parameters: + # id (required) - The ID or code name of the project + # title (required) - The title of the milestone + # description (optional) - The description of the milestone + # due_date (optional) - The due date of the milestone + # Example Request: + # POST /projects/:id/milestones + post ":id/milestones" do + authorize! :admin_milestone, user_project + + attrs = attributes_for_keys [:title, :description, :due_date] + @milestone = user_project.milestones.new attrs + if @milestone.save + present @milestone, with: Entities::Milestone + else + not_found! + end + end + + # Update an existing project milestone + # + # Parameters: + # id (required) - The ID or code name of a project + # milestone_id (required) - The ID of a project milestone + # title (optional) - The title of a milestone + # description (optional) - The description of a milestone + # due_date (optional) - The due date of a milestone + # closed (optional) - The status of the milestone + # Example Request: + # PUT /projects/:id/milestones/:milestone_id + put ":id/milestones/:milestone_id" do + authorize! :admin_milestone, user_project + + @milestone = user_project.milestones.find(params[:milestone_id]) + attrs = attributes_for_keys [:title, :description, :due_date, :closed] + if @milestone.update_attributes attrs + present @milestone, with: Entities::Milestone + else + not_found! + end + end + end + end +end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index eb23641c..c3dc3da6 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -9,7 +9,7 @@ module Gitlab # Example Request: # GET /projects get do - @projects = current_user.projects + @projects = paginate current_user.projects present @projects, with: Entities::Project end @@ -23,6 +23,162 @@ module Gitlab present user_project, with: Entities::Project end + # Create new project + # + # Parameters: + # name (required) - name for new project + # code (optional) - code for new project, uses project name if not set + # path (optional) - path for new project, uses project name if not set + # description (optional) - short project description + # default_branch (optional) - 'master' by default + # issues_enabled (optional) - enabled by default + # wall_enabled (optional) - enabled by default + # merge_requests_enabled (optional) - enabled by default + # wiki_enabled (optional) - enabled by default + # Example Request + # POST /projects + post do + params[:code] ||= params[:name] + params[:path] ||= params[:name] + attrs = attributes_for_keys [:code, + :path, + :name, + :description, + :default_branch, + :issues_enabled, + :wall_enabled, + :merge_requests_enabled, + :wiki_enabled] + @project = Project.create_by_user(attrs, current_user) + if @project.saved? + present @project, with: Entities::Project + else + not_found! + end + end + + # Get a project team members + # + # Parameters: + # id (required) - The ID or code name of a project + # Example Request: + # GET /projects/:id/members + get ":id/members" do + @members = paginate user_project.users + present @members, with: Entities::ProjectMember, project: user_project + end + + # Get a project team members + # + # Parameters: + # id (required) - The ID or code name of a project + # user_id (required) - The ID of a user + # Example Request: + # GET /projects/:id/members/:user_id + get ":id/members/:user_id" do + @member = user_project.users.find params[:user_id] + present @member, with: Entities::ProjectMember, project: user_project + end + + # Add a new project team member + # + # Parameters: + # id (required) - The ID or code name of a project + # user_id (required) - The ID of a user + # access_level (required) - Project access level + # Example Request: + # POST /projects/:id/members + post ":id/members" do + authorize! :admin_project, user_project + users_project = user_project.users_projects.new( + user_id: params[:user_id], + project_access: params[:access_level] + ) + + if users_project.save + @member = users_project.user + present @member, with: Entities::ProjectMember, project: user_project + else + not_found! + end + end + + # Update project team member + # + # Parameters: + # id (required) - The ID or code name of a project + # user_id (required) - The ID of a team member + # access_level (required) - Project access level + # Example Request: + # PUT /projects/:id/members/:user_id + put ":id/members/:user_id" do + authorize! :admin_project, user_project + users_project = user_project.users_projects.find_by_user_id params[:user_id] + + if users_project.update_attributes(project_access: params[:access_level]) + @member = users_project.user + present @member, with: Entities::ProjectMember, project: user_project + else + not_found! + end + end + + # Remove a team member from project + # + # Parameters: + # id (required) - The ID or code name of a project + # user_id (required) - The ID of a team member + # Example Request: + # DELETE /projects/:id/members/:user_id + delete ":id/members/:user_id" do + authorize! :admin_project, user_project + users_project = user_project.users_projects.find_by_user_id params[:user_id] + users_project.destroy + end + + # Get project hooks + # + # Parameters: + # id (required) - The ID or code name of a project + # Example Request: + # GET /projects/:id/hooks + get ":id/hooks" do + authorize! :admin_project, user_project + @hooks = paginate user_project.hooks + present @hooks, with: Entities::Hook + end + + # Add hook to project + # + # Parameters: + # id (required) - The ID or code name of a project + # url (required) - The hook URL + # Example Request: + # POST /projects/:id/hooks + post ":id/hooks" do + authorize! :admin_project, user_project + @hook = user_project.hooks.new({"url" => params[:url]}) + if @hook.save + present @hook, with: Entities::Hook + else + error!({'message' => '404 Not found'}, 404) + end + end + + # Delete project hook + # + # Parameters: + # id (required) - The ID or code name of a project + # hook_id (required) - The ID of hook to delete + # Example Request: + # DELETE /projects/:id/hooks + delete ":id/hooks" do + authorize! :admin_project, user_project + @hook = user_project.hooks.find(params[:hook_id]) + @hook.destroy + nil + end + # Get a project repository branches # # Parameters: @@ -55,6 +211,24 @@ module Gitlab present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject end + # Get a project repository commits + # + # Parameters: + # id (required) - The ID or code name of a project + # ref_name (optional) - The name of a repository branch or tag + # Example Request: + # GET /projects/:id/repository/commits + get ":id/repository/commits" do + authorize! :download_code, user_project + + page = params[:page] || 0 + per_page = params[:per_page] || 20 + ref = params[:ref_name] || user_project.try(:default_branch) || 'master' + + commits = user_project.commits(ref, nil, per_page, page * per_page) + present CommitDecorator.decorate(commits), with: Entities::RepoCommit + end + # Get a project snippet # # Parameters: @@ -78,18 +252,18 @@ module Gitlab # Example Request: # POST /projects/:id/snippets post ":id/snippets" do - @snippet = user_project.snippets.new( - title: params[:title], - file_name: params[:file_name], - expires_at: params[:lifetime], - content: params[:code] - ) + authorize! :write_snippet, user_project + + attrs = attributes_for_keys [:title, :file_name] + attrs[:expires_at] = params[:lifetime] if params[:lifetime].present? + attrs[:content] = params[:code] if params[:code].present? + @snippet = user_project.snippets.new attrs @snippet.author = current_user if @snippet.save present @snippet, with: Entities::ProjectSnippet else - error!({'message' => '404 Not found'}, 404) + not_found! end end @@ -106,17 +280,16 @@ module Gitlab # PUT /projects/:id/snippets/:snippet_id put ":id/snippets/:snippet_id" do @snippet = user_project.snippets.find(params[:snippet_id]) - parameters = { - title: (params[:title] || @snippet.title), - file_name: (params[:file_name] || @snippet.file_name), - expires_at: (params[:lifetime] || @snippet.expires_at), - content: (params[:code] || @snippet.content) - } + authorize! :modify_snippet, @snippet - if @snippet.update_attributes(parameters) + attrs = attributes_for_keys [:title, :file_name] + attrs[:expires_at] = params[:lifetime] if params[:lifetime].present? + attrs[:content] = params[:code] if params[:code].present? + + if @snippet.update_attributes attrs present @snippet, with: Entities::ProjectSnippet else - error!({'message' => '404 Not found'}, 404) + not_found! end end @@ -129,6 +302,8 @@ module Gitlab # DELETE /projects/:id/snippets/:snippet_id delete ":id/snippets/:snippet_id" do @snippet = user_project.snippets.find(params[:snippet_id]) + authorize! :modify_snippet, @snippet + @snippet.destroy end @@ -154,13 +329,15 @@ module Gitlab # Example Request: # GET /projects/:id/repository/commits/:sha/blob get ":id/repository/commits/:sha/blob" do + authorize! :download_code, user_project + ref = params[:sha] commit = user_project.commit ref - error!('404 Commit Not Found', 404) unless commit + not_found! "Commit" unless commit tree = Tree.new commit.tree, user_project, ref, params[:filepath] - error!('404 File Not Found', 404) unless tree.try(:tree) + not_found! "File" unless tree.try(:tree) if tree.text? encoding = Gitlab::Encode.detect_encoding(tree.data) diff --git a/lib/api/session.rb b/lib/api/session.rb new file mode 100644 index 00000000..b4050160 --- /dev/null +++ b/lib/api/session.rb @@ -0,0 +1,20 @@ +module Gitlab + # Users API + class Session < Grape::API + # Login to get token + # + # Example Request: + # POST /session + post "/session" do + resource = User.find_for_database_authentication(email: params[:email]) + + return unauthorized! unless resource + + if resource.valid_password?(params[:password]) + present resource, with: Entities::UserLogin + else + unauthorized! + end + end + end +end diff --git a/lib/api/users.rb b/lib/api/users.rb index 81cb2a0e..0ca8fb2a 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -9,7 +9,7 @@ module Gitlab # Example Request: # GET /users get do - @users = User.all + @users = paginate User present @users, with: Entities::User end @@ -25,12 +25,59 @@ module Gitlab end end - # Get currently authenticated user - # - # Example Request: - # GET /user - get "/user" do - present @current_user, with: Entities::User + resource :user do + # Get currently authenticated user + # + # Example Request: + # GET /user + get do + present @current_user, with: Entities::User + end + + # Get currently authenticated user's keys + # + # Example Request: + # GET /user/keys + get "keys" do + present current_user.keys, with: Entities::SSHKey + end + + # Get single key owned by currently authenticated user + # + # Example Request: + # GET /user/keys/:id + get "keys/:id" do + key = current_user.keys.find params[:id] + present key, with: Entities::SSHKey + end + + # Add new ssh key to currently authenticated user + # + # Parameters: + # key (required) - New SSH Key + # title (required) - New SSH Key's title + # Example Request: + # POST /user/keys + post "keys" do + attrs = attributes_for_keys [:title, :key] + key = current_user.keys.new attrs + if key.save + present key, with: Entities::SSHKey + else + not_found! + end + end + + # Delete existed ssh key of currently authenticated user + # + # Parameters: + # id (required) - SSH Key ID + # Example Request: + # DELETE /user/keys/:id + delete "keys/:id" do + key = current_user.keys.find params[:id] + key.delete + end end end end diff --git a/lib/gitlab/app_logger.rb b/lib/gitlab/app_logger.rb new file mode 100644 index 00000000..8e4717b4 --- /dev/null +++ b/lib/gitlab/app_logger.rb @@ -0,0 +1,11 @@ +module Gitlab + class AppLogger < Gitlab::Logger + def self.file_name + 'application.log' + end + + def format_message(severity, timestamp, progname, msg) + "#{timestamp.to_s(:long)}: #{msg}\n" + end + end +end diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb new file mode 100644 index 00000000..90bd5d74 --- /dev/null +++ b/lib/gitlab/auth.rb @@ -0,0 +1,66 @@ +module Gitlab + class Auth + def find_for_ldap_auth(auth, signed_in_resource = nil) + uid = auth.info.uid + provider = auth.provider + email = auth.info.email.downcase unless auth.info.email.nil? + raise OmniAuth::Error, "LDAP accounts must provide an uid and email address" if uid.nil? or email.nil? + + if @user = User.find_by_extern_uid_and_provider(uid, provider) + @user + elsif @user = User.find_by_email(email) + log.info "Updating legacy LDAP user #{email} with extern_uid => #{uid}" + @user.update_attributes(:extern_uid => uid, :provider => provider) + @user + else + create_from_omniauth(auth, true) + end + end + + def create_from_omniauth(auth, ldap = false) + provider = auth.provider + uid = auth.info.uid || auth.uid + name = auth.info.name.force_encoding("utf-8") + email = auth.info.email.downcase unless auth.info.email.nil? + + ldap_prefix = ldap ? '(LDAP) ' : '' + raise OmniAuth::Error, "#{ldap_prefix}#{provider} does not provide an email"\ + " address" if auth.info.email.blank? + + log.info "#{ldap_prefix}Creating user from #{provider} login"\ + " {uid => #{uid}, name => #{name}, email => #{email}}" + password = Devise.friendly_token[0, 8].downcase + @user = User.new( + extern_uid: uid, + provider: provider, + name: name, + email: email, + password: password, + password_confirmation: password, + projects_limit: Gitlab.config.default_projects_limit, + ) + if Gitlab.config.omniauth['block_auto_created_users'] && !ldap + @user.blocked = true + end + @user.save! + @user + end + + def find_or_new_for_omniauth(auth) + provider, uid = auth.provider, auth.uid + + if @user = User.find_by_provider_and_extern_uid(provider, uid) + @user + else + if Gitlab.config.omniauth['allow_single_sign_on'] + @user = create_from_omniauth(auth) + @user + end + end + end + + def log + Gitlab::AppLogger + end + end +end diff --git a/lib/gitlab/backend/gitolite.rb b/lib/gitlab/backend/gitolite.rb new file mode 100644 index 00000000..fe5dcef4 --- /dev/null +++ b/lib/gitlab/backend/gitolite.rb @@ -0,0 +1,43 @@ +require_relative 'gitolite_config' + +module Gitlab + class Gitolite + class AccessDenied < StandardError; end + + def config + Gitlab::GitoliteConfig.new + end + + def set_key key_id, key_content, projects + config.apply do |config| + config.write_key(key_id, key_content) + config.update_projects(projects) + end + end + + def remove_key key_id, projects + config.apply do |config| + config.rm_key(key_id) + config.update_projects(projects) + end + end + + def update_repository project + config.update_project!(project.path, project) + end + + def remove_repository project + config.destroy_project!(project) + end + + def url_to_repo path + Gitlab.config.ssh_path + "#{path}.git" + end + + def enable_automerge + config.admin_all_repo! + end + + alias_method :create_repository, :update_repository + end +end diff --git a/lib/gitlab/backend/gitolite_config.rb b/lib/gitlab/backend/gitolite_config.rb new file mode 100644 index 00000000..f51e8efc --- /dev/null +++ b/lib/gitlab/backend/gitolite_config.rb @@ -0,0 +1,196 @@ +require 'gitolite' +require 'timeout' +require 'fileutils' + +module Gitlab + class GitoliteConfig + class PullError < StandardError; end + class PushError < StandardError; end + + attr_reader :config_tmp_dir, :ga_repo, :conf + + def config_tmp_dir + @config_tmp_dir ||= File.join(Rails.root, 'tmp',"gitlabhq-gitolite-#{Time.now.to_i}") + end + + def ga_repo + @ga_repo ||= ::Gitolite::GitoliteAdmin.new(File.join(config_tmp_dir,'gitolite')) + end + + def apply + Timeout::timeout(30) do + File.open(File.join(Rails.root, 'tmp', "gitlabhq-gitolite.lock"), "w+") do |f| + begin + # Set exclusive lock + # to prevent race condition + f.flock(File::LOCK_EX) + + # Pull gitolite-admin repo + # in tmp dir before do any changes + pull(config_tmp_dir) + + # Build ga_repo object and @conf + # to access gitolite-admin configuration + @conf = ga_repo.config + + # Do any changes + # in gitolite-admin + # config here + yield(self) + + # Save changes in + # gitolite-admin repo + # before pusht it + ga_repo.save + + # Push gitolite-admin repo + # to apply all changes + push(config_tmp_dir) + + # Remove tmp dir + # wiith gitolite-admin + FileUtils.rm_rf(config_tmp_dir) + ensure + # unlock so other task cann access + # gitolite configuration + f.flock(File::LOCK_UN) + end + end + end + rescue PullError => ex + log("Pull error -> " + ex.message) + raise Gitolite::AccessDenied, ex.message + + rescue PushError => ex + log("Push error -> " + " " + ex.message) + raise Gitolite::AccessDenied, ex.message + + rescue Exception => ex + log(ex.class.name + " " + ex.message) + raise Gitolite::AccessDenied.new("gitolite timeout") + end + + def log message + Gitlab::GitLogger.error(message) + end + + def destroy_project(project) + FileUtils.rm_rf(project.path_to_repo) + conf.rm_repo(project.path) + end + + def destroy_project!(project) + apply do |config| + config.destroy_project(project) + end + end + + def write_key(id, key) + File.open(File.join(config_tmp_dir, 'gitolite/keydir',"#{id}.pub"), 'w') do |f| + f.write(key.gsub(/\n/,'')) + end + end + + def rm_key(user) + File.unlink(File.join(config_tmp_dir, 'gitolite/keydir',"#{user}.pub")) + `cd #{File.join(config_tmp_dir,'gitolite')} ; git rm keydir/#{user}.pub` + end + + # update or create + def update_project(repo_name, project) + repo = update_project_config(project, conf) + conf.add_repo(repo, true) + end + + def update_project!(repo_name, project) + apply do |config| + config.update_project(repo_name, project) + end + end + + # Updates many projects and uses project.path as the repo path + # An order of magnitude faster than update_project + def update_projects(projects) + projects.each do |project| + repo = update_project_config(project, conf) + conf.add_repo(repo, true) + end + end + + def update_project_config(project, conf) + repo_name = project.path + + repo = if conf.has_repo?(repo_name) + conf.get_repo(repo_name) + else + ::Gitolite::Config::Repo.new(repo_name) + end + + name_readers = project.repository_readers + name_writers = project.repository_writers + name_masters = project.repository_masters + + pr_br = project.protected_branches.map(&:name).join("$ ") + + repo.clean_permissions + + # Deny access to protected branches for writers + unless name_writers.blank? || pr_br.blank? + repo.add_permission("-", pr_br.strip + "$ ", name_writers) + end + + # Add read permissions + repo.add_permission("R", "", name_readers) unless name_readers.blank? + + # Add write permissions + repo.add_permission("RW+", "", name_writers) unless name_writers.blank? + repo.add_permission("RW+", "", name_masters) unless name_masters.blank? + + repo + end + + # Enable access to all repos for gitolite admin. + # We use it for accept merge request feature + def admin_all_repo + owner_name = Gitlab.config.gitolite_admin_key + + # @ALL repos premission for gitolite owner + repo_name = "@all" + repo = if conf.has_repo?(repo_name) + conf.get_repo(repo_name) + else + ::Gitolite::Config::Repo.new(repo_name) + end + + repo.add_permission("RW+", "", owner_name) + conf.add_repo(repo, true) + end + + def admin_all_repo! + apply { |config| config.admin_all_repo } + end + + private + + def pull tmp_dir + Dir.mkdir tmp_dir + `git clone #{Gitlab.config.gitolite_admin_uri} #{tmp_dir}/gitolite` + + unless File.exists?(File.join(tmp_dir, 'gitolite', 'conf', 'gitolite.conf')) + raise PullError, "unable to clone gitolite-admin repo" + end + end + + def push tmp_dir + Dir.chdir(File.join(tmp_dir, "gitolite")) + system('git add -A') + system('git commit -am "GitLab"') + if system('git push') + Dir.chdir(Rails.root) + else + raise PushError, "unable to push gitolite-admin repo" + end + end + end +end + diff --git a/config/initializers/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb similarity index 64% rename from config/initializers/grack_auth.rb rename to lib/gitlab/backend/grack_auth.rb index 5995b873..43a75cc3 100644 --- a/config/initializers/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -12,21 +12,22 @@ module Grack # Pass Gitolite update hook ENV['GL_BYPASS_UPDATE_HOOK'] = "true" - # Need this patch because the rails mount - @env['PATH_INFO'] = @env['REQUEST_PATH'] + # Need this patch due to the rails mount + @env['PATH_INFO'] = @request.path + @env['SCRIPT_NAME'] = "" # Find project by PATH_INFO from env - if m = /^\/([\w-]+).git/.match(@env['PATH_INFO']).to_a + if m = /^\/([\w-]+).git/.match(@request.path_info).to_a return false unless project = Project.find_by_path(m.last) end # Git upload and receive - if @env['REQUEST_METHOD'] == 'GET' + if @request.get? true - elsif @env['REQUEST_METHOD'] == 'POST' - if @env['REQUEST_URI'].end_with?('git-upload-pack') + elsif @request.post? + if @request.path_info.end_with?('git-upload-pack') return project.dev_access_for?(user) - elsif @env['REQUEST_URI'].end_with?('git-receive-pack') + elsif @request.path_info.end_with?('git-receive-pack') if project.protected_branches.map(&:name).include?(current_ref) project.master_access_for?(user) else @@ -42,13 +43,13 @@ module Grack def current_ref if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/ - input = Zlib::GzipReader.new(@request.body).string + input = Zlib::GzipReader.new(@request.body).read else - input = @request.body.string + input = @request.body.read end - - oldrev, newrev, ref = input.split(' ') - /refs\/heads\/([\w-]+)/.match(ref).to_a.last + # Need to reset seek point + @request.body.rewind + /refs\/heads\/([\w-]+)/.match(input).to_a.first end end# Auth end# Grack diff --git a/lib/gitlab/git_host.rb b/lib/gitlab/git_host.rb deleted file mode 100644 index 76b2c7b1..00000000 --- a/lib/gitlab/git_host.rb +++ /dev/null @@ -1,17 +0,0 @@ -require File.join(Rails.root, "lib", "gitlab", "gitolite") - -module Gitlab - class GitHost - def self.system - Gitlab::Gitolite - end - - def self.admin_uri - Gitlab.config.git_host.admin_uri - end - - def self.url_to_repo(path) - Gitlab.config.ssh_path + "#{path}.git" - end - end -end diff --git a/lib/gitlab/git_logger.rb b/lib/gitlab/git_logger.rb new file mode 100644 index 00000000..fbfed205 --- /dev/null +++ b/lib/gitlab/git_logger.rb @@ -0,0 +1,11 @@ +module Gitlab + class GitLogger < Gitlab::Logger + def self.file_name + 'githost.log' + end + + def format_message(severity, timestamp, progname, msg) + "#{timestamp.to_s(:long)} -> #{severity} -> #{msg}\n" + end + end +end diff --git a/lib/gitlab/gitolite.rb b/lib/gitlab/gitolite.rb deleted file mode 100644 index e82f9e62..00000000 --- a/lib/gitlab/gitolite.rb +++ /dev/null @@ -1,157 +0,0 @@ -require 'gitolite' -require 'timeout' -require 'fileutils' - -module Gitlab - class Gitolite - class AccessDenied < StandardError; end - - def self.update_project(path, project) - self.new.configure { |git| git.update_project(path, project) } - end - - def self.destroy_project(project) - self.new.configure { |git| git.destroy_project(project) } - end - - def pull - # create tmp dir - @local_dir = File.join(Rails.root, 'tmp',"gitlabhq-gitolite-#{Time.now.to_i}") - Dir.mkdir @local_dir - - `git clone #{GitHost.admin_uri} #{@local_dir}/gitolite` - end - - def push - Dir.chdir(File.join(@local_dir, "gitolite")) - `git add -A` - `git commit -am "Gitlab"` - `git push` - Dir.chdir(Rails.root) - - FileUtils.rm_rf(@local_dir) - end - - def configure - Timeout::timeout(30) do - File.open(File.join(Rails.root, 'tmp', "gitlabhq-gitolite.lock"), "w+") do |f| - begin - f.flock(File::LOCK_EX) - pull - yield(self) - push - ensure - f.flock(File::LOCK_UN) - end - end - end - rescue Exception => ex - Gitlab::Logger.error(ex.message) - raise Gitolite::AccessDenied.new("gitolite timeout") - end - - def destroy_project(project) - FileUtils.rm_rf(project.path_to_repo) - - ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite')) - conf = ga_repo.config - conf.rm_repo(project.path) - ga_repo.save - end - - #update or create - def update_keys(user, key) - File.open(File.join(@local_dir, 'gitolite/keydir',"#{user}.pub"), 'w') {|f| f.write(key.gsub(/\n/,'')) } - end - - def delete_key(user) - File.unlink(File.join(@local_dir, 'gitolite/keydir',"#{user}.pub")) - `cd #{File.join(@local_dir,'gitolite')} ; git rm keydir/#{user}.pub` - end - - # update or create - def update_project(repo_name, project) - ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite')) - conf = ga_repo.config - repo = update_project_config(project, conf) - conf.add_repo(repo, true) - - ga_repo.save - end - - # Updates many projects and uses project.path as the repo path - # An order of magnitude faster than update_project - def update_projects(projects) - ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite')) - conf = ga_repo.config - - projects.each do |project| - repo = update_project_config(project, conf) - conf.add_repo(repo, true) - end - - ga_repo.save - end - - def update_project_config(project, conf) - repo_name = project.path - - repo = if conf.has_repo?(repo_name) - conf.get_repo(repo_name) - else - ::Gitolite::Config::Repo.new(repo_name) - end - - name_readers = project.repository_readers - name_writers = project.repository_writers - name_masters = project.repository_masters - - pr_br = project.protected_branches.map(&:name).join(" ") - - repo.clean_permissions - - # Deny access to protected branches for writers - unless name_writers.blank? || pr_br.blank? - repo.add_permission("-", pr_br, name_writers) - end - - # Add read permissions - repo.add_permission("R", "", name_readers) unless name_readers.blank? - - # Add write permissions - repo.add_permission("RW+", "", name_writers) unless name_writers.blank? - repo.add_permission("RW+", "", name_masters) unless name_masters.blank? - - repo - end - - def admin_all_repo - ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite')) - conf = ga_repo.config - owner_name = "" - - # Read gitolite-admin user - # - begin - repo = conf.get_repo("gitolite-admin") - owner_name = repo.permissions[0]["RW+"][""][0] - raise StandardError if owner_name.blank? - rescue => ex - puts "Can't determine gitolite-admin owner".red - raise StandardError - end - - # @ALL repos premission for gitolite owner - repo_name = "@all" - repo = if conf.has_repo?(repo_name) - conf.get_repo(repo_name) - else - ::Gitolite::Config::Repo.new(repo_name) - end - - repo.add_permission("RW+", "", owner_name) - conf.add_repo(repo, true) - ga_repo.save - end - end -end diff --git a/lib/gitlab/graph_commit.rb b/lib/gitlab/graph_commit.rb index b9859d79..d3668a99 100644 --- a/lib/gitlab/graph_commit.rb +++ b/lib/gitlab/graph_commit.rb @@ -5,6 +5,8 @@ module Gitlab attr_accessor :time, :space attr_accessor :refs + include ActionView::Helpers::TagHelper + def self.to_graph(project) @repo = project.repo commits = Grit::Commit.find_all(@repo, nil, {max_count: 650}) @@ -164,7 +166,7 @@ module Gitlab h[:refs] = refs.collect{|r|r.name}.join(" ") unless refs.nil? h[:id] = sha h[:date] = date - h[:message] = Gitlab::Encode.utf8(message) + h[:message] = escape_once(Gitlab::Encode.utf8(message)) h[:login] = author.email h end diff --git a/lib/gitlab/logger.rb b/lib/gitlab/logger.rb index c3a19e71..9405163d 100644 --- a/lib/gitlab/logger.rb +++ b/lib/gitlab/logger.rb @@ -9,17 +9,13 @@ module Gitlab end def self.read_latest - path = Rails.root.join("log/githost.log") + path = Rails.root.join("log", file_name) self.build unless File.exist?(path) logs = File.read(path).split("\n") end def self.build - new(File.join(Rails.root, "log/githost.log")) + new(File.join(Rails.root, "log", file_name)) end - - def format_message(severity, timestamp, progname, msg) - "#{timestamp.to_s(:long)} -> #{severity} -> #{msg}\n" - end end end diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index 75fa835d..9201003e 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -1,7 +1,8 @@ module Gitlab - # Custom parser for Gitlab-flavored Markdown + # Custom parser for GitLab-flavored Markdown # - # It replaces references in the text with links to the appropriate items in Gitlab. + # It replaces references in the text with links to the appropriate items in + # GitLab. # # Supported reference formats are: # * @foo for team members @@ -10,44 +11,99 @@ module Gitlab # * $123 for snippets # * 123456 for commits # + # It also parses Emoji codes to insert images. See + # http://www.emoji-cheat-sheet.com/ for a list of the supported icons. + # # Examples # - # >> m = Markdown.new(...) - # - # >> m.parse("Hey @david, can you fix this?") + # >> gfm("Hey @david, can you fix this?") # => "Hey @david, can you fix this?" # - # >> m.parse("Commit 35d5f7c closes #1234") + # >> gfm("Commit 35d5f7c closes #1234") # => "Commit 35d5f7c closes #1234" - class Markdown - include Rails.application.routes.url_helpers - include ActionView::Helpers - + # + # >> gfm(":trollface:") + # => "\":trollface:\" + module Markdown REFERENCE_PATTERN = %r{ - ([^\w&;])? # Prefix (1) + (\W)? # Prefix (1) ( # Reference (2) @([\w\._]+) # User name (3) |[#!$](\d+) # Issue/MR/Snippet ID (4) |([\h]{6,40}) # Commit ID (5) ) - ([^\w&;])? # Suffix (6) + (\W)? # Suffix (6) }x.freeze + EMOJI_PATTERN = %r{(:(\S+):)}.freeze + attr_reader :html_options - def initialize(project, html_options = {}) - @project = project + # Public: Parse the provided text with GitLab-Flavored Markdown + # + # text - the source text + # html_options - extra options for the reference links as given to link_to + # + # Note: reference links will only be generated if @project is set + def gfm(text, html_options = {}) + return text if text.nil? + + # Duplicate the string so we don't alter the original, then call to_str + # to cast it back to a String instead of a SafeBuffer. This is required + # for gsub calls to work as we need them to. + text = text.dup.to_str + @html_options = html_options + + # Extract pre blocks so they are not altered + # from http://github.github.com/github-flavored-markdown/ + extractions = {} + text.gsub!(%r{
.*?
|.*?}m) do |match| + md5 = Digest::MD5.hexdigest(match) + extractions[md5] = match + "{gfm-extraction-#{md5}}" + end + + # TODO: add popups with additional information + + text = parse(text) + + # Insert pre block extractions + text.gsub!(/\{gfm-extraction-(\h{32})\}/) do + extractions[$1] + end + + sanitize text.html_safe, attributes: ActionView::Base.sanitized_allowed_attributes + %w(id class) end + private + + # Private: Parses text for references and emoji + # + # text - Text to parse + # + # Note: reference links will only be generated if @project is set + # + # Returns parsed text def parse(text) - text.gsub(REFERENCE_PATTERN) do |match| + parse_references(text) if @project + parse_emoji(text) + + text + end + + def parse_references(text) + # parse reference links + text.gsub!(REFERENCE_PATTERN) do |match| prefix = $1 || '' reference = $2 identifier = $3 || $4 || $5 suffix = $6 || '' - if ref_link = reference_link(reference, identifier) + # Avoid HTML entities + if prefix.ends_with?('&') || suffix.starts_with?(';') + match + elsif ref_link = reference_link(reference, identifier) prefix + ref_link + suffix else match @@ -55,7 +111,25 @@ module Gitlab end end - private + def parse_emoji(text) + # parse emoji + text.gsub!(EMOJI_PATTERN) do |match| + if valid_emoji?($2) + image_tag("emoji/#{$2}.png", size: "20x20", class: 'emoji', title: $1, alt: $1) + else + match + end + end + end + + # Private: Checks if an emoji icon exists in the image asset directory + # + # emoji - Identifier of the emoji as a string (e.g., "+1", "heart") + # + # Returns boolean + def valid_emoji?(emoji) + File.exists?(Rails.root.join('app', 'assets', 'images', 'emoji', "#{emoji}.png")) + end # Private: Dispatches to a dedicated processing method based on reference # @@ -100,7 +174,7 @@ module Gitlab def reference_commit(identifier) if commit = @project.commit(identifier) - link_to(identifier, project_commit_path(@project, id: commit.id), html_options.merge(title: "Commit: #{commit.author_name} - #{CommitDecorator.new(commit).title}", class: "gfm gfm-commit #{html_options[:class]}")) + link_to(identifier, project_commit_path(@project, id: commit.id), html_options.merge(title: CommitDecorator.new(commit).link_title, class: "gfm gfm-commit #{html_options[:class]}")) end end end diff --git a/lib/gitlab/merge.rb b/lib/gitlab/merge.rb index 134695ce..18013574 100644 --- a/lib/gitlab/merge.rb +++ b/lib/gitlab/merge.rb @@ -21,8 +21,7 @@ module Gitlab if output =~ /CONFLICT/ false else - repo.git.push({}, "origin", merge_request.target_branch) - true + !!repo.git.push({}, "origin", merge_request.target_branch) end end end diff --git a/lib/hooks/post-receive b/lib/hooks/post-receive index d38bd13e..a4fa9f1c 100755 --- a/lib/hooks/post-receive +++ b/lib/hooks/post-receive @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# This file was placed here by Gitlab. It makes sure that your pushed commits +# This file was placed here by GitLab. It makes sure that your pushed commits # will be processed properly. while read oldrev newrev ref diff --git a/lib/tasks/bulk_add_permission.rake b/lib/tasks/bulk_add_permission.rake new file mode 100644 index 00000000..55797825 --- /dev/null +++ b/lib/tasks/bulk_add_permission.rake @@ -0,0 +1,26 @@ +desc "Add all users to all projects, system administratos are added as masters" +task :add_users_to_project_teams => :environment do |t, args| + users = User.find_all_by_admin(false, :select => 'id').map(&:id) + admins = User.find_all_by_admin(true, :select => 'id').map(&:id) + + users.each do |user| + puts "#{user}" + end + + Project.all.each do |project| + puts "Importing #{users.length} users into #{project.path}" + UsersProject.bulk_import(project, users, UsersProject::DEVELOPER) + puts "Importing #{admins.length} admins into #{project.path}" + UsersProject.bulk_import(project, admins, UsersProject::MASTER) + end +end + +desc "Add user to as a developer to all projects" +task :add_user_to_project_teams, [:email] => :environment do |t, args| + user_email = args.email + user = User.find_by_email(user_email) + + project_ids = Project.all.map(&:id) + + UsersProject.user_bulk_import(user,project_ids,UsersProject::DEVELOPER) +end diff --git a/lib/tasks/bulk_import.rake b/lib/tasks/bulk_import.rake index 607250f1..edb4a599 100644 --- a/lib/tasks/bulk_import.rake +++ b/lib/tasks/bulk_import.rake @@ -1,11 +1,10 @@ -IMPORT_DIRECTORY = 'import_projects' -REPOSITORY_DIRECTORY = '/home/git/repositories' -desc "Imports existing Git repos into new projects from the import_projects folder" -task :import_projects, [:email] => :environment do |t, args| +desc "Imports existing Git repos from a directory into new projects in git_base_path" +task :import_projects, [:directory,:email] => :environment do |t, args| user_email = args.email - repos_to_import = Dir.glob("#{IMPORT_DIRECTORY}/*") - + import_directory = args.directory + repos_to_import = Dir.glob("#{import_directory}/*") + git_base_path = Gitlab.config.git_base_path puts "Found #{repos_to_import.length} repos to import" imported_count = 0 @@ -13,11 +12,9 @@ task :import_projects, [:email] => :environment do |t, args| failed_count = 0 repos_to_import.each do |repo_path| repo_name = File.basename repo_path - repo_full_path = File.join(Rails.root, repo_path) puts " Processing #{repo_name}" - - clone_path = "#{REPOSITORY_DIRECTORY}/#{repo_name}.git" + clone_path = "#{git_base_path}#{repo_name}.git" if Dir.exists? clone_path if Project.find_by_code(repo_name) @@ -29,7 +26,7 @@ task :import_projects, [:email] => :environment do |t, args| end else # Clone the repo - unless clone_bare_repo_as_git(repo_full_path, clone_path) + unless clone_bare_repo_as_git(repo_path, clone_path) failed_count += 1 next end @@ -47,14 +44,17 @@ task :import_projects, [:email] => :environment do |t, args| puts "Finished importing #{imported_count} projects (skipped #{skipped_count}, failed #{failed_count})." end -# Clones a repo as bare git repo using the git user +# Clones a repo as bare git repo using the git_user def clone_bare_repo_as_git(existing_path, new_path) + git_user = Gitlab.config.ssh_user begin - sh "sudo -u git -i git clone --bare '#{existing_path}' #{new_path}" + sh "sudo -u #{git_user} -i git clone --bare '#{existing_path}' #{new_path}" true - rescue + rescue Exception=> msg puts " ERROR: Faild to clone #{existing_path} to #{new_path}" - false + puts " Make sure #{git_user} can reach #{existing_path}" + puts " Exception-MSG: #{msg}" + false end end diff --git a/lib/tasks/cucumber.rake b/lib/tasks/cucumber.rake deleted file mode 100644 index 83f79471..00000000 --- a/lib/tasks/cucumber.rake +++ /dev/null @@ -1,65 +0,0 @@ -# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. -# It is recommended to regenerate this file in the future when you upgrade to a -# newer version of cucumber-rails. Consider adding your own code to a new file -# instead of editing this one. Cucumber will automatically load all features/**/*.rb -# files. - - -unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks - -vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first -$LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil? - -begin - require 'cucumber/rake/task' - - namespace :cucumber do - Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t| - t.binary = vendored_cucumber_bin # If nil, the gem's binary is used. - t.fork = true # You may get faster startup if you set this to false - t.profile = 'default' - end - - Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t| - t.binary = vendored_cucumber_bin - t.fork = true # You may get faster startup if you set this to false - t.profile = 'wip' - end - - Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t| - t.binary = vendored_cucumber_bin - t.fork = true # You may get faster startup if you set this to false - t.profile = 'rerun' - end - - desc 'Run all features' - task :all => [:ok, :wip] - - task :statsetup do - require 'rails/code_statistics' - ::STATS_DIRECTORIES << %w(Cucumber\ features features) if File.exist?('features') - ::CodeStatistics::TEST_TYPES << "Cucumber features" if File.exist?('features') - end - end - desc 'Alias for cucumber:ok' - task :cucumber => 'cucumber:ok' - - task :default => :cucumber - - task :features => :cucumber do - STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***" - end - - # In case we don't have ActiveRecord, append a no-op task that we can depend upon. - task 'db:test:prepare' do - end - - task :stats => 'cucumber:statsetup' -rescue LoadError - desc 'cucumber rake task not available (cucumber not installed)' - task :cucumber do - abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin' - end -end - -end diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake index d9053c23..04d240f6 100644 --- a/lib/tasks/gitlab/backup.rake +++ b/lib/tasks/gitlab/backup.rake @@ -144,8 +144,7 @@ namespace :gitlab do if Kernel.system("cd #{File.dirname(project.second)} > /dev/null 2>&1 && git clone --bare #{backup_path_repo}/#{project.first}.bundle #{project.first}.git > /dev/null 2>&1") permission_commands = [ "sudo chmod -R g+rwX #{Gitlab.config.git_base_path}", - "sudo chown -R #{Gitlab.config.ssh_user}:#{Gitlab.config.ssh_user} #{Gitlab.config.git_base_path}", - "sudo chown gitlab:gitlab /home/git/repositories/**/hooks/post-receive" + "sudo chown -R #{Gitlab.config.ssh_user}:#{Gitlab.config.ssh_user} #{Gitlab.config.git_base_path}" ] permission_commands.each { |command| Kernel.system(command) } puts "[DONE]".green diff --git a/lib/tasks/gitlab/enable_automerge.rake b/lib/tasks/gitlab/enable_automerge.rake index 07f80586..0a1a0fa7 100644 --- a/lib/tasks/gitlab/enable_automerge.rake +++ b/lib/tasks/gitlab/enable_automerge.rake @@ -2,9 +2,7 @@ namespace :gitlab do namespace :app do desc "GITLAB | Enable auto merge" task :enable_automerge => :environment do - Gitlab::GitHost.system.new.configure do |git| - git.admin_all_repo - end + Gitlab::Gitolite.new.enable_automerge Project.find_each do |project| if project.repo_exists? && !project.satellite.exists? diff --git a/lib/tasks/gitlab/gitolite_rebuild.rake b/lib/tasks/gitlab/gitolite_rebuild.rake index 5ab17606..534aa315 100644 --- a/lib/tasks/gitlab/gitolite_rebuild.rake +++ b/lib/tasks/gitlab/gitolite_rebuild.rake @@ -16,7 +16,7 @@ namespace :gitlab do task :update_keys => :environment do puts "Starting Key" Key.find_each(:batch_size => 100) do |key| - key.update_repository + Gitlab::Gitolite.new.set_key(key.identifier, key.key, key.projects) print '.' end puts "Done with keys" diff --git a/lib/tasks/gitlab/status.rake b/lib/tasks/gitlab/status.rake index 02d27d4b..e5b5e122 100644 --- a/lib/tasks/gitlab/status.rake +++ b/lib/tasks/gitlab/status.rake @@ -56,7 +56,7 @@ namespace :gitlab do return end - gitolite_hooks_path = File.join("/home", Gitlab.config.ssh_user, "share", "gitolite", "hooks", "common") + gitolite_hooks_path = File.join(Gitlab.config.git_hooks_path, "common") gitlab_hook_files = ['post-receive'] gitlab_hook_files.each do |file_name| dest = File.join(gitolite_hooks_path, file_name) @@ -81,7 +81,7 @@ namespace :gitlab do next end - puts "post-reveice file ok".green + puts "post-receive file ok".green end end diff --git a/lib/tasks/gitlab/test.rake b/lib/tasks/gitlab/test.rake index 77e148cf..ad1bfb2e 100644 --- a/lib/tasks/gitlab/test.rake +++ b/lib/tasks/gitlab/test.rake @@ -1,5 +1,4 @@ namespace :gitlab do - desc "GITLAB | Run both cucumber & rspec" - task :test => ['cucumber', 'spec'] + desc "GITLAB | Run both spinach and rspec" + task :test => ['spinach', 'spec'] end - diff --git a/lib/tasks/gitlab/write_hook.rake b/lib/tasks/gitlab/write_hook.rake index 098331b8..5e9fc8eb 100644 --- a/lib/tasks/gitlab/write_hook.rake +++ b/lib/tasks/gitlab/write_hook.rake @@ -1,8 +1,8 @@ namespace :gitlab do namespace :gitolite do - desc "GITLAB | Write GITLAB hook for gitolite" + desc "GITLAB | Write GitLab hook for gitolite" task :write_hooks => :environment do - gitolite_hooks_path = File.join("/home", Gitlab.config.ssh_user, "share", "gitolite", "hooks", "common") + gitolite_hooks_path = File.join(Gitlab.config.git_hooks_path, "common") gitlab_hooks_path = Rails.root.join("lib", "hooks") gitlab_hook_files = ['post-receive'] diff --git a/lib/tasks/travis.rake b/lib/tasks/travis.rake index 58767e10..13e32135 100644 --- a/lib/tasks/travis.rake +++ b/lib/tasks/travis.rake @@ -1,5 +1,5 @@ task :travis do - ["cucumber", "rspec spec"].each do |cmd| + ["spinach", "rspec spec"].each do |cmd| puts "Starting to run #{cmd}..." system("export DISPLAY=:99.0 && bundle exec #{cmd}") raise "#{cmd} failed!" unless $?.exitstatus == 0 diff --git a/resque_dev.sh b/resque_dev.sh index b09cfd9e..0f1d6edb 100755 --- a/resque_dev.sh +++ b/resque_dev.sh @@ -1 +1,2 @@ -bundle exec rake environment resque:work QUEUE=post_receive,mailer,system_hook VVERBOSE=1 +mkdir -p tmp/pids +bundle exec rake environment resque:work QUEUE=post_receive,mailer,system_hook VVERBOSE=1 PIDFILE=tmp/pids/resque_worker.pid RAILS_ENV=development BACKGROUND=yes diff --git a/script/cucumber b/script/cucumber deleted file mode 100755 index 7fa5c920..00000000 --- a/script/cucumber +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env ruby - -vendored_cucumber_bin = Dir["#{File.dirname(__FILE__)}/../vendor/{gems,plugins}/cucumber*/bin/cucumber"].first -if vendored_cucumber_bin - load File.expand_path(vendored_cucumber_bin) -else - require 'rubygems' unless ENV['NO_RUBYGEMS'] - require 'cucumber' - load Cucumber::BINARY -end diff --git a/spec/api/projects_spec.rb b/spec/api/projects_spec.rb deleted file mode 100644 index ff45619e..00000000 --- a/spec/api/projects_spec.rb +++ /dev/null @@ -1,136 +0,0 @@ -require 'spec_helper' - -describe Gitlab::API do - let(:user) { Factory :user } - let!(:project) { Factory :project, owner: user } - let!(:snippet) { Factory :snippet, author: user, project: project, title: 'example' } - before { project.add_access(user, :read) } - - describe "GET /projects" do - it "should return authentication error" do - get "#{api_prefix}/projects" - response.status.should == 401 - end - - describe "authenticated GET /projects" do - it "should return an array of projects" do - get "#{api_prefix}/projects?private_token=#{user.private_token}" - response.status.should == 200 - json_response.should be_an Array - json_response.first['name'].should == project.name - json_response.first['owner']['email'].should == user.email - end - end - end - - describe "GET /projects/:id" do - it "should return a project by id" do - get "#{api_prefix}/projects/#{project.id}?private_token=#{user.private_token}" - response.status.should == 200 - json_response['name'].should == project.name - json_response['owner']['email'].should == user.email - end - - it "should return a project by code name" do - get "#{api_prefix}/projects/#{project.code}?private_token=#{user.private_token}" - response.status.should == 200 - json_response['name'].should == project.name - end - - it "should return a 404 error if not found" do - get "#{api_prefix}/projects/42?private_token=#{user.private_token}" - response.status.should == 404 - json_response['message'].should == '404 Not found' - end - end - - describe "GET /projects/:id/repository/branches" do - it "should return an array of project branches" do - get "#{api_prefix}/projects/#{project.code}/repository/branches?private_token=#{user.private_token}" - response.status.should == 200 - json_response.should be_an Array - json_response.first['name'].should == project.repo.heads.sort_by(&:name).first.name - end - end - - describe "GET /projects/:id/repository/branches/:branch" do - it "should return the branch information for a single branch" do - get "#{api_prefix}/projects/#{project.code}/repository/branches/new_design?private_token=#{user.private_token}" - response.status.should == 200 - - json_response['name'].should == 'new_design' - json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1' - end - end - - describe "GET /projects/:id/repository/tags" do - it "should return an array of project tags" do - get "#{api_prefix}/projects/#{project.code}/repository/tags?private_token=#{user.private_token}" - response.status.should == 200 - json_response.should be_an Array - json_response.first['name'].should == project.repo.tags.sort_by(&:name).reverse.first.name - end - end - - describe "GET /projects/:id/snippets/:snippet_id" do - it "should return a project snippet" do - get "#{api_prefix}/projects/#{project.code}/snippets/#{snippet.id}?private_token=#{user.private_token}" - response.status.should == 200 - json_response['title'].should == snippet.title - end - end - - describe "POST /projects/:id/snippets" do - it "should create a new project snippet" do - post "#{api_prefix}/projects/#{project.code}/snippets?private_token=#{user.private_token}", - title: 'api test', file_name: 'sample.rb', code: 'test' - response.status.should == 201 - json_response['title'].should == 'api test' - end - end - - describe "PUT /projects/:id/snippets" do - it "should update an existing project snippet" do - put "#{api_prefix}/projects/#{project.code}/snippets/#{snippet.id}?private_token=#{user.private_token}", - code: 'updated code' - response.status.should == 200 - json_response['title'].should == 'example' - snippet.reload.content.should == 'updated code' - end - end - - describe "DELETE /projects/:id/snippets/:snippet_id" do - it "should delete existing project snippet" do - expect { - delete "#{api_prefix}/projects/#{project.code}/snippets/#{snippet.id}?private_token=#{user.private_token}" - }.to change { Snippet.count }.by(-1) - end - end - - describe "GET /projects/:id/snippets/:snippet_id/raw" do - it "should get a raw project snippet" do - get "#{api_prefix}/projects/#{project.code}/snippets/#{snippet.id}/raw?private_token=#{user.private_token}" - response.status.should == 200 - end - end - - describe "GET /projects/:id/:sha/blob" do - it "should get the raw file contents" do - get "#{api_prefix}/projects/#{project.code}/repository/commits/master/blob?filepath=README.md&private_token=#{user.private_token}" - - response.status.should == 200 - end - - it "should return 404 for invalid branch_name" do - get "#{api_prefix}/projects/#{project.code}/repository/commits/invalid_branch_name/blob?filepath=README.md&private_token=#{user.private_token}" - - response.status.should == 404 - end - - it "should return 404 for invalid file" do - get "#{api_prefix}/projects/#{project.code}/repository/commits/master/blob?filepath=README.invalid&private_token=#{user.private_token}" - - response.status.should == 404 - end - end -end diff --git a/spec/api/users_spec.rb b/spec/api/users_spec.rb deleted file mode 100644 index 32b9379d..00000000 --- a/spec/api/users_spec.rb +++ /dev/null @@ -1,37 +0,0 @@ -require 'spec_helper' - -describe Gitlab::API do - let(:user) { Factory :user } - - describe "GET /users" do - it "should return authentication error" do - get "#{api_prefix}/users" - response.status.should == 401 - end - - describe "authenticated GET /users" do - it "should return an array of users" do - get "#{api_prefix}/users?private_token=#{user.private_token}" - response.status.should == 200 - json_response.should be_an Array - json_response.first['email'].should == user.email - end - end - end - - describe "GET /users/:id" do - it "should return a user by id" do - get "#{api_prefix}/users/#{user.id}?private_token=#{user.private_token}" - response.status.should == 200 - json_response['email'].should == user.email - end - end - - describe "GET /user" do - it "should return current user" do - get "#{api_prefix}/user?private_token=#{user.private_token}" - response.status.should == 200 - json_response['email'].should == user.email - end - end -end diff --git a/spec/factories.rb b/spec/factories.rb index ab2ca468..92790a3f 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -1,92 +1,133 @@ -require File.join(Rails.root, 'spec', 'factory') - -Factory.add(:project, Project) do |obj| - obj.name = Faker::Internet.user_name - obj.path = 'gitlabhq' - obj.owner = Factory(:user) - obj.code = 'LGT' +# Backwards compatibility with the old method +def Factory(type, *args) + FactoryGirl.create(type, *args) end -Factory.add(:project_without_owner, Project) do |obj| - obj.name = Faker::Internet.user_name - obj.path = 'gitlabhq' - obj.code = 'LGT' +module Factory + def self.create(type, *args) + FactoryGirl.create(type, *args) + end + + def self.new(type, *args) + FactoryGirl.build(type, *args) + end + def self.attributes(type, *args) + FactoryGirl.attributes_for(type, *args) + end end -Factory.add(:public_project, Project) do |obj| - obj.name = Faker::Internet.user_name - obj.path = 'gitlabhq' - obj.private_flag = false - obj.owner = Factory(:user) - obj.code = 'LGT' -end +FactoryGirl.define do + sequence :sentence, aliases: [:title, :content] do + Faker::Lorem.sentence + end -Factory.add(:user, User) do |obj| - obj.email = Faker::Internet.email - obj.password = "123456" - obj.name = Faker::Name.name - obj.password_confirmation = "123456" -end + sequence :name, aliases: [:file_name] do + Faker::Name.name + end -Factory.add(:admin, User) do |obj| - obj.email = Faker::Internet.email - obj.password = "123456" - obj.name = Faker::Name.name - obj.password_confirmation = "123456" - obj.admin = true -end + sequence(:url) { Faker::Internet.uri('http') } -Factory.add(:issue, Issue) do |obj| - obj.title = Faker::Lorem.sentence - obj.author = Factory :user - obj.assignee = Factory :user -end + factory :user, aliases: [:author, :assignee, :owner] do + email { Faker::Internet.email } + name + password "123456" + password_confirmation { password } -Factory.add(:merge_request, MergeRequest) do |obj| - obj.title = Faker::Lorem.sentence - obj.author = Factory :user - obj.assignee = Factory :user - obj.source_branch = "master" - obj.target_branch = "stable" - obj.closed = false -end + trait :admin do + admin true + end -Factory.add(:snippet, Snippet) do |obj| - obj.title = Faker::Lorem.sentence - obj.file_name = Faker::Lorem.sentence - obj.content = Faker::Lorem.sentences -end + factory :admin, traits: [:admin] + end -Factory.add(:note, Note) do |obj| - obj.note = Faker::Lorem.sentence -end + factory :project do + sequence(:name) { |n| "project#{n}" } + path { name } + code { name } + owner + end -Factory.add(:key, Key) do |obj| - obj.title = "Example key" - obj.key = File.read(File.join(Rails.root, "db", "pkey.example")) -end + factory :users_project do + user + project + end -Factory.add(:project_hook, ProjectHook) do |obj| - obj.url = Faker::Internet.uri("http") -end + factory :issue do + title + author + project -Factory.add(:system_hook, SystemHook) do |obj| - obj.url = Faker::Internet.uri("http") -end + trait :closed do + closed true + end -Factory.add(:wiki, Wiki) do |obj| - obj.title = Faker::Lorem.sentence - obj.content = Faker::Lorem.sentence - obj.user = Factory(:user) - obj.project = Factory(:project) -end + factory :closed_issue, traits: [:closed] + end -Factory.add(:event, Event) do |obj| - obj.title = Faker::Lorem.sentence - obj.project = Factory(:project) -end + factory :merge_request do + title + author + project + source_branch "master" + target_branch "stable" + end -Factory.add(:milestone, Milestone) do |obj| - obj.title = Faker::Lorem.sentence - obj.due_date = Date.today + 1.month + factory :note do + project + note "Note" + end + + factory :event do + end + + factory :key do + title + key do + """ + ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4 + 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4 + soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0= + """ + end + + factory :deploy_key do + project + end + + factory :personal_key do + user + end + end + + factory :milestone do + title + project + end + + factory :system_hook do + url + end + + factory :project_hook do + url + end + + factory :wiki do + title + content + user + end + + factory :snippet do + project + author + title + content + file_name + end + + factory :protected_branch do + name + project + end end diff --git a/spec/factories_spec.rb b/spec/factories_spec.rb new file mode 100644 index 00000000..5ccc17bd --- /dev/null +++ b/spec/factories_spec.rb @@ -0,0 +1,9 @@ +require 'spec_helper' + +FactoryGirl.factories.map(&:name).each do |factory_name| + describe "#{factory_name} factory" do + it 'should be valid' do + build(factory_name).should be_valid + end + end +end diff --git a/spec/factory.rb b/spec/factory.rb deleted file mode 100644 index 1758b4d6..00000000 --- a/spec/factory.rb +++ /dev/null @@ -1,29 +0,0 @@ -class Factory - @factories = {} - - class << self - def add(name, klass, &block) - @factories[name] = [klass, block] - end - - def create(name, opts = {}) - new(name, opts).tap(&:save!) - end - - def new(name, opts = {}) - factory= @factories[name] - factory[0].new.tap do |obj| - factory[1].call(obj) - end.tap do |obj| - opts.each do |k, opt| - obj.send("#{k}=", opt) - end - end - end - end -end - -def Factory(name, opts={}) - Factory.create name, opts -end - diff --git a/spec/helpers/gitlab_flavored_markdown_spec.rb b/spec/helpers/gitlab_flavored_markdown_spec.rb deleted file mode 100644 index e147cb39..00000000 --- a/spec/helpers/gitlab_flavored_markdown_spec.rb +++ /dev/null @@ -1,232 +0,0 @@ -require "spec_helper" - -describe GitlabMarkdownHelper do - before do - @project = Project.find_by_path("gitlabhq") || Factory(:project) - @commit = @project.repo.commits.first.parents.first - @commit = CommitDecorator.decorate(Commit.new(@commit)) - @other_project = Factory :project, path: "OtherPath", code: "OtherCode" - @fake_user = Factory :user, name: "fred" - end - - describe "#gfm" do - it "should return text if @project is not set" do - @project = nil - - gfm("foo").should == "foo" - end - - describe "referencing a commit" do - it "should link using a full id" do - gfm("Reverts changes from #{@commit.id}").should == "Reverts changes from #{link_to @commit.id, project_commit_path(@project, id: @commit.id), title: "Commit: #{@commit.author_name} - #{@commit.title}", class: "gfm gfm-commit "}" - end - - it "should link using a short id" do - gfm("Backported from #{@commit.id[0, 6]}").should == "Backported from #{link_to @commit.id[0, 6], project_commit_path(@project, id: @commit.id), title: "Commit: #{@commit.author_name} - #{@commit.title}", class: "gfm gfm-commit "}" - end - - it "should link with adjecent text" do - gfm("Reverted (see #{@commit.id})").should == "Reverted (see #{link_to @commit.id, project_commit_path(@project, id: @commit.id), title: "Commit: #{@commit.author_name} - #{@commit.title}", class: "gfm gfm-commit "})" - end - - it "should not link with an invalid id" do - gfm("What happened in 12345678?").should == "What happened in 12345678?" - end - end - - describe "referencing a team member" do - it "should link using a simple name" do - user = Factory :user, name: "barry" - @project.users << user - member = @project.users_projects.where(user_id: user).first - - gfm("@#{user.name} you are right").should == "#{link_to "@#{user.name}", project_team_member_path(@project, member), class: "gfm gfm-team_member "} you are right" - end - - it "should link using a name with dots" do - user = Factory :user, name: "alphA.Beta" - @project.users << user - member = @project.users_projects.where(user_id: user).first - - gfm("@#{user.name} you are right").should == "#{link_to "@#{user.name}", project_team_member_path(@project, member), class: "gfm gfm-team_member "} you are right" - end - - it "should link using name with underscores" do - user = Factory :user, name: "ping_pong_king" - @project.users << user - member = @project.users_projects.where(user_id: user).first - - gfm("@#{user.name} you are right").should == "#{link_to "@#{user.name}", project_team_member_path(@project, member), class: "gfm gfm-team_member "} you are right" - end - - it "should link with adjecent text" do - user = Factory.create(:user, name: "ace") - @project.users << user - member = @project.users_projects.where(user_id: user).first - - gfm("Mail the Admin (@#{user.name})").should == "Mail the Admin (#{link_to "@#{user.name}", project_team_member_path(@project, member), class: "gfm gfm-team_member "})" - end - - it "should add styles" do - user = Factory :user, name: "barry" - @project.users << user - gfm("@#{user.name} you are right").should have_selector(".gfm.gfm-team_member") - end - - it "should not link using a bogus name" do - gfm("What hapened to @foo?").should == "What hapened to @foo?" - end - end - - describe "referencing an issue" do - before do - @issue = Factory :issue, assignee: @fake_user, author: @fake_user, project: @project - @invalid_issue = Factory :issue, assignee: @fake_user, author: @fake_user, project: @other_project - end - - it "should link using a correct id" do - gfm("Fixes ##{@issue.id}").should == "Fixes #{link_to "##{@issue.id}", project_issue_path(@project, @issue), title: "Issue: #{@issue.title}", class: "gfm gfm-issue "}" - end - - it "should link with adjecent text" do - gfm("This has already been discussed (see ##{@issue.id})").should == "This has already been discussed (see #{link_to "##{@issue.id}", project_issue_path(@project, @issue), title: "Issue: #{@issue.title}", class: "gfm gfm-issue "})" - end - - it "should add styles" do - gfm("Fixes ##{@issue.id}").should have_selector(".gfm.gfm-issue") - end - - it "should not link using an invalid id" do - gfm("##{@invalid_issue.id} has been marked duplicate of this").should == "##{@invalid_issue.id} has been marked duplicate of this" - end - end - - describe "referencing a merge request" do - before do - @merge_request = Factory :merge_request, assignee: @fake_user, author: @fake_user, project: @project - @invalid_merge_request = Factory :merge_request, assignee: @fake_user, author: @fake_user, project: @other_project - end - - it "should link using a correct id" do - gfm("Fixed in !#{@merge_request.id}").should == "Fixed in #{link_to "!#{@merge_request.id}", project_merge_request_path(@project, @merge_request), title: "Merge Request: #{@merge_request.title}", class: "gfm gfm-merge_request "}" - end - - it "should link with adjecent text" do - gfm("This has been fixed already (see !#{@merge_request.id})").should == "This has been fixed already (see #{link_to "!#{@merge_request.id}", project_merge_request_path(@project, @merge_request), title: "Merge Request: #{@merge_request.title}", class: "gfm gfm-merge_request "})" - end - - it "should add styles" do - gfm("Fixed in !#{@merge_request.id}").should have_selector(".gfm.gfm-merge_request") - end - - it "should not link using an invalid id" do - gfm("!#{@invalid_merge_request.id} violates our coding guidelines") - end - end - - describe "referencing a snippet" do - before do - @snippet = Factory.create(:snippet, - title: "Render asset to string", - author: @fake_user, - project: @project) - end - - it "should link using a correct id" do - gfm("Check out $#{@snippet.id}").should == "Check out #{link_to "$#{@snippet.id}", project_snippet_path(@project, @snippet), title: "Snippet: #{@snippet.title}", class: "gfm gfm-snippet "}" - end - - it "should link with adjecent text" do - gfm("I have created a snippet for that ($#{@snippet.id})").should == "I have created a snippet for that (#{link_to "$#{@snippet.id}", project_snippet_path(@project, @snippet), title: "Snippet: #{@snippet.title}", class: "gfm gfm-snippet "})" - end - - it "should add styles" do - gfm("Check out $#{@snippet.id}").should have_selector(".gfm.gfm-snippet") - end - - it "should not link using an invalid id" do - gfm("Don't use $1234").should == "Don't use $1234" - end - end - - it "should link to multiple things" do - user = Factory :user, name: "barry" - @project.users << user - member = @project.users_projects.where(user_id: user).first - - gfm("Let @#{user.name} fix the *mess* in #{@commit.id}").should == "Let #{link_to "@#{user.name}", project_team_member_path(@project, member), class: "gfm gfm-team_member "} fix the *mess* in #{link_to @commit.id, project_commit_path(@project, id: @commit.id), title: "Commit: #{@commit.author_name} - #{@commit.title}", class: "gfm gfm-commit "}" - end - - it "should not trip over other stuff", focus: true do - gfm("_Please_ *stop* 'helping' and all the other b*$#%' you do.").should == "_Please_ *stop* 'helping' and all the other b*$#%' you do." - end - - it "should not touch HTML entities" do - gfm("We'll accept good pull requests.").should == "We'll accept good pull requests." - end - - it "should forward HTML options to links" do - gfm("fixed in #{@commit.id}", class: "foo").should have_selector("a.foo") - end - end - - describe "#link_to_gfm" do - let(:issue1) { Factory :issue, assignee: @fake_user, author: @fake_user, project: @project } - let(:issue2) { Factory :issue, assignee: @fake_user, author: @fake_user, project: @project } - - it "should handle references nested in links with all the text" do - link_to_gfm("This should finally fix ##{issue1.id} and ##{issue2.id} for real", project_commit_path(@project, id: @commit.id)).should == "#{link_to "This should finally fix ", project_commit_path(@project, id: @commit.id)}#{link_to "##{issue1.id}", project_issue_path(@project, issue1), title: "Issue: #{issue1.title}", class: "gfm gfm-issue "}#{link_to " and ", project_commit_path(@project, id: @commit.id)}#{link_to "##{issue2.id}", project_issue_path(@project, issue2), title: "Issue: #{issue2.title}", class: "gfm gfm-issue "}#{link_to " for real", project_commit_path(@project, id: @commit.id)}" - end - - it "should forward HTML options" do - link_to_gfm("This should finally fix ##{issue1.id} for real", project_commit_path(@project, id: @commit.id), class: "foo").should have_selector(".foo") - end - end - - describe "#markdown" do - before do - @issue = Factory :issue, assignee: @fake_user, author: @fake_user, project: @project - @merge_request = Factory :merge_request, assignee: @fake_user, author: @fake_user, project: @project - @note = Factory.create(:note, - note: "Screenshot of the new feature", - project: @project, - noteable_id: @commit.id, - noteable_type: "Commit", - attachment: "screenshot123.jpg") - @snippet = Factory.create(:snippet, - title: "Render asset to string", - author: @fake_user, - project: @project) - - @other_user = Factory :user, name: "bill" - @project.users << @other_user - @member = @project.users_projects.where(user_id: @other_user).first - end - - it "should handle references in paragraphs" do - markdown("\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. #{@commit.id} Nam pulvinar sapien eget odio adipiscing at faucibus orci vestibulum.\n").should == "

Lorem ipsum dolor sit amet, consectetur adipiscing elit. #{link_to @commit.id, project_commit_path(@project, id: @commit.id), title: "Commit: #{@commit.author_name} - #{@commit.title}", class: "gfm gfm-commit "} Nam pulvinar sapien eget odio adipiscing at faucibus orci vestibulum.

\n" - end - - it "should handle references in headers" do - markdown("\n# Working around ##{@issue.id} for now\n## Apply !#{@merge_request.id}").should == "

Working around #{link_to "##{@issue.id}", project_issue_path(@project, @issue), title: "Issue: #{@issue.title}", class: "gfm gfm-issue "} for now

\n\n

Apply #{link_to "!#{@merge_request.id}", project_merge_request_path(@project, @merge_request), title: "Merge Request: #{@merge_request.title}", class: "gfm gfm-merge_request "}

\n" - end - - it "should handle references in lists" do - markdown("\n* dark: ##{@issue.id}\n* light by @#{@other_user.name}\n").should == "\n" - end - - it "should handle references in " do - markdown("Apply _!#{@merge_request.id}_ ASAP").should == "

Apply #{link_to "!#{@merge_request.id}", project_merge_request_path(@project, @merge_request), title: "Merge Request: #{@merge_request.title}", class: "gfm gfm-merge_request "} ASAP

\n" - end - - it "should leave code blocks untouched" do - markdown("\n some code from $#{@snippet.id}\n here too\n").should == "
some code from $#{@snippet.id}\nhere too\n
\n
\n" - - markdown("\n```\nsome code from $#{@snippet.id}\nhere too\n```\n").should == "
some code from $#{@snippet.id}\nhere too\n
\n
\n" - end - - it "should leave inline code untouched" do - markdown("\nDon't use `$#{@snippet.id}` here.\n").should == "

Don't use $#{@snippet.id} here.

\n" - end - end -end diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb new file mode 100644 index 00000000..a6708a7a --- /dev/null +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -0,0 +1,341 @@ +require "spec_helper" + +describe GitlabMarkdownHelper do + let!(:project) { create(:project) } + + let(:user) { create(:user, name: 'gfm') } + let(:commit) { CommitDecorator.decorate(project.commit) } + let(:issue) { create(:issue, project: project) } + let(:merge_request) { create(:merge_request, project: project) } + let(:snippet) { create(:snippet, project: project) } + let(:member) { project.users_projects.where(user_id: user).first } + + before do + # Helper expects a @project instance variable + @project = project + end + + describe "#gfm" do + it "should return unaltered text if project is nil" do + actual = "Testing references: ##{issue.id}" + + gfm(actual).should_not == actual + + @project = nil + gfm(actual).should == actual + end + + it "should not alter non-references" do + actual = expected = "_Please_ *stop* 'helping' and all the other b*$#%' you do." + gfm(actual).should == expected + end + + it "should not touch HTML entities" do + @project.issues.stub(:where).with(id: '39').and_return([issue]) + actual = expected = "We'll accept good pull requests." + gfm(actual).should == expected + end + + it "should forward HTML options to links" do + gfm("Fixed in #{commit.id}", class: "foo").should have_selector("a.gfm.foo") + end + + describe "referencing a commit" do + let(:expected) { project_commit_path(project, commit) } + + it "should link using a full id" do + actual = "Reverts #{commit.id}" + gfm(actual).should match(expected) + end + + it "should link using a short id" do + actual = "Backported from #{commit.short_id(6)}" + gfm(actual).should match(expected) + end + + it "should link with adjacent text" do + actual = "Reverted (see #{commit.id})" + gfm(actual).should match(expected) + end + + it "should keep whitespace intact" do + actual = "Changes #{commit.id} dramatically" + expected = /Changes #{commit.id}<\/a> dramatically/ + gfm(actual).should match(expected) + end + + it "should not link with an invalid id" do + actual = expected = "What happened in #{commit.id.reverse}" + gfm(actual).should == expected + end + + it "should include a title attribute" do + actual = "Reverts #{commit.id}" + gfm(actual).should match(/title="#{commit.link_title}"/) + end + + it "should include standard gfm classes" do + actual = "Reverts #{commit.id}" + gfm(actual).should match(/class="\s?gfm gfm-commit\s?"/) + end + end + + describe "referencing a team member" do + let(:actual) { "@#{user.name} you are right." } + let(:expected) { project_team_member_path(project, member) } + + before do + project.users << user + end + + it "should link using a simple name" do + gfm(actual).should match(expected) + end + + it "should link using a name with dots" do + user.update_attributes(name: "alphA.Beta") + gfm(actual).should match(expected) + end + + it "should link using name with underscores" do + user.update_attributes(name: "ping_pong_king") + gfm(actual).should match(expected) + end + + it "should link with adjacent text" do + actual = "Mail the admin (@gfm)" + gfm(actual).should match(expected) + end + + it "should keep whitespace intact" do + actual = "Yes, @#{user.name} is right." + expected = /Yes, @#{user.name}<\/a> is right/ + gfm(actual).should match(expected) + end + + it "should not link with an invalid id" do + actual = expected = "@#{user.name.reverse} you are right." + gfm(actual).should == expected + end + + it "should include standard gfm classes" do + gfm(actual).should match(/class="\s?gfm gfm-team_member\s?"/) + end + end + + # Shared examples for referencing an object + # + # Expects the following attributes to be available in the example group: + # + # - object - The object itself + # - reference - The object reference string (e.g., #1234, $1234, !1234) + # + # Currently limited to Snippets, Issues and MergeRequests + shared_examples 'referenced object' do + let(:actual) { "Reference to #{reference}" } + let(:expected) { polymorphic_path([project, object]) } + + it "should link using a valid id" do + gfm(actual).should match(expected) + end + + it "should link with adjacent text" do + # Wrap the reference in parenthesis + gfm(actual.gsub(reference, "(#{reference})")).should match(expected) + + # Append some text to the end of the reference + gfm(actual.gsub(reference, "#{reference}, right?")).should match(expected) + end + + it "should keep whitespace intact" do + actual = "Referenced #{reference} already." + expected = /Referenced [^\s]+<\/a> already/ + gfm(actual).should match(expected) + end + + it "should not link with an invalid id" do + # Modify the reference string so it's still parsed, but is invalid + reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2)) + gfm(actual).should == actual + end + + it "should include a title attribute" do + title = "#{object.class.to_s.titlecase}: #{object.title}" + gfm(actual).should match(/title="#{title}"/) + end + + it "should include standard gfm classes" do + css = object.class.to_s.underscore + gfm(actual).should match(/class="\s?gfm gfm-#{css}\s?"/) + end + end + + describe "referencing an issue" do + let(:object) { issue } + let(:reference) { "##{issue.id}" } + + include_examples 'referenced object' + end + + describe "referencing a merge request" do + let(:object) { merge_request } + let(:reference) { "!#{merge_request.id}" } + + include_examples 'referenced object' + end + + describe "referencing a snippet" do + let(:object) { snippet } + let(:reference) { "$#{snippet.id}" } + + include_examples 'referenced object' + end + + describe "referencing multiple objects" do + let(:actual) { "!#{merge_request.id} -> #{commit.id} -> ##{issue.id}" } + + it "should link to the merge request" do + expected = project_merge_request_path(project, merge_request) + gfm(actual).should match(expected) + end + + it "should link to the commit" do + expected = project_commit_path(project, commit) + gfm(actual).should match(expected) + end + + it "should link to the issue" do + expected = project_issue_path(project, issue) + gfm(actual).should match(expected) + end + end + + describe "emoji" do + it "matches at the start of a string" do + gfm(":+1:").should match(/ big time/) + end + + it "ignores invalid emoji" do + gfm(":invalid-emoji:").should_not match(/") + + # Leading commit link + groups[0].should match(/href="#{commit_path}"/) + groups[0].should match(/This should finally fix $/) + + # First issue link + groups[1].should match(/href="#{project_issue_path(project, issues[0])}"/) + groups[1].should match(/##{issues[0].id}$/) + + # Internal commit link + groups[2].should match(/href="#{commit_path}"/) + groups[2].should match(/ and /) + + # Second issue link + groups[3].should match(/href="#{project_issue_path(project, issues[1])}"/) + groups[3].should match(/##{issues[1].id}$/) + + # Trailing commit link + groups[4].should match(/href="#{commit_path}"/) + groups[4].should match(/ for real$/) + end + + it "should forward HTML options" do + actual = link_to_gfm("Fixed in #{commit.id}", commit_path, class: 'foo') + actual.should have_selector 'a.gfm.gfm-commit.foo' + end + + it "escapes HTML passed in as the body" do + actual = "This is a

test

- see ##{issues[0].id}" + link_to_gfm(actual, commit_path).should match('<h1>test</h1>') + end + end + + describe "#markdown" do + it "should handle references in paragraphs" do + actual = "\n\nLorem ipsum dolor sit amet. #{commit.id} Nam pulvinar sapien eget.\n" + expected = project_commit_path(project, commit) + markdown(actual).should match(expected) + end + + it "should handle references in headers" do + actual = "\n# Working around ##{issue.id}\n## Apply !#{merge_request.id}" + + markdown(actual).should match(%r{Working around ##{issue.id}}) + markdown(actual).should match(%r{Apply !#{merge_request.id}}) + end + + it "should handle references in lists" do + project.users << user + + actual = "\n* dark: ##{issue.id}\n* light by @#{member.user_name}" + + markdown(actual).should match(%r{
  • dark: ##{issue.id}
  • }) + markdown(actual).should match(%r{
  • light by @#{member.user_name}
  • }) + end + + it "should handle references in " do + actual = "Apply _!#{merge_request.id}_ ASAP" + + markdown(actual).should match(%r{Apply !#{merge_request.id}}) + end + + it "should leave code blocks untouched" do + markdown("\n some code from $#{snippet.id}\n here too\n").should == "
    some code from $#{snippet.id}\nhere too\n
    \n
    \n" + + markdown("\n```\nsome code from $#{snippet.id}\nhere too\n```\n").should == "
    some code from $#{snippet.id}\nhere too\n
    \n
    \n" + end + + it "should leave inline code untouched" do + markdown("\nDon't use `$#{snippet.id}` here.\n").should == "

    Don't use $#{snippet.id} here.

    \n" + end + end +end diff --git a/spec/helpers/tree_helper_spec.rb b/spec/helpers/tree_helper_spec.rb new file mode 100644 index 00000000..d450b687 --- /dev/null +++ b/spec/helpers/tree_helper_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe TreeHelper do + describe '#markup?' do + %w(textile rdoc org creole mediawiki rst asciidoc pod).each do |type| + it "returns true for #{type} files" do + markup?("README.#{type}").should be_true + end + end + + it "returns false when given a non-markup filename" do + markup?('README.rb').should_not be_true + end + end +end diff --git a/spec/lib/auth_spec.rb b/spec/lib/auth_spec.rb new file mode 100644 index 00000000..1e03bc59 --- /dev/null +++ b/spec/lib/auth_spec.rb @@ -0,0 +1,95 @@ +require 'spec_helper' + +describe Gitlab::Auth do + let(:gl_auth) { Gitlab::Auth.new } + + before do + Gitlab.config.stub(omniauth: {}) + + @info = mock( + uid: '12djsak321', + name: 'John', + email: 'john@mail.com' + ) + end + + describe :find_for_ldap_auth do + before do + @auth = mock( + uid: '12djsak321', + info: @info, + provider: 'ldap' + ) + end + + it "should find by uid & provider" do + User.should_receive :find_by_extern_uid_and_provider + gl_auth.find_for_ldap_auth(@auth) + end + + it "should update credentials by email if missing uid" do + user = double('User') + User.stub find_by_extern_uid_and_provider: nil + User.stub find_by_email: user + user.should_receive :update_attributes + gl_auth.find_for_ldap_auth(@auth) + end + + + it "should create from auth if user doesnot exist"do + User.stub find_by_extern_uid_and_provider: nil + User.stub find_by_email: nil + gl_auth.should_receive :create_from_omniauth + gl_auth.find_for_ldap_auth(@auth) + end + end + + describe :find_or_new_for_omniauth do + before do + @auth = mock( + info: @info, + provider: 'twitter', + uid: '12djsak321', + ) + end + + it "should find user"do + User.should_receive :find_by_provider_and_extern_uid + gl_auth.should_not_receive :create_from_omniauth + gl_auth.find_or_new_for_omniauth(@auth) + end + + it "should not create user"do + User.stub find_by_provider_and_extern_uid: nil + gl_auth.should_not_receive :create_from_omniauth + gl_auth.find_or_new_for_omniauth(@auth) + end + + it "should create user if single_sing_on"do + Gitlab.config.omniauth['allow_single_sign_on'] = true + User.stub find_by_provider_and_extern_uid: nil + gl_auth.should_receive :create_from_omniauth + gl_auth.find_or_new_for_omniauth(@auth) + end + end + + describe :create_from_omniauth do + it "should create user from LDAP" do + @auth = mock(info: @info, provider: 'ldap') + user = gl_auth.create_from_omniauth(@auth, true) + + user.should be_valid + user.extern_uid.should == @info.uid + user.provider.should == 'ldap' + end + + it "should create user from Omniauth" do + @auth = mock(info: @info, provider: 'twitter') + user = gl_auth.create_from_omniauth(@auth, false) + + user.should be_valid + user.extern_uid.should == @info.uid + user.provider.should == 'twitter' + end + end +end diff --git a/spec/lib/gitolite_config_spec.rb b/spec/lib/gitolite_config_spec.rb new file mode 100644 index 00000000..c3ce0db5 --- /dev/null +++ b/spec/lib/gitolite_config_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +describe Gitlab::GitoliteConfig do + let(:gitolite) { Gitlab::GitoliteConfig.new } + + it { should respond_to :write_key } + it { should respond_to :rm_key } + it { should respond_to :update_project } + it { should respond_to :update_project! } + it { should respond_to :update_projects } + it { should respond_to :destroy_project } + it { should respond_to :destroy_project! } + it { should respond_to :apply } + it { should respond_to :admin_all_repo } + it { should respond_to :admin_all_repo! } +end diff --git a/spec/lib/gitolite_spec.rb b/spec/lib/gitolite_spec.rb new file mode 100644 index 00000000..cc8ce8b2 --- /dev/null +++ b/spec/lib/gitolite_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe Gitlab::Gitolite do + let(:project) { double('Project', path: 'diaspora') } + let(:gitolite_config) { double('Gitlab::GitoliteConfig') } + let(:gitolite) { Gitlab::Gitolite.new } + + before do + gitolite.stub(config: gitolite_config) + end + + it { should respond_to :set_key } + it { should respond_to :remove_key } + + it { should respond_to :update_repository } + it { should respond_to :create_repository } + it { should respond_to :remove_repository } + + it { gitolite.url_to_repo('diaspora').should == Gitlab.config.ssh_path + "diaspora.git" } + + it "should call config update" do + gitolite_config.should_receive(:update_project!) + gitolite.update_repository project + end +end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 93427ebf..4a9f142e 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -24,7 +24,7 @@ describe Notify do end it 'has the correct subject' do - should have_subject /^gitlab \| Account was created for you$/ + should have_subject /^gitlab \| Account was created for you$/i end it 'contains the new user\'s login name' do @@ -91,6 +91,29 @@ describe Notify do should have_body_text /#{project_issue_path project, issue}/ end end + + describe 'status changed' do + let(:current_user) { Factory.create :user, email: "current@email.com" } + let(:status) { 'closed' } + subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user) } + + it 'has the correct subject' do + should have_subject /changed issue ##{issue.id} \| #{issue.title}/i + end + + it 'contains the new status' do + should have_body_text /#{status}/i + end + + it 'contains the user name' do + should have_body_text /#{current_user.name}/i + end + + it 'contains a link to the issue' do + should have_body_text /#{project_issue_path project, issue}/ + end + end + end context 'for merge requests' do @@ -145,6 +168,26 @@ describe Notify do end end + describe 'project access changed' do + let(:project) { Factory.create(:project, + path: "Fuu", + code: "Fuu") } + let(:user) { Factory.create :user } + let(:users_project) { Factory.create(:users_project, + project: project, + user: user) } + subject { Notify.project_access_granted_email(users_project.id) } + it 'has the correct subject' do + should have_subject /access to project was granted/ + end + it 'contains name of project' do + should have_body_text /#{project.name}/ + end + it 'contains new user role' do + should have_body_text /#{users_project.project_access_human}/ + end + end + context 'items that are noteable, the email for a note' do let(:note_author) { Factory.create(:user, name: 'author_name') } let(:note) { Factory.create(:note, project: project, author: note_author) } diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 188f0997..5cb68761 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -1,24 +1,9 @@ -# == Schema Information -# -# Table name: events -# -# id :integer(4) not null, primary key -# target_type :string(255) -# target_id :integer(4) -# title :string(255) -# data :text -# project_id :integer(4) -# created_at :datetime not null -# updated_at :datetime not null -# action :integer(4) -# author_id :integer(4) -# - require 'spec_helper' describe Event do describe "Associations" do it { should belong_to(:project) } + it { should belong_to(:target) } end describe "Respond to" do @@ -29,22 +14,12 @@ describe Event do it { should respond_to(:commits) } end - describe "Creation" do - before do - @event = Factory :event - end - - it "should create a valid event" do - @event.should be_valid - end - end - - describe "Push event" do - before do + describe "Push event" do + before do project = Factory :project @user = project.owner - data = { + data = { before: "0000000000000000000000000000000000000000", after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e", ref: "refs/heads/master", @@ -74,4 +49,25 @@ describe Event do it { @event.branch_name.should == "master" } it { @event.author.should == @user } end + + describe 'Team events' do + let(:user_project) { stub.as_null_object } + let(:observer) { UsersProjectObserver.instance } + + before { + Event.should_receive :create + } + + describe "Joined project team" do + it "should create event" do + observer.after_create user_project + end + end + + describe "Left project team" do + it "should create event" do + observer.after_destroy user_project + end + end + end end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index e9cbd725..34192da9 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -2,28 +2,20 @@ require 'spec_helper' describe Issue do describe "Associations" do - it { should belong_to(:project) } - it { should belong_to(:author) } - it { should belong_to(:assignee) } it { should belong_to(:milestone) } end describe "Validation" do - it { should validate_presence_of(:title) } - it { should validate_presence_of(:author_id) } - it { should validate_presence_of(:project_id) } + it { should ensure_length_of(:description).is_within(0..2000) } + it { should ensure_inclusion_of(:closed).in_array([true, false]) } end - describe "Scope" do - it { Issue.should respond_to :closed } - it { Issue.should respond_to :opened } + describe 'modules' do + it { should include_module(IssueCommonality) } + it { should include_module(Votes) } end - subject { Factory.create(:issue, - author: Factory(:user), - assignee: Factory(:user), - project: Factory.create(:project)) } - it { should be_valid } + subject { Factory.create(:issue) } describe '#is_being_reassigned?' do it 'returns true if the issue assignee has changed' do @@ -41,11 +33,7 @@ describe Issue do subject.is_being_closed?.should be_true end it 'returns false if the closed attribute has changed and is now false' do - issue = Factory.create(:issue, - closed: true, - author: Factory(:user), - assignee: Factory(:user), - project: Factory.create(:project)) + issue = Factory.create(:closed_issue) issue.closed = false issue.is_being_closed?.should be_false end @@ -57,11 +45,7 @@ describe Issue do describe '#is_being_reopened?' do it 'returns true if the closed attribute has changed and is now false' do - issue = Factory.create(:issue, - closed: true, - author: Factory(:user), - assignee: Factory(:user), - project: Factory.create(:project)) + issue = Factory.create(:closed_issue) issue.closed = false issue.is_being_reopened?.should be_true end @@ -73,64 +57,4 @@ describe Issue do subject.is_being_reopened?.should be_false end end - - describe "plus 1" do - let(:project) { Factory(:project) } - subject { - Factory.create(:issue, - author: Factory(:user), - assignee: Factory(:user), - project: project) - } - - it "with no notes has a 0/0 score" do - subject.upvotes.should == 0 - end - - it "should recognize non-+1 notes" do - subject.notes << Factory(:note, note: "No +1 here", project: Factory(:project, path: 'plusone', code: 'plusone')) - subject.should have(1).note - subject.notes.first.upvote?.should be_false - subject.upvotes.should == 0 - end - - it "should recognize a single +1 note" do - subject.notes << Factory(:note, note: "+1 This is awesome", project: Factory(:project, path: 'plusone', code: 'plusone')) - subject.upvotes.should == 1 - end - - it "should recognize a multiple +1 notes" do - subject.notes << Factory(:note, note: "+1 This is awesome", project: Factory(:project, path: 'plusone', code: 'plusone')) - subject.notes << Factory(:note, note: "+1 I want this", project: Factory(:project, path: 'plustwo', code: 'plustwo')) - subject.upvotes.should == 2 - end - end - - describe ".search" do - let!(:issue) { Factory.create(:issue, title: "Searchable issue", - project: Factory.create(:project)) } - - it "matches by title" do - Issue.search('able').all.should == [issue] - end - end end -# == Schema Information -# -# Table name: issues -# -# id :integer(4) not null, primary key -# title :string(255) -# assignee_id :integer(4) -# author_id :integer(4) -# project_id :integer(4) -# created_at :datetime not null -# updated_at :datetime not null -# closed :boolean(1) default(FALSE), not null -# position :integer(4) default(0) -# critical :boolean(1) default(FALSE), not null -# branch_name :string(255) -# description :text -# milestone_id :integer(4) -# - diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index 0f9b3177..85cd291d 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -2,12 +2,15 @@ require 'spec_helper' describe Key do describe "Associations" do - it { should belong_to(:user) or belong_to(:project) } + it { should belong_to(:user) } + it { should belong_to(:project) } end describe "Validation" do it { should validate_presence_of(:title) } it { should validate_presence_of(:key) } + it { should ensure_length_of(:title).is_within(0..255) } + it { should ensure_length_of(:key).is_within(0..5000) } end describe "Methods" do @@ -17,20 +20,15 @@ describe Key do context "validation of uniqueness" do context "as a deploy key" do - let(:project) { Factory.create(:project, path: 'alpha', code: 'alpha') } - let(:another_project) { Factory.create(:project, path: 'beta', code: 'beta') } - - before do - deploy_key = Factory.create(:key, project: project) - end + let!(:deploy_key) { create(:deploy_key) } it "does not accept the same key twice for a project" do - key = Factory.new(:key, project: project) + key = build(:key, project: deploy_key.project) key.should_not be_valid end it "does accept the same key for another project" do - key = Factory.new(:key, project: another_project) + key = build(:key, project_id: 0) key.should be_valid end end @@ -39,27 +37,13 @@ describe Key do let(:user) { Factory.create(:user) } it "accepts the key once" do - Factory.new(:key, user: user).should be_valid + build(:key, user: user).should be_valid end it "does not accepts the key twice" do - Factory.create(:key, user: user) - Factory.new(:key, user: user).should_not be_valid + create(:key, user: user) + build(:key, user: user).should_not be_valid end end end end -# == Schema Information -# -# Table name: keys -# -# id :integer(4) not null, primary key -# user_id :integer(4) -# created_at :datetime not null -# updated_at :datetime not null -# key :text -# title :string(255) -# identifier :string(255) -# project_id :integer(4) -# - diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index c7ad08a1..523e823d 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1,88 +1,13 @@ require 'spec_helper' describe MergeRequest do - describe "Associations" do - it { should belong_to(:project) } - it { should belong_to(:author) } - it { should belong_to(:assignee) } - end - describe "Validation" do it { should validate_presence_of(:target_branch) } it { should validate_presence_of(:source_branch) } - it { should validate_presence_of(:title) } - it { should validate_presence_of(:author_id) } - it { should validate_presence_of(:project_id) } end - describe "Scope" do - it { MergeRequest.should respond_to :closed } - it { MergeRequest.should respond_to :opened } - end - - it { Factory.create(:merge_request, - author: Factory(:user), - assignee: Factory(:user), - project: Factory.create(:project)).should be_valid } - - describe "plus 1" do - let(:project) { Factory(:project) } - subject { - Factory.create(:merge_request, - author: Factory(:user), - assignee: Factory(:user), - project: project) - } - - it "with no notes has a 0/0 score" do - subject.upvotes.should == 0 - end - - it "should recognize non-+1 notes" do - subject.notes << Factory(:note, note: "No +1 here", project: Factory(:project, path: 'plusone', code: 'plusone')) - subject.should have(1).note - subject.notes.first.upvote?.should be_false - subject.upvotes.should == 0 - end - - it "should recognize a single +1 note" do - subject.notes << Factory(:note, note: "+1 This is awesome", project: Factory(:project, path: 'plusone', code: 'plusone')) - subject.upvotes.should == 1 - end - - it "should recognize a multiple +1 notes" do - subject.notes << Factory(:note, note: "+1 This is awesome", project: Factory(:project, path: 'plusone', code: 'plusone')) - subject.notes << Factory(:note, note: "+1 I want this", project: Factory(:project, path: 'plustwo', code: 'plustwo')) - subject.upvotes.should == 2 - end - end - - describe ".search" do - let!(:issue) { Factory.create(:issue, title: "Searchable issue", - project: Factory.create(:project)) } - - it "matches by title" do - Issue.search('able').all.should == [issue] - end + describe 'modules' do + it { should include_module(IssueCommonality) } + it { should include_module(Votes) } end end -# == Schema Information -# -# Table name: merge_requests -# -# id :integer(4) not null, primary key -# target_branch :string(255) not null -# source_branch :string(255) not null -# project_id :integer(4) not null -# author_id :integer(4) -# assignee_id :integer(4) -# title :string(255) -# closed :boolean(1) default(FALSE), not null -# created_at :datetime not null -# updated_at :datetime not null -# st_commits :text(2147483647 -# st_diffs :text(2147483647 -# merged :boolean(1) default(FALSE), not null -# state :integer(4) default(1), not null -# - diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 880d3f30..f0f0f883 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -1,17 +1,3 @@ -# == Schema Information -# -# Table name: milestones -# -# id :integer(4) not null, primary key -# title :string(255) not null -# project_id :integer(4) not null -# description :text -# due_date :date -# closed :boolean(1) default(FALSE), not null -# created_at :datetime not null -# updated_at :datetime not null -# - require 'spec_helper' describe Milestone do @@ -23,32 +9,39 @@ describe Milestone do describe "Validation" do it { should validate_presence_of(:title) } it { should validate_presence_of(:project_id) } + it { should ensure_inclusion_of(:closed).in_array([true, false]) } end - let(:project) { Factory :project } - let(:milestone) { Factory :milestone, project: project } - let(:issue) { Factory :issue, project: project } + let(:milestone) { Factory :milestone } + let(:issue) { Factory :issue } - it { milestone.should be_valid } - - describe "Issues" do - before do + describe "#percent_complete" do + it "should not count open issues" do milestone.issues << issue + milestone.percent_complete.should == 0 end - it { milestone.percent_complete.should == 0 } + it "should count closed issues" do + issue.update_attributes(closed: true) + milestone.issues << issue + milestone.percent_complete.should == 100 + end - it do - issue.update_attributes closed: true + it "should recover from dividing by zero" do + milestone.issues.should_receive(:count).and_return(0) milestone.percent_complete.should == 100 end end - describe :expires_at do - before do - milestone.update_attributes due_date: Date.today + 1.day + describe "#expires_at" do + it "should be nil when due_date is unset" do + milestone.update_attributes(due_date: nil) + milestone.expires_at.should be_nil end - it { milestone.expires_at.should_not be_nil } + it "should not be nil when due_date is set" do + milestone.update_attributes(due_date: Date.tomorrow) + milestone.expires_at.should be_present + end end end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index c97b23cb..7809953f 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -1,11 +1,10 @@ require 'spec_helper' describe Note do - let(:project) { Factory :project } - let!(:commit) { project.commit } - describe "Associations" do it { should belong_to(:project) } + it { should belong_to(:noteable) } + it { should belong_to(:author).class_name('User') } end describe "Validation" do @@ -13,8 +12,6 @@ describe Note do it { should validate_presence_of(:project) } end - it { Factory.create(:note, - project: project).should be_valid } describe "Scopes" do it "should have a today named scope that returns ..." do Note.today.where_values.should == ["created_at >= '#{Date.today}'"] @@ -25,26 +22,44 @@ describe Note do let(:project) { Factory(:project) } it "recognizes a neutral note" do - note = Factory(:note, project: project, note: "This is not a +1 note") + note = Factory(:note, note: "This is not a +1 note") note.should_not be_upvote + note.should_not be_downvote + end + + it "recognizes a neutral emoji note" do + note = build(:note, note: "I would :+1: this, but I don't want to") + note.should_not be_upvote + note.should_not be_downvote end it "recognizes a +1 note" do - note = Factory(:note, project: project, note: "+1 for this") + note = Factory(:note, note: "+1 for this") note.should be_upvote end - it "recognizes a -1 note as no vote" do - note = Factory(:note, project: project, note: "-1 for this") - note.should_not be_upvote + it "recognizes a +1 emoji as a vote" do + note = build(:note, note: ":+1: for this") + note.should be_upvote + end + + it "recognizes a -1 note" do + note = Factory(:note, note: "-1 for this") + note.should be_downvote + end + + it "recognizes a -1 emoji as a vote" do + note = build(:note, note: ":-1: for this") + note.should be_downvote end end - describe "Commit notes" do + let(:project) { create(:project) } + let(:commit) { project.commit } + describe "Commit notes" do before do @note = Factory :note, - project: project, noteable_id: commit.id, noteable_type: "Commit" end @@ -58,7 +73,6 @@ describe Note do describe "Pre-line commit notes" do before do @note = Factory :note, - project: project, noteable_id: commit.id, noteable_type: "Commit", line_code: "0_16_1" @@ -91,8 +105,8 @@ describe Note do describe :authorization do before do - @p1 = project - @p2 = Factory :project, code: "alien", path: "gitlabhq_1" + @p1 = create(:project) + @p2 = Factory :project @u1 = Factory :user @u2 = Factory :user @u3 = Factory :user @@ -135,19 +149,3 @@ describe Note do end end end -# == Schema Information -# -# Table name: notes -# -# id :integer(4) not null, primary key -# note :text -# noteable_id :string(255) -# noteable_type :string(255) -# author_id :integer(4) -# created_at :datetime not null -# updated_at :datetime not null -# project_id :integer(4) -# attachment :string(255) -# line_code :string(255) -# - diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index af193295..756f69de 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2,23 +2,56 @@ require 'spec_helper' describe Project do describe "Associations" do + it { should belong_to(:owner).class_name('User') } it { should have_many(:users) } - it { should have_many(:protected_branches).dependent(:destroy) } it { should have_many(:events).dependent(:destroy) } - it { should have_many(:wikis).dependent(:destroy) } it { should have_many(:merge_requests).dependent(:destroy) } - it { should have_many(:users_projects).dependent(:destroy) } it { should have_many(:issues).dependent(:destroy) } + it { should have_many(:milestones).dependent(:destroy) } + it { should have_many(:users_projects).dependent(:destroy) } it { should have_many(:notes).dependent(:destroy) } it { should have_many(:snippets).dependent(:destroy) } - it { should have_many(:hooks).dependent(:destroy) } it { should have_many(:deploy_keys).dependent(:destroy) } + it { should have_many(:hooks).dependent(:destroy) } + it { should have_many(:wikis).dependent(:destroy) } + it { should have_many(:protected_branches).dependent(:destroy) } end describe "Validation" do + let!(:project) { create(:project) } + it { should validate_presence_of(:name) } + it { should validate_uniqueness_of(:name) } + it { should ensure_length_of(:name).is_within(0..255) } + it { should validate_presence_of(:path) } + it { should validate_uniqueness_of(:path) } + it { should ensure_length_of(:path).is_within(0..255) } + # TODO: Formats + + it { should ensure_length_of(:description).is_within(0..2000) } + it { should validate_presence_of(:code) } + it { should validate_uniqueness_of(:code) } + it { should ensure_length_of(:code).is_within(1..255) } + # TODO: Formats + + it { should validate_presence_of(:owner) } + it { should ensure_inclusion_of(:issues_enabled).in_array([true, false]) } + it { should ensure_inclusion_of(:wall_enabled).in_array([true, false]) } + it { should ensure_inclusion_of(:merge_requests_enabled).in_array([true, false]) } + it { should ensure_inclusion_of(:wiki_enabled).in_array([true, false]) } + + it "should not allow new projects beyond user limits" do + project.stub(:owner).and_return(double(can_create_project?: false, projects_limit: 1)) + project.should_not be_valid + project.errors[:base].first.should match(/Your own projects limit is 1/) + end + + it "should not allow 'gitolite-admin' as repo name" do + should allow_value("blah").for(:path) + should_not allow_value("gitolite-admin").for(:path) + end end describe "Respond to" do @@ -73,9 +106,11 @@ describe Project do it { should respond_to(:trigger_post_receive) } end - it "should not allow 'gitolite-admin' as repo name" do - should allow_value("blah").for(:path) - should_not allow_value("gitolite-admin").for(:path) + describe 'modules' do + it { should include_module(Repository) } + it { should include_module(PushObserver) } + it { should include_module(Authority) } + it { should include_module(Team) } end it "should return valid url to repo" do @@ -85,7 +120,7 @@ describe Project do it "should return path to repo" do project = Project.new(path: "somewhere") - project.path_to_repo.should == File.join(Rails.root, "tmp", "tests", "somewhere") + project.path_to_repo.should == File.join(Rails.root, "tmp", "repositories", "somewhere") end it "returns the full web URL for this repo" do @@ -110,7 +145,7 @@ describe Project do let(:last_event) { double } before do - project.stub(:events).and_return( [ double, double, last_event ] ) + project.stub_chain(:events, :order).and_return( [ double, double, last_event ] ) end it { project.last_activity.should == last_event } @@ -208,7 +243,7 @@ describe Project do end end - describe :update_merge_requests do + describe :update_merge_requests do let(:project) { Factory :project } before do @@ -228,7 +263,7 @@ describe Project do @merge_request.closed.should be_true end - it "should update merge request commits with new one if pushed to source branch" do + it "should update merge request commits with new one if pushed to source branch" do @merge_request.last_commit.should == nil project.update_merge_requests("8716fc78f3c65bbf7bcf7b574febd583bc5d2812", "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a", "refs/heads/master", @key.user) @merge_request.reload @@ -236,23 +271,3 @@ describe Project do end end end -# == Schema Information -# -# Table name: projects -# -# id :integer(4) not null, primary key -# name :string(255) -# path :string(255) -# description :text -# created_at :datetime not null -# updated_at :datetime not null -# private_flag :boolean(1) default(TRUE), not null -# code :string(255) -# owner_id :integer(4) -# default_branch :string(255) default("master"), not null -# issues_enabled :boolean(1) default(TRUE), not null -# wall_enabled :boolean(1) default(TRUE), not null -# merge_requests_enabled :boolean(1) default(TRUE), not null -# wiki_enabled :boolean(1) default(TRUE), not null -# - diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb index 1654e3b6..9180bc3b 100644 --- a/spec/models/protected_branch_spec.rb +++ b/spec/models/protected_branch_spec.rb @@ -1,19 +1,6 @@ -# == Schema Information -# -# Table name: protected_branches -# -# id :integer(4) not null, primary key -# project_id :integer(4) not null -# name :string(255) not null -# created_at :datetime not null -# updated_at :datetime not null -# - require 'spec_helper' describe ProtectedBranch do - let(:project) { Factory(:project) } - describe 'Associations' do it { should belong_to(:project) } end @@ -24,26 +11,26 @@ describe ProtectedBranch do end describe 'Callbacks' do - subject { ProtectedBranch.new(project: project, name: 'branch_name') } + let(:branch) { build(:protected_branch) } it 'call update_repository after save' do - subject.should_receive(:update_repository) - subject.save + branch.should_receive(:update_repository) + branch.save end it 'call update_repository after destroy' do - subject.should_receive(:update_repository) - subject.destroy + branch.save + branch.should_receive(:update_repository) + branch.destroy end end describe '#commit' do - subject { ProtectedBranch.new(project: project, name: 'cant_touch_this') } + let(:branch) { create(:protected_branch) } it 'commits itself to its project' do - project.should_receive(:commit).with('cant_touch_this') - - subject.commit + branch.project.should_receive(:commit).with(branch.name) + branch.commit end end end diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 9b4aaa13..ffb861c4 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -3,29 +3,21 @@ require 'spec_helper' describe Snippet do describe "Associations" do it { should belong_to(:project) } - it { should belong_to(:author) } + it { should belong_to(:author).class_name('User') } + it { should have_many(:notes).dependent(:destroy) } end describe "Validation" do - it { should validate_presence_of(:title) } it { should validate_presence_of(:author_id) } it { should validate_presence_of(:project_id) } + + it { should validate_presence_of(:title) } + it { should ensure_length_of(:title).is_within(0..255) } + it { should validate_presence_of(:file_name) } + it { should ensure_length_of(:title).is_within(0..255) } + it { should validate_presence_of(:content) } + it { should ensure_length_of(:content).is_within(0..10_000) } end end -# == Schema Information -# -# Table name: snippets -# -# id :integer(4) not null, primary key -# title :string(255) -# content :text -# author_id :integer(4) not null -# project_id :integer(4) not null -# created_at :datetime not null -# updated_at :datetime not null -# file_name :string(255) -# expires_at :datetime -# - diff --git a/spec/models/system_hook_spec.rb b/spec/models/system_hook_spec.rb index 56d76ed0..fe2a5836 100644 --- a/spec/models/system_hook_spec.rb +++ b/spec/models/system_hook_spec.rb @@ -10,13 +10,12 @@ describe SystemHook do end it "project_create hook" do - user = Factory :user with_resque do - project = Factory :project_without_owner, owner: user + project = Factory :project end WebMock.should have_requested(:post, @system_hook.url).with(body: /project_create/).once end - + it "project_destroy hook" do project = Factory :project with_resque do @@ -31,7 +30,7 @@ describe SystemHook do end WebMock.should have_requested(:post, @system_hook.url).with(body: /user_create/).once end - + it "user_destroy hook" do user = Factory :user with_resque do @@ -39,7 +38,7 @@ describe SystemHook do end WebMock.should have_requested(:post, @system_hook.url).with(body: /user_destroy/).once end - + it "project_create hook" do user = Factory :user project = Factory :project @@ -48,7 +47,7 @@ describe SystemHook do end WebMock.should have_requested(:post, @system_hook.url).with(body: /user_add_to_team/).once end - + it "project_destroy hook" do user = Factory :user project = Factory :project diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 265dcef1..08176754 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2,12 +2,26 @@ require 'spec_helper' describe User do describe "Associations" do + it { should have_many(:users_projects).dependent(:destroy) } it { should have_many(:projects) } - it { should have_many(:users_projects) } - it { should have_many(:issues) } - it { should have_many(:assigned_issues) } - it { should have_many(:merge_requests) } - it { should have_many(:assigned_merge_requests) } + it { should have_many(:my_own_projects).class_name('Project') } + it { should have_many(:keys).dependent(:destroy) } + it { should have_many(:events).class_name('Event').dependent(:destroy) } + it { should have_many(:recent_events).class_name('Event') } + it { should have_many(:issues).dependent(:destroy) } + it { should have_many(:notes).dependent(:destroy) } + it { should have_many(:assigned_issues).dependent(:destroy) } + it { should have_many(:merge_requests).dependent(:destroy) } + it { should have_many(:assigned_merge_requests).dependent(:destroy) } + end + + describe 'validations' do + it { should validate_presence_of(:projects_limit) } + it { should validate_numericality_of(:projects_limit) } + it { should allow_value(0).for(:projects_limit) } + it { should_not allow_value(-1).for(:projects_limit) } + + it { should ensure_length_of(:bio).is_within(0..255) } end describe "Respond to" do @@ -17,81 +31,46 @@ describe User do it { should respond_to(:private_token) } end - it "should return valid identifier" do - user = User.new(email: "test@mail.com") - user.identifier.should == "test_mail_com" - end - - it "should return identifier without + sign" do - user = User.new(email: "test+foo@mail.com") - user.identifier.should == "test_foo_mail_com" - end - - it "should execute callback when force_random_password specified" do - user = User.new(email: "test@mail.com", force_random_password: true) - user.should_receive(:generate_password) - user.save - end - - it "should not generate password by default" do - user = Factory(:user, password: 'abcdefg', password_confirmation: 'abcdefg') - user.password.should == 'abcdefg' - end - - it "should generate password when forcing random password" do - Devise.stub(:friendly_token).and_return('123456789') - user = User.create(email: "test1@mail.com", force_random_password: true) - user.password.should == user.password_confirmation - user.password.should == '12345678' - end - - it "should have authentication token" do - user = Factory(:user) - user.authentication_token.should_not == "" - end - - describe "dependent" do - before do - @user = Factory :user - @note = Factory :note, - author: @user, - project: Factory(:project) + describe '#identifier' do + it "should return valid identifier" do + user = build(:user, email: "test@mail.com") + user.identifier.should == "test_mail_com" end - it "should destroy all notes with user" do - Note.find_by_id(@note.id).should_not be_nil - @user.destroy - Note.find_by_id(@note.id).should be_nil + it "should return identifier without + sign" do + user = build(:user, email: "test+foo@mail.com") + user.identifier.should == "test_foo_mail_com" + end + + it "should conform to Gitolite's required identifier pattern" do + user = build(:user, email: "_test@example.com") + user.identifier.should == 'test_example_com' + end + end + + describe '#generate_password' do + it "should execute callback when force_random_password specified" do + user = build(:user, force_random_password: true) + user.should_receive(:generate_password) + user.save + end + + it "should not generate password by default" do + user = create(:user, password: 'abcdefg') + user.password.should == 'abcdefg' + end + + it "should generate password when forcing random password" do + Devise.stub(:friendly_token).and_return('123456789') + user = create(:user, password: 'abcdefg', force_random_password: true) + user.password.should == '12345678' + end + end + + describe 'authentication token' do + it "should have authentication token" do + user = Factory(:user) + user.authentication_token.should_not be_blank end end end -# == Schema Information -# -# Table name: users -# -# id :integer(4) not null, primary key -# email :string(255) default(""), not null -# encrypted_password :string(128) default(""), not null -# reset_password_token :string(255) -# reset_password_sent_at :datetime -# remember_created_at :datetime -# sign_in_count :integer(4) default(0) -# current_sign_in_at :datetime -# last_sign_in_at :datetime -# current_sign_in_ip :string(255) -# last_sign_in_ip :string(255) -# created_at :datetime not null -# updated_at :datetime not null -# name :string(255) -# admin :boolean(1) default(FALSE), not null -# projects_limit :integer(4) default(10) -# skype :string(255) default(""), not null -# linkedin :string(255) default(""), not null -# twitter :string(255) default(""), not null -# authentication_token :string(255) -# dark_scheme :boolean(1) default(FALSE), not null -# theme_id :integer(4) default(1), not null -# bio :string(255) -# blocked :boolean(1) default(FALSE), not null -# - diff --git a/spec/models/users_project_spec.rb b/spec/models/users_project_spec.rb index 87fbfbf2..33cb358e 100644 --- a/spec/models/users_project_spec.rb +++ b/spec/models/users_project_spec.rb @@ -7,7 +7,11 @@ describe UsersProject do end describe "Validation" do + let!(:users_project) { create(:users_project) } + it { should validate_presence_of(:user_id) } + it { should validate_uniqueness_of(:user_id).scoped_to(:project_id).with_message(/already exists/) } + it { should validate_presence_of(:project_id) } end @@ -16,15 +20,3 @@ describe UsersProject do it { should respond_to(:user_email) } end end -# == Schema Information -# -# Table name: users_projects -# -# id :integer(4) not null, primary key -# user_id :integer(4) not null -# project_id :integer(4) not null -# created_at :datetime not null -# updated_at :datetime not null -# project_access :integer(4) default(0), not null -# - diff --git a/spec/models/web_hook_spec.rb b/spec/models/web_hook_spec.rb index 88594761..3cba5b64 100644 --- a/spec/models/web_hook_spec.rb +++ b/spec/models/web_hook_spec.rb @@ -52,14 +52,3 @@ describe ProjectHook do end end end -# == Schema Information -# -# Table name: web_hooks -# -# id :integer(4) not null, primary key -# url :string(255) -# project_id :integer(4) -# created_at :datetime not null -# updated_at :datetime not null -# - diff --git a/spec/models/wiki_spec.rb b/spec/models/wiki_spec.rb index 892d0e8f..de6ce426 100644 --- a/spec/models/wiki_spec.rb +++ b/spec/models/wiki_spec.rb @@ -4,27 +4,13 @@ describe Wiki do describe "Associations" do it { should belong_to(:project) } it { should belong_to(:user) } + it { should have_many(:notes).dependent(:destroy) } end describe "Validation" do it { should validate_presence_of(:title) } + it { should ensure_length_of(:title).is_within(1..250) } it { should validate_presence_of(:content) } it { should validate_presence_of(:user_id) } end - - it { Factory(:wiki).should be_valid } end -# == Schema Information -# -# Table name: wikis -# -# id :integer(4) not null, primary key -# title :string(255) -# content :text -# project_id :integer(4) -# created_at :datetime not null -# updated_at :datetime not null -# slug :string(255) -# user_id :integer(4) -# - diff --git a/spec/monkeypatch.rb b/spec/monkeypatch.rb deleted file mode 100644 index 855a31f0..00000000 --- a/spec/monkeypatch.rb +++ /dev/null @@ -1,51 +0,0 @@ -# Stubbing Project <-> git host path -# create project using Factory only -class Project - def update_repository - true - end - - def destroy_repository - true - end - - def path_to_repo - File.join(Rails.root, "tmp", "tests", path) - end - - def satellite - @satellite ||= FakeSatellite.new - end -end - -class Key - def update_repository - true - end - - def repository_delete_key - true - end -end - -class UsersProject - def update_repository - true - end -end - -class FakeSatellite - def exists? - true - end - - def create - true - end -end - -class ProtectedBranch - def update_repository - true - end -end diff --git a/spec/models/activity_observer_spec.rb b/spec/observers/activity_observer_spec.rb similarity index 100% rename from spec/models/activity_observer_spec.rb rename to spec/observers/activity_observer_spec.rb diff --git a/spec/models/issue_observer_spec.rb b/spec/observers/issue_observer_spec.rb similarity index 58% rename from spec/models/issue_observer_spec.rb rename to spec/observers/issue_observer_spec.rb index c6a405f1..b5943f2c 100644 --- a/spec/models/issue_observer_spec.rb +++ b/spec/observers/issue_observer_spec.rb @@ -3,7 +3,8 @@ require 'spec_helper' describe IssueObserver do let(:some_user) { double(:user, id: 1) } let(:assignee) { double(:user, id: 2) } - let(:issue) { double(:issue, id: 42, assignee: assignee) } + let(:author) { double(:user, id: 3) } + let(:issue) { double(:issue, id: 42, assignee: assignee, author: author) } before(:each) { subject.stub(:current_user).and_return(some_user) } @@ -67,36 +68,90 @@ describe IssueObserver do end end - context 'a status "closed" note' do - it 'is created if the issue is being closed' do + context 'a status "closed"' do + it 'note is created if the issue is being closed' do issue.should_receive(:is_being_closed?).and_return(true) Note.should_receive(:create_status_change_note).with(issue, some_user, 'closed') subject.after_update(issue) end - it 'is not created if the issue is not being closed' do + it 'note is not created if the issue is not being closed' do issue.should_receive(:is_being_closed?).and_return(false) Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'closed') subject.after_update(issue) end + + it 'notification is delivered if the issue being closed' do + issue.stub(:is_being_closed?).and_return(true) + Notify.should_receive(:issue_status_changed_email).twice + Note.should_receive(:create_status_change_note).with(issue, some_user, 'closed') + + subject.after_update(issue) + end + + it 'notification is not delivered if the issue not being closed' do + issue.stub(:is_being_closed?).and_return(false) + Notify.should_not_receive(:issue_status_changed_email) + Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'closed') + + subject.after_update(issue) + end + + it 'notification is delivered only to author if the issue being closed' do + issue_without_assignee = double(:issue, id: 42, author: author, assignee: nil) + issue_without_assignee.stub(:is_being_reassigned?).and_return(false) + issue_without_assignee.stub(:is_being_closed?).and_return(true) + issue_without_assignee.stub(:is_being_reopened?).and_return(false) + Notify.should_receive(:issue_status_changed_email).once + Note.should_receive(:create_status_change_note).with(issue_without_assignee, some_user, 'closed') + + subject.after_update(issue_without_assignee) + end end - context 'a status "reopened" note' do - it 'is created if the issue is being reopened' do + context 'a status "reopened"' do + it 'note is created if the issue is being reopened' do issue.should_receive(:is_being_reopened?).and_return(true) Note.should_receive(:create_status_change_note).with(issue, some_user, 'reopened') subject.after_update(issue) end - it 'is not created if the issue is not being reopened' do + it 'note is not created if the issue is not being reopened' do issue.should_receive(:is_being_reopened?).and_return(false) Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'reopened') subject.after_update(issue) end + + it 'notification is delivered if the issue being reopened' do + issue.stub(:is_being_reopened?).and_return(true) + Notify.should_receive(:issue_status_changed_email).twice + Note.should_receive(:create_status_change_note).with(issue, some_user, 'reopened') + + subject.after_update(issue) + end + + it 'notification is not delivered if the issue not being reopened' do + issue.stub(:is_being_reopened?).and_return(false) + Notify.should_not_receive(:issue_status_changed_email) + Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'reopened') + + subject.after_update(issue) + end + + it 'notification is delivered only to author if the issue being reopened' do + issue_without_assignee = double(:issue, id: 42, author: author, assignee: nil) + issue_without_assignee.stub(:is_being_reassigned?).and_return(false) + issue_without_assignee.stub(:is_being_closed?).and_return(false) + issue_without_assignee.stub(:is_being_reopened?).and_return(true) + Notify.should_receive(:issue_status_changed_email).once + Note.should_receive(:create_status_change_note).with(issue_without_assignee, some_user, 'reopened') + + subject.after_update(issue_without_assignee) + end end end diff --git a/spec/observers/key_observer_spec.rb b/spec/observers/key_observer_spec.rb new file mode 100644 index 00000000..7f2a76a3 --- /dev/null +++ b/spec/observers/key_observer_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe KeyObserver do + before do + @key = double('Key', + identifier: 'admin_654654', + key: '== a vaild ssh key', + projects: [], + is_deploy_key: false + ) + + @gitolite = double('Gitlab::Gitolite', + set_key: true, + remove_key: true + ) + + @observer = KeyObserver.instance + @observer.stub(:git_host => @gitolite) + end + + context :after_save do + it do + @gitolite.should_receive(:set_key).with(@key.identifier, @key.key, @key.projects) + @observer.after_save(@key) + end + end + + context :after_destroy do + it do + @gitolite.should_receive(:remove_key).with(@key.identifier, @key.projects) + @observer.after_destroy(@key) + end + end +end diff --git a/spec/models/user_observer_spec.rb b/spec/observers/user_observer_spec.rb similarity index 74% rename from spec/models/user_observer_spec.rb rename to spec/observers/user_observer_spec.rb index 23dac98b..0420a250 100644 --- a/spec/models/user_observer_spec.rb +++ b/spec/observers/user_observer_spec.rb @@ -13,7 +13,7 @@ describe UserObserver do end context 'when a new user is created' do - let(:user) { double(:user, id: 42, password: 'P@ssword!') } + let(:user) { double(:user, id: 42, password: 'P@ssword!', name: 'John', email: 'u@mail.local') } let(:notification) { double :notification } it 'sends an email' do @@ -22,5 +22,10 @@ describe UserObserver do subject.after_create(user) end + + it 'trigger logger' do + Gitlab::AppLogger.should_receive(:info) + subject.after_create(user) + end end end diff --git a/spec/observers/users_project_observer_spec.rb b/spec/observers/users_project_observer_spec.rb new file mode 100644 index 00000000..07d71da8 --- /dev/null +++ b/spec/observers/users_project_observer_spec.rb @@ -0,0 +1,68 @@ +require 'spec_helper' + +describe UsersProjectObserver do + let(:users_project) { stub.as_null_object } + subject { UsersProjectObserver.instance } + + describe "#after_create" do + it "should called when UsersProject created" do + subject.should_receive(:after_create) + + UsersProject.observers.enable :users_project_observer do + create(:users_project) + end + end + + it "should send email to user" do + Event.stub(:create => true) + Notify.should_receive(:project_access_granted_email).and_return(stub(deliver: true)) + + subject.after_create(users_project) + end + + it "should create new event" do + Event.should_receive(:create).with( + project_id: users_project.project.id, + action: Event::Joined, + author_id: users_project.user.id + ) + + subject.after_create(users_project) + end + end + + describe "#after_update" do + it "should called when UsersProject updated" do + subject.should_receive(:after_update) + + UsersProject.observers.enable :users_project_observer do + create(:users_project).update_attribute(:project_access, 40) + end + end + + it "should send email to user" do + Notify.should_receive(:project_access_granted_email).with(users_project.id).and_return(double(deliver: true)) + + subject.after_update(users_project) + end + end + + describe "#after_destroy" do + it "should called when UsersProject destroyed" do + subject.should_receive(:after_destroy) + + UsersProject.observers.enable :users_project_observer do + create(:users_project).destroy + end + end + + it "should create new event" do + Event.should_receive(:create).with( + project_id: users_project.project.id, + action: Event::Left, + author_id: users_project.user.id + ) + subject.after_destroy(users_project) + end + end +end diff --git a/spec/requests/admin/admin_projects_spec.rb b/spec/requests/admin/admin_projects_spec.rb index 0ce66f5f..2edfb592 100644 --- a/spec/requests/admin/admin_projects_spec.rb +++ b/spec/requests/admin/admin_projects_spec.rb @@ -87,7 +87,7 @@ describe "Admin::Projects" do visit new_admin_project_path fill_in 'project_name', with: 'NewProject' fill_in 'project_code', with: 'NPR' - fill_in 'project_path', with: 'gitlabhq_1' + fill_in 'project_path', with: 'newproject' expect { click_button "Create project" }.to change { Project.count }.by(1) @project = Project.last end diff --git a/spec/requests/admin/security_spec.rb b/spec/requests/admin/security_spec.rb index 0c369740..63068326 100644 --- a/spec/requests/admin/security_spec.rb +++ b/spec/requests/admin/security_spec.rb @@ -2,20 +2,26 @@ require 'spec_helper' describe "Admin::Projects" do describe "GET /admin/projects" do - it { admin_projects_path.should be_allowed_for :admin } - it { admin_projects_path.should be_denied_for :user } - it { admin_projects_path.should be_denied_for :visitor } + subject { admin_projects_path } + + it { should be_allowed_for :admin } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /admin/users" do - it { admin_users_path.should be_allowed_for :admin } - it { admin_users_path.should be_denied_for :user } - it { admin_users_path.should be_denied_for :visitor } + subject { admin_users_path } + + it { should be_allowed_for :admin } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /admin/hooks" do - it { admin_hooks_path.should be_allowed_for :admin } - it { admin_hooks_path.should be_denied_for :user } - it { admin_hooks_path.should be_denied_for :visitor } + subject { admin_hooks_path } + + it { should be_allowed_for :admin } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end end diff --git a/spec/api/issues_spec.rb b/spec/requests/api/issues_spec.rb similarity index 68% rename from spec/api/issues_spec.rb rename to spec/requests/api/issues_spec.rb index f6d8e379..442e9c73 100644 --- a/spec/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -1,20 +1,24 @@ require 'spec_helper' describe Gitlab::API do + include ApiHelpers + let(:user) { Factory :user } let!(:project) { Factory :project, owner: user } let!(:issue) { Factory :issue, author: user, assignee: user, project: project } before { project.add_access(user, :read) } describe "GET /issues" do - it "should return authentication error" do - get "#{api_prefix}/issues" - response.status.should == 401 + context "when unauthenticated" do + it "should return authentication error" do + get api("/issues") + response.status.should == 401 + end end - describe "authenticated GET /issues" do + context "when authenticated" do it "should return an array of issues" do - get "#{api_prefix}/issues?private_token=#{user.private_token}" + get api("/issues", user) response.status.should == 200 json_response.should be_an Array json_response.first['title'].should == issue.title @@ -24,7 +28,7 @@ describe Gitlab::API do describe "GET /projects/:id/issues" do it "should return project issues" do - get "#{api_prefix}/projects/#{project.code}/issues?private_token=#{user.private_token}" + get api("/projects/#{project.code}/issues", user) response.status.should == 200 json_response.should be_an Array json_response.first['title'].should == issue.title @@ -33,7 +37,7 @@ describe Gitlab::API do describe "GET /projects/:id/issues/:issue_id" do it "should return a project issue by id" do - get "#{api_prefix}/projects/#{project.code}/issues/#{issue.id}?private_token=#{user.private_token}" + get api("/projects/#{project.code}/issues/#{issue.id}", user) response.status.should == 200 json_response['title'].should == issue.title end @@ -41,7 +45,7 @@ describe Gitlab::API do describe "POST /projects/:id/issues" do it "should create a new project issue" do - post "#{api_prefix}/projects/#{project.code}/issues?private_token=#{user.private_token}", + post api("/projects/#{project.code}/issues", user), title: 'new issue', labels: 'label, label2' response.status.should == 201 json_response['title'].should == 'new issue' @@ -52,7 +56,7 @@ describe Gitlab::API do describe "PUT /projects/:id/issues/:issue_id" do it "should update a project issue" do - put "#{api_prefix}/projects/#{project.code}/issues/#{issue.id}?private_token=#{user.private_token}", + put api("/projects/#{project.code}/issues/#{issue.id}", user), title: 'updated title', labels: 'label2', closed: 1 response.status.should == 200 json_response['title'].should == 'updated title' @@ -63,9 +67,8 @@ describe Gitlab::API do describe "DELETE /projects/:id/issues/:issue_id" do it "should delete a project issue" do - expect { - delete "#{api_prefix}/projects/#{project.code}/issues/#{issue.id}?private_token=#{user.private_token}" - }.to change { Issue.count }.by(-1) + delete api("/projects/#{project.code}/issues/#{issue.id}", user) + response.status.should == 405 end end end diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb new file mode 100644 index 00000000..cf5f65f0 --- /dev/null +++ b/spec/requests/api/milestones_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe Gitlab::API do + include ApiHelpers + + let(:user) { Factory :user } + let!(:project) { Factory :project, owner: user } + let!(:milestone) { Factory :milestone, project: project } + + before { project.add_access(user, :read) } + + describe "GET /projects/:id/milestones" do + it "should return project milestones" do + get api("/projects/#{project.code}/milestones", user) + response.status.should == 200 + json_response.should be_an Array + json_response.first['title'].should == milestone.title + end + end + + describe "GET /projects/:id/milestones/:milestone_id" do + it "should return a project milestone by id" do + get api("/projects/#{project.code}/milestones/#{milestone.id}", user) + response.status.should == 200 + json_response['title'].should == milestone.title + end + end + + describe "POST /projects/:id/milestones" do + it "should create a new project milestone" do + post api("/projects/#{project.code}/milestones", user), + title: 'new milestone' + response.status.should == 201 + json_response['title'].should == 'new milestone' + json_response['description'].should be_nil + end + end + + describe "PUT /projects/:id/milestones/:milestone_id" do + it "should update a project milestone" do + put api("/projects/#{project.code}/milestones/#{milestone.id}", user), + title: 'updated title' + response.status.should == 200 + json_response['title'].should == 'updated title' + end + end +end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb new file mode 100644 index 00000000..498bbad6 --- /dev/null +++ b/spec/requests/api/projects_spec.rb @@ -0,0 +1,281 @@ +require 'spec_helper' + +describe Gitlab::API do + include ApiHelpers + + let(:user) { Factory :user } + let(:user2) { Factory.create(:user) } + let(:user3) { Factory.create(:user) } + let!(:hook) { Factory :project_hook, project: project, url: "http://example.com" } + let!(:project) { Factory :project, owner: user } + let!(:snippet) { Factory :snippet, author: user, project: project, title: 'example' } + let!(:users_project) { Factory :users_project, user: user, project: project, project_access: UsersProject::MASTER } + let!(:users_project2) { Factory :users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER } + before { project.add_access(user, :read) } + + describe "GET /projects" do + context "when unauthenticated" do + it "should return authentication error" do + get api("/projects") + response.status.should == 401 + end + end + + context "when authenticated" do + it "should return an array of projects" do + get api("/projects", user) + response.status.should == 200 + json_response.should be_an Array + json_response.first['name'].should == project.name + json_response.first['owner']['email'].should == user.email + end + end + end + + describe "POST /projects" do + it "should create new project without code and path" do + expect { post api("/projects", user), name: 'foo' }.to change {Project.count}.by(1) + end + + it "should not create new project without name" do + expect { post api("/projects", user) }.to_not change {Project.count} + end + + it "should respond with 201 on success" do + post api("/projects", user), name: 'foo' + response.status.should == 201 + end + + it "should repsond with 404 on failure" do + post api("/projects", user) + response.status.should == 404 + end + + it "should assign attributes to project" do + project = Factory.attributes(:project, { + path: 'path', + code: 'code', + description: Faker::Lorem.sentence, + default_branch: 'stable', + issues_enabled: false, + wall_enabled: false, + merge_requests_enabled: false, + wiki_enabled: false + }) + + post api("/projects", user), project + + project.each_pair do |k,v| + json_response[k.to_s].should == v + end + end + end + + describe "GET /projects/:id" do + it "should return a project by id" do + get api("/projects/#{project.id}", user) + response.status.should == 200 + json_response['name'].should == project.name + json_response['owner']['email'].should == user.email + end + + it "should return a project by code name" do + get api("/projects/#{project.code}", user) + response.status.should == 200 + json_response['name'].should == project.name + end + + it "should return a 404 error if not found" do + get api("/projects/42", user) + response.status.should == 404 + json_response['message'].should == '404 Not Found' + end + end + + describe "GET /projects/:id/repository/branches" do + it "should return an array of project branches" do + get api("/projects/#{project.code}/repository/branches", user) + response.status.should == 200 + json_response.should be_an Array + json_response.first['name'].should == project.repo.heads.sort_by(&:name).first.name + end + end + + describe "GET /projects/:id/repository/branches/:branch" do + it "should return the branch information for a single branch" do + get api("/projects/#{project.code}/repository/branches/new_design", user) + response.status.should == 200 + + json_response['name'].should == 'new_design' + json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1' + end + end + + describe "GET /projects/:id/members" do + it "should return project team members" do + get api("/projects/#{project.code}/members", user) + response.status.should == 200 + json_response.should be_an Array + json_response.count.should == 2 + json_response.first['email'].should == user.email + end + end + + describe "GET /projects/:id/members/:user_id" do + it "should return project team member" do + get api("/projects/#{project.code}/members/#{user.id}", user) + response.status.should == 200 + json_response['email'].should == user.email + json_response['access_level'].should == UsersProject::MASTER + end + end + + describe "POST /projects/:id/members" do + it "should add user to project team" do + expect { + post api("/projects/#{project.code}/members", user), user_id: user2.id, + access_level: UsersProject::DEVELOPER + }.to change { UsersProject.count }.by(1) + + response.status.should == 201 + json_response['email'].should == user2.email + json_response['access_level'].should == UsersProject::DEVELOPER + end + end + + describe "PUT /projects/:id/members/:user_id" do + it "should update project team member" do + put api("/projects/#{project.code}/members/#{user3.id}", user), access_level: UsersProject::MASTER + response.status.should == 200 + json_response['email'].should == user3.email + json_response['access_level'].should == UsersProject::MASTER + end + end + + describe "DELETE /projects/:id/members/:user_id" do + it "should remove user from project team" do + expect { + delete api("/projects/#{project.code}/members/#{user3.id}", user) + }.to change { UsersProject.count }.by(-1) + end + end + + describe "GET /projects/:id/hooks" do + it "should return project hooks" do + get api("/projects/#{project.code}/hooks", user) + + response.status.should == 200 + + json_response.should be_an Array + json_response.count.should == 1 + json_response.first['url'].should == "http://example.com" + end + end + + describe "POST /projects/:id/users" do + it "should add hook to project" do + expect { + post api("/projects/#{project.code}/hooks", user), + "url" => "http://example.com" + }.to change {project.hooks.count}.by(1) + end + end + + describe "DELETE /projects/:id/hooks" do + it "should delete hook from project" do + expect { + delete api("/projects/#{project.code}/hooks", user), + hook_id: hook.id + }.to change {project.hooks.count}.by(-1) + end + end + + describe "GET /projects/:id/repository/tags" do + it "should return an array of project tags" do + get api("/projects/#{project.code}/repository/tags", user) + response.status.should == 200 + json_response.should be_an Array + json_response.first['name'].should == project.repo.tags.sort_by(&:name).reverse.first.name + end + end + + describe "GET /projects/:id/repository/commits" do + context "authorized user" do + before { project.add_access(user2, :read) } + + it "should return project commits" do + get api("/projects/#{project.code}/repository/commits", user) + response.status.should == 200 + + json_response.should be_an Array + json_response.first['id'].should == project.commit.id + end + end + + context "unauthorized user" do + it "should not return project commits" do + get api("/projects/#{project.code}/repository/commits") + response.status.should == 401 + end + end + end + + describe "GET /projects/:id/snippets/:snippet_id" do + it "should return a project snippet" do + get api("/projects/#{project.code}/snippets/#{snippet.id}", user) + response.status.should == 200 + json_response['title'].should == snippet.title + end + end + + describe "POST /projects/:id/snippets" do + it "should create a new project snippet" do + post api("/projects/#{project.code}/snippets", user), + title: 'api test', file_name: 'sample.rb', code: 'test' + response.status.should == 201 + json_response['title'].should == 'api test' + end + end + + describe "PUT /projects/:id/snippets" do + it "should update an existing project snippet" do + put api("/projects/#{project.code}/snippets/#{snippet.id}", user), + code: 'updated code' + response.status.should == 200 + json_response['title'].should == 'example' + snippet.reload.content.should == 'updated code' + end + end + + describe "DELETE /projects/:id/snippets/:snippet_id" do + it "should delete existing project snippet" do + expect { + delete api("/projects/#{project.code}/snippets/#{snippet.id}", user) + }.to change { Snippet.count }.by(-1) + end + end + + describe "GET /projects/:id/snippets/:snippet_id/raw" do + it "should get a raw project snippet" do + get api("/projects/#{project.code}/snippets/#{snippet.id}/raw", user) + response.status.should == 200 + end + end + + describe "GET /projects/:id/:sha/blob" do + it "should get the raw file contents" do + get api("/projects/#{project.code}/repository/commits/master/blob?filepath=README.md", user) + response.status.should == 200 + end + + it "should return 404 for invalid branch_name" do + get api("/projects/#{project.code}/repository/commits/invalid_branch_name/blob?filepath=README.md", user) + response.status.should == 404 + end + + it "should return 404 for invalid file" do + get api("/projects/#{project.code}/repository/commits/master/blob?filepath=README.invalid", user) + response.status.should == 404 + end + end +end diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb new file mode 100644 index 00000000..f251f392 --- /dev/null +++ b/spec/requests/api/session_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe Gitlab::API do + include ApiHelpers + + let(:user) { Factory :user } + + describe "POST /session" do + context "when valid password" do + it "should return private token" do + post api("/session"), email: user.email, password: '123456' + response.status.should == 201 + + json_response['email'].should == user.email + json_response['private_token'].should == user.private_token + end + end + + context "when invalid password" do + it "should return authentication error" do + post api("/session"), email: user.email, password: '123' + response.status.should == 401 + + json_response['email'].should be_nil + json_response['private_token'].should be_nil + end + end + + context "when empty password" do + it "should return authentication error" do + post api("/session"), email: user.email + response.status.should == 401 + + json_response['email'].should be_nil + json_response['private_token'].should be_nil + end + end + end +end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb new file mode 100644 index 00000000..243f70f5 --- /dev/null +++ b/spec/requests/api/users_spec.rb @@ -0,0 +1,106 @@ +require 'spec_helper' + +describe Gitlab::API do + include ApiHelpers + + let(:user) { Factory :user } + let(:key) { Factory :key, user: user } + + describe "GET /users" do + context "when unauthenticated" do + it "should return authentication error" do + get api("/users") + response.status.should == 401 + end + end + + context "when authenticated" do + it "should return an array of users" do + get api("/users", user) + response.status.should == 200 + json_response.should be_an Array + json_response.first['email'].should == user.email + end + end + end + + describe "GET /users/:id" do + it "should return a user by id" do + get api("/users/#{user.id}", user) + response.status.should == 200 + json_response['email'].should == user.email + end + end + + describe "GET /user" do + it "should return current user" do + get api("/user", user) + response.status.should == 200 + json_response['email'].should == user.email + end + end + + describe "GET /user/keys" do + context "when unauthenticated" do + it "should return authentication error" do + get api("/user/keys") + response.status.should == 401 + end + end + + context "when authenticated" do + it "should return array of ssh keys" do + user.keys << key + user.save + get api("/user/keys", user) + response.status.should == 200 + json_response.should be_an Array + json_response.first["title"].should == key.title + end + end + end + + describe "GET /user/keys/:id" do + it "should returm single key" do + user.keys << key + user.save + get api("/user/keys/#{key.id}", user) + response.status.should == 200 + json_response["title"].should == key.title + end + + it "should return 404 Not Found within invalid ID" do + get api("/user/keys/42", user) + response.status.should == 404 + end + end + + describe "POST /user/keys" do + it "should not create invalid ssh key" do + post api("/user/keys", user), { title: "invalid key" } + response.status.should == 404 + end + + it "should create ssh key" do + key_attrs = Factory.attributes :key + expect { + post api("/user/keys", user), key_attrs + }.to change{ user.keys.count }.by(1) + end + end + + describe "DELETE /user/keys/:id" do + it "should delete existed key" do + user.keys << key + user.save + expect { + delete api("/user/keys/#{key.id}", user) + }.to change{user.keys.count}.by(-1) + end + + it "should return 404 Not Found within invalid ID" do + delete api("/user/keys/42", user) + response.status.should == 404 + end + end +end diff --git a/spec/requests/atom/dashboard_issues_spec.rb b/spec/requests/atom/dashboard_issues_spec.rb index 9b4ffc0e..8d1111fc 100644 --- a/spec/requests/atom/dashboard_issues_spec.rb +++ b/spec/requests/atom/dashboard_issues_spec.rb @@ -1,46 +1,23 @@ require 'spec_helper' -describe "User Issues Dashboard" do +describe "Dashboard Issues Feed" do describe "GET /issues" do - before do + let!(:user) { Factory :user } + let!(:project1) { Factory :project } + let!(:project2) { Factory :project } + let!(:issue1) { Factory :issue, author: user, assignee: user, project: project1 } + let!(:issue2) { Factory :issue, author: user, assignee: user, project: project2 } - login_as :user - - @project1 = Factory :project, - path: "project1", - code: "TEST1" - - @project2 = Factory :project, - path: "project2", - code: "TEST2" - - @project1.add_access(@user, :read, :write) - @project2.add_access(@user, :read, :write) - - @issue1 = Factory :issue, - author: @user, - assignee: @user, - project: @project1 - - @issue2 = Factory :issue, - author: @user, - assignee: @user, - project: @project2 - - visit dashboard_issues_path - end - - describe "atom feed", js: false do + describe "atom feed" do it "should render atom feed via private token" do - logout - visit dashboard_issues_path(:atom, private_token: @user.private_token) + visit dashboard_issues_path(:atom, private_token: user.private_token) page.response_headers['Content-Type'].should have_content("application/atom+xml") - page.body.should have_selector("title", text: "#{@user.name} issues") - page.body.should have_selector("author email", text: @issue1.author_email) - page.body.should have_selector("entry summary", text: @issue1.title) - page.body.should have_selector("author email", text: @issue2.author_email) - page.body.should have_selector("entry summary", text: @issue2.title) + page.body.should have_selector("title", text: "#{user.name} issues") + page.body.should have_selector("author email", text: issue1.author_email) + page.body.should have_selector("entry summary", text: issue1.title) + page.body.should have_selector("author email", text: issue2.author_email) + page.body.should have_selector("entry summary", text: issue2.title) end end end diff --git a/spec/requests/atom/dashboard_spec.rb b/spec/requests/atom/dashboard_spec.rb index 00c7a525..9459dd01 100644 --- a/spec/requests/atom/dashboard_spec.rb +++ b/spec/requests/atom/dashboard_spec.rb @@ -1,27 +1,21 @@ require 'spec_helper' -describe "User Dashboard" do - before { login_as :user } - +describe "Dashboard Feed" do describe "GET /" do - before do - @project = Factory :project, owner: @user - @project.add_access(@user, :read) - visit dashboard_path + let!(:user) { Factory :user } + + context "projects atom feed via private token" do + it "should render projects atom feed" do + visit dashboard_path(:atom, private_token: user.private_token) + page.body.should have_selector("feed title") + end end - it "should render projects atom feed via private token" do - logout - - visit dashboard_path(:atom, private_token: @user.private_token) - page.body.should have_selector("feed title") - end - - it "should not render projects page via private token" do - logout - - visit dashboard_path(private_token: @user.private_token) - current_path.should == new_user_session_path + context "projects page via private token" do + it "should redirect to login page" do + visit dashboard_path(private_token: user.private_token) + current_path.should == new_user_session_path + end end end end diff --git a/spec/requests/atom/issues_spec.rb b/spec/requests/atom/issues_spec.rb index 468d1b22..c8671979 100644 --- a/spec/requests/atom/issues_spec.rb +++ b/spec/requests/atom/issues_spec.rb @@ -1,40 +1,34 @@ require 'spec_helper' -describe "Issues" do - let(:project) { Factory :project } - - before do - login_as :user - project.add_access(@user, :read, :write) - end - +describe "Issues Feed" do describe "GET /issues" do - before do - @issue = Factory :issue, - author: @user, - assignee: @user, - project: project + let!(:user) { Factory :user } + let!(:project) { Factory :project, owner: user } + let!(:issue) { Factory :issue, author: user, project: project } - visit project_issues_path(project) + before { project.add_access(user, :read, :write) } + + context "when authenticated" do + it "should render atom feed" do + login_with user + visit project_issues_path(project, :atom) + + page.response_headers['Content-Type'].should have_content("application/atom+xml") + page.body.should have_selector("title", text: "#{project.name} issues") + page.body.should have_selector("author email", text: issue.author_email) + page.body.should have_selector("entry summary", text: issue.title) + end end - it "should render atom feed" do - visit project_issues_path(project, :atom) + context "when authenticated via private token" do + it "should render atom feed" do + visit project_issues_path(project, :atom, private_token: user.private_token) - page.response_headers['Content-Type'].should have_content("application/atom+xml") - page.body.should have_selector("title", text: "#{project.name} issues") - page.body.should have_selector("author email", text: @issue.author_email) - page.body.should have_selector("entry summary", text: @issue.title) - end - - it "should render atom feed via private token" do - logout - visit project_issues_path(project, :atom, private_token: @user.private_token) - - page.response_headers['Content-Type'].should have_content("application/atom+xml") - page.body.should have_selector("title", text: "#{project.name} issues") - page.body.should have_selector("author email", text: @issue.author_email) - page.body.should have_selector("entry summary", text: @issue.title) + page.response_headers['Content-Type'].should have_content("application/atom+xml") + page.body.should have_selector("title", text: "#{project.name} issues") + page.body.should have_selector("author email", text: issue.author_email) + page.body.should have_selector("entry summary", text: issue.title) + end end end end diff --git a/spec/requests/gitlab_flavored_markdown_spec.rb b/spec/requests/gitlab_flavored_markdown_spec.rb index 1076e90c..68d354b7 100644 --- a/spec/requests/gitlab_flavored_markdown_spec.rb +++ b/spec/requests/gitlab_flavored_markdown_spec.rb @@ -25,6 +25,7 @@ describe "Gitlab Flavored Markdown" do @tag_name = "gfm-test-tag" r.git.native(:tag, {}, @tag_name, commit.id) end + after do # delete test branch and tag project.repo.git.native(:branch, {D: true}, @branch_name) diff --git a/spec/requests/projects_deploy_keys_spec.rb b/spec/requests/projects_deploy_keys_spec.rb index 0fea7b46..894aa6d3 100644 --- a/spec/requests/projects_deploy_keys_spec.rb +++ b/spec/requests/projects_deploy_keys_spec.rb @@ -42,7 +42,7 @@ describe "Projects", "DeployKeys" do describe "fill in" do before do fill_in "key_title", with: "laptop" - fill_in "key_key", with: "publickey234=" + fill_in "key_key", with: "ssh-rsa publickey234=" end it { expect { click_button "Save" }.to change {Key.count}.by(1) } @@ -55,12 +55,12 @@ describe "Projects", "DeployKeys" do end end - describe "Show page" do + describe "Show page" do before do @key = Factory :key, project: project - visit project_deploy_key_path(project, @key) + visit project_deploy_key_path(project, @key) end - + it { page.should have_content @key.title } it { page.should have_content @key.key[0..10] } end diff --git a/spec/requests/projects_spec.rb b/spec/requests/projects_spec.rb index 63f8a696..92e89a16 100644 --- a/spec/requests/projects_spec.rb +++ b/spec/requests/projects_spec.rb @@ -3,6 +3,16 @@ require 'spec_helper' describe "Projects" do before { login_as :user } + describe 'GET /project/new' do + it "should work autocomplete", :js => true do + visit new_project_path + + fill_in 'project_name', with: 'Awesome' + find("#project_path").value.should == 'awesome' + find("#project_code").value.should == 'awesome' + end + end + describe "GET /projects/show" do before do @project = Factory :project, owner: @user diff --git a/spec/requests/security/profile_access_spec.rb b/spec/requests/security/profile_access_spec.rb index b8ed27f0..69c1c29c 100644 --- a/spec/requests/security/profile_access_spec.rb +++ b/spec/requests/security/profile_access_spec.rb @@ -11,24 +11,30 @@ describe "Users Security" do end describe "GET /keys" do - it { keys_path.should be_allowed_for @u1 } - it { keys_path.should be_allowed_for :admin } - it { keys_path.should be_allowed_for :user } - it { keys_path.should be_denied_for :visitor } + subject { keys_path } + + it { should be_allowed_for @u1 } + it { should be_allowed_for :admin } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } end describe "GET /profile" do - it { profile_path.should be_allowed_for @u1 } - it { profile_path.should be_allowed_for :admin } - it { profile_path.should be_allowed_for :user } - it { profile_path.should be_denied_for :visitor } + subject { profile_path } + + it { should be_allowed_for @u1 } + it { should be_allowed_for :admin } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } end - describe "GET /profile/password" do - it { profile_password_path.should be_allowed_for @u1 } - it { profile_password_path.should be_allowed_for :admin } - it { profile_password_path.should be_allowed_for :user } - it { profile_password_path.should be_denied_for :visitor } + describe "GET /profile/account" do + subject { profile_account_path } + + it { should be_allowed_for @u1 } + it { should be_allowed_for :admin } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } end end end diff --git a/spec/requests/security/project_access_spec.rb b/spec/requests/security/project_access_spec.rb index d503cf85..af0d5fcd 100644 --- a/spec/requests/security/project_access_spec.rb +++ b/spec/requests/security/project_access_spec.rb @@ -26,64 +26,76 @@ describe "Application access" do end describe "GET /project_code" do - it { project_path(@project).should be_allowed_for @u1 } - it { project_path(@project).should be_allowed_for @u3 } - it { project_path(@project).should be_denied_for :admin } - it { project_path(@project).should be_denied_for @u2 } - it { project_path(@project).should be_denied_for :user } - it { project_path(@project).should be_denied_for :visitor } + subject { project_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/master/tree" do - it { tree_project_ref_path(@project, @project.root_ref).should be_allowed_for @u1 } - it { tree_project_ref_path(@project, @project.root_ref).should be_allowed_for @u3 } - it { tree_project_ref_path(@project, @project.root_ref).should be_denied_for :admin } - it { tree_project_ref_path(@project, @project.root_ref).should be_denied_for @u2 } - it { tree_project_ref_path(@project, @project.root_ref).should be_denied_for :user } - it { tree_project_ref_path(@project, @project.root_ref).should be_denied_for :visitor } + subject { tree_project_ref_path(@project, @project.root_ref) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/commits" do - it { project_commits_path(@project).should be_allowed_for @u1 } - it { project_commits_path(@project).should be_allowed_for @u3 } - it { project_commits_path(@project).should be_denied_for :admin } - it { project_commits_path(@project).should be_denied_for @u2 } - it { project_commits_path(@project).should be_denied_for :user } - it { project_commits_path(@project).should be_denied_for :visitor } + subject { project_commits_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/commit" do - it { project_commit_path(@project, @project.commit.id).should be_allowed_for @u1 } - it { project_commit_path(@project, @project.commit.id).should be_allowed_for @u3 } - it { project_commit_path(@project, @project.commit.id).should be_denied_for :admin } - it { project_commit_path(@project, @project.commit.id).should be_denied_for @u2 } - it { project_commit_path(@project, @project.commit.id).should be_denied_for :user } - it { project_commit_path(@project, @project.commit.id).should be_denied_for :visitor } + subject { project_commit_path(@project, @project.commit.id) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/team" do - it { team_project_path(@project).should be_allowed_for @u1 } - it { team_project_path(@project).should be_allowed_for @u3 } - it { team_project_path(@project).should be_denied_for :admin } - it { team_project_path(@project).should be_denied_for @u2 } - it { team_project_path(@project).should be_denied_for :user } - it { team_project_path(@project).should be_denied_for :visitor } + subject { project_team_index_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/wall" do - it { wall_project_path(@project).should be_allowed_for @u1 } - it { wall_project_path(@project).should be_allowed_for @u3 } - it { wall_project_path(@project).should be_denied_for :admin } - it { wall_project_path(@project).should be_denied_for @u2 } - it { wall_project_path(@project).should be_denied_for :user } - it { wall_project_path(@project).should be_denied_for :visitor } + subject { wall_project_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/blob" do before do - @commit = @project.commit - @path = @commit.tree.contents.select { |i| i.is_a?(Grit::Blob)}.first.name - @blob_path = blob_project_ref_path(@project, @commit.id, path: @path) + commit = @project.commit + path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob)}.first.name + @blob_path = blob_project_ref_path(@project, commit.id, path: path) end it { @blob_path.should be_allowed_for @u1 } @@ -95,93 +107,113 @@ describe "Application access" do end describe "GET /project_code/edit" do - it { edit_project_path(@project).should be_allowed_for @u1 } - it { edit_project_path(@project).should be_denied_for @u3 } - it { edit_project_path(@project).should be_denied_for :admin } - it { edit_project_path(@project).should be_denied_for @u2 } - it { edit_project_path(@project).should be_denied_for :user } - it { edit_project_path(@project).should be_denied_for :visitor } + subject { edit_project_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_denied_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/deploy_keys" do - it { project_deploy_keys_path(@project).should be_allowed_for @u1 } - it { project_deploy_keys_path(@project).should be_denied_for @u3 } - it { project_deploy_keys_path(@project).should be_denied_for :admin } - it { project_deploy_keys_path(@project).should be_denied_for @u2 } - it { project_deploy_keys_path(@project).should be_denied_for :user } - it { project_deploy_keys_path(@project).should be_denied_for :visitor } + subject { project_deploy_keys_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_denied_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/issues" do - it { project_issues_path(@project).should be_allowed_for @u1 } - it { project_issues_path(@project).should be_allowed_for @u3 } - it { project_issues_path(@project).should be_denied_for :admin } - it { project_issues_path(@project).should be_denied_for @u2 } - it { project_issues_path(@project).should be_denied_for :user } - it { project_issues_path(@project).should be_denied_for :visitor } + subject { project_issues_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/snippets" do - it { project_snippets_path(@project).should be_allowed_for @u1 } - it { project_snippets_path(@project).should be_allowed_for @u3 } - it { project_snippets_path(@project).should be_denied_for :admin } - it { project_snippets_path(@project).should be_denied_for @u2 } - it { project_snippets_path(@project).should be_denied_for :user } - it { project_snippets_path(@project).should be_denied_for :visitor } + subject { project_snippets_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/merge_requests" do - it { project_merge_requests_path(@project).should be_allowed_for @u1 } - it { project_merge_requests_path(@project).should be_allowed_for @u3 } - it { project_merge_requests_path(@project).should be_denied_for :admin } - it { project_merge_requests_path(@project).should be_denied_for @u2 } - it { project_merge_requests_path(@project).should be_denied_for :user } - it { project_merge_requests_path(@project).should be_denied_for :visitor } + subject { project_merge_requests_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/repository" do - it { project_repository_path(@project).should be_allowed_for @u1 } - it { project_repository_path(@project).should be_allowed_for @u3 } - it { project_repository_path(@project).should be_denied_for :admin } - it { project_repository_path(@project).should be_denied_for @u2 } - it { project_repository_path(@project).should be_denied_for :user } - it { project_repository_path(@project).should be_denied_for :visitor } + subject { project_repository_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/repository/branches" do - it { branches_project_repository_path(@project).should be_allowed_for @u1 } - it { branches_project_repository_path(@project).should be_allowed_for @u3 } - it { branches_project_repository_path(@project).should be_denied_for :admin } - it { branches_project_repository_path(@project).should be_denied_for @u2 } - it { branches_project_repository_path(@project).should be_denied_for :user } - it { branches_project_repository_path(@project).should be_denied_for :visitor } + subject { branches_project_repository_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/repository/tags" do - it { tags_project_repository_path(@project).should be_allowed_for @u1 } - it { tags_project_repository_path(@project).should be_allowed_for @u3 } - it { tags_project_repository_path(@project).should be_denied_for :admin } - it { tags_project_repository_path(@project).should be_denied_for @u2 } - it { tags_project_repository_path(@project).should be_denied_for :user } - it { tags_project_repository_path(@project).should be_denied_for :visitor } + subject { tags_project_repository_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/hooks" do - it { project_hooks_path(@project).should be_allowed_for @u1 } - it { project_hooks_path(@project).should be_allowed_for @u3 } - it { project_hooks_path(@project).should be_denied_for :admin } - it { project_hooks_path(@project).should be_denied_for @u2 } - it { project_hooks_path(@project).should be_denied_for :user } - it { project_hooks_path(@project).should be_denied_for :visitor } + subject { project_hooks_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end describe "GET /project_code/files" do - it { files_project_path(@project).should be_allowed_for @u1 } - it { files_project_path(@project).should be_allowed_for @u3 } - it { files_project_path(@project).should be_denied_for :admin } - it { files_project_path(@project).should be_denied_for @u2 } - it { files_project_path(@project).should be_denied_for :user } - it { files_project_path(@project).should be_denied_for :visitor } + subject { files_project_path(@project) } + + it { should be_allowed_for @u1 } + it { should be_allowed_for @u3 } + it { should be_denied_for :admin } + it { should be_denied_for @u2 } + it { should be_denied_for :user } + it { should be_denied_for :visitor } end end end diff --git a/spec/roles/issue_commonality_spec.rb b/spec/roles/issue_commonality_spec.rb new file mode 100644 index 00000000..77b98b46 --- /dev/null +++ b/spec/roles/issue_commonality_spec.rb @@ -0,0 +1,69 @@ +require 'spec_helper' + +describe Issue, "IssueCommonality" do + let(:issue) { create(:issue) } + + describe "Associations" do + it { should belong_to(:project) } + it { should belong_to(:author) } + it { should belong_to(:assignee) } + it { should have_many(:notes).dependent(:destroy) } + end + + describe "Validation" do + it { should validate_presence_of(:project_id) } + it { should validate_presence_of(:author_id) } + it { should validate_presence_of(:title) } + it { should ensure_length_of(:title).is_at_least(0).is_at_most(255) } + end + + describe "Scope" do + it { described_class.should respond_to(:opened) } + it { described_class.should respond_to(:closed) } + it { described_class.should respond_to(:assigned) } + end + + it "has an :author_id_of_changes accessor" do + issue.should respond_to(:author_id_of_changes) + issue.should respond_to(:author_id_of_changes=) + end + + describe ".search" do + let!(:searchable_issue) { create(:issue, title: "Searchable issue") } + + it "matches by title" do + described_class.search('able').all.should == [searchable_issue] + end + end + + describe "#today?" do + it "returns true when created today" do + # Avoid timezone differences and just return exactly what we want + Date.stub(:today).and_return(issue.created_at.to_date) + issue.today?.should be_true + end + + it "returns false when not created today" do + Date.stub(:today).and_return(Date.yesterday) + issue.today?.should be_false + end + end + + describe "#new?" do + it "returns true when created today and record hasn't been updated" do + issue.stub(:today?).and_return(true) + issue.new?.should be_true + end + + it "returns false when not created today" do + issue.stub(:today?).and_return(false) + issue.new?.should be_false + end + + it "returns false when record has been updated" do + issue.stub(:today?).and_return(true) + issue.touch + issue.new?.should be_false + end + end +end diff --git a/spec/roles/repository_spec.rb b/spec/roles/repository_spec.rb new file mode 100644 index 00000000..0fda57a3 --- /dev/null +++ b/spec/roles/repository_spec.rb @@ -0,0 +1,72 @@ +require 'spec_helper' + +describe Project, "Repository" do + let(:project) { build(:project) } + + describe "#empty_repo?" do + it "should return true if the repo doesn't exist" do + project.stub(repo_exists?: false, has_commits?: true) + project.should be_empty_repo + end + + it "should return true if the repo has commits" do + project.stub(repo_exists?: true, has_commits?: false) + project.should be_empty_repo + end + + it "should return false if the repo exists and has commits" do + project.stub(repo_exists?: true, has_commits?: true) + project.should_not be_empty_repo + end + end + + describe "#discover_default_branch" do + let(:master) { double(name: 'master') } + let(:stable) { double(name: 'stable') } + + it "returns 'master' when master exists" do + project.should_receive(:heads).and_return([stable, master]) + project.discover_default_branch.should == 'master' + end + + it "returns non-master when master exists but default branch is set to something else" do + project.default_branch = 'stable' + project.should_receive(:heads).and_return([stable, master]) + project.discover_default_branch.should == 'stable' + end + + it "returns a non-master branch when only one exists" do + project.should_receive(:heads).and_return([stable]) + project.discover_default_branch.should == 'stable' + end + + it "returns nil when no branch exists" do + project.should_receive(:heads).and_return([]) + project.discover_default_branch.should be_nil + end + end + + describe "#root_ref" do + it "returns default_branch when set" do + project.default_branch = 'stable' + project.root_ref.should == 'stable' + end + + it "returns 'master' when default_branch is nil" do + project.default_branch = nil + project.root_ref.should == 'master' + end + end + + describe "#root_ref?" do + it "returns true when branch is root_ref" do + project.default_branch = 'stable' + project.root_ref?('stable').should be_true + end + + it "returns false when branch is not root_ref" do + project.default_branch = nil + project.root_ref?('stable').should be_false + end + end +end diff --git a/spec/roles/votes_spec.rb b/spec/roles/votes_spec.rb new file mode 100644 index 00000000..98666022 --- /dev/null +++ b/spec/roles/votes_spec.rb @@ -0,0 +1,132 @@ +require 'spec_helper' + +describe Issue do + let(:issue) { create(:issue) } + + describe "#upvotes" do + it "with no notes has a 0/0 score" do + issue.upvotes.should == 0 + end + + it "should recognize non-+1 notes" do + issue.notes << create(:note, note: "No +1 here") + issue.should have(1).note + issue.notes.first.upvote?.should be_false + issue.upvotes.should == 0 + end + + it "should recognize a single +1 note" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.upvotes.should == 1 + end + + it "should recognize multiple +1 notes" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.notes << create(:note, note: "+1 I want this") + issue.upvotes.should == 2 + end + end + + describe "#downvotes" do + it "with no notes has a 0/0 score" do + issue.downvotes.should == 0 + end + + it "should recognize non--1 notes" do + issue.notes << create(:note, note: "Almost got a -1") + issue.should have(1).note + issue.notes.first.downvote?.should be_false + issue.downvotes.should == 0 + end + + it "should recognize a single -1 note" do + issue.notes << create(:note, note: "-1 This is bad") + issue.downvotes.should == 1 + end + + it "should recognize multiple -1 notes" do + issue.notes << create(:note, note: "-1 This is bad") + issue.notes << create(:note, note: "-1 Away with this") + issue.downvotes.should == 2 + end + end + + describe "#votes_count" do + it "with no notes has a 0/0 score" do + issue.votes_count.should == 0 + end + + it "should recognize non notes" do + issue.notes << create(:note, note: "No +1 here") + issue.should have(1).note + issue.votes_count.should == 0 + end + + it "should recognize a single +1 note" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.votes_count.should == 1 + end + + it "should recognize a single -1 note" do + issue.notes << create(:note, note: "-1 This is bad") + issue.votes_count.should == 1 + end + + it "should recognize multiple notes" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.notes << create(:note, note: "-1 This is bad") + issue.notes << create(:note, note: "+1 I want this") + issue.votes_count.should == 3 + end + end + + describe "#upvotes_in_percent" do + it "with no notes has a 0% score" do + issue.upvotes_in_percent.should == 0 + end + + it "should count a single 1 note as 100%" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.upvotes_in_percent.should == 100 + end + + it "should count multiple +1 notes as 100%" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.notes << create(:note, note: "+1 I want this") + issue.upvotes_in_percent.should == 100 + end + + it "should count fractions for multiple +1 and -1 notes correctly" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.notes << create(:note, note: "+1 I want this") + issue.notes << create(:note, note: "-1 This is bad") + issue.notes << create(:note, note: "+1 me too") + issue.upvotes_in_percent.should == 75 + end + end + + describe "#downvotes_in_percent" do + it "with no notes has a 0% score" do + issue.downvotes_in_percent.should == 0 + end + + it "should count a single -1 note as 100%" do + issue.notes << create(:note, note: "-1 This is bad") + issue.downvotes_in_percent.should == 100 + end + + it "should count multiple -1 notes as 100%" do + issue.notes << create(:note, note: "-1 This is bad") + issue.notes << create(:note, note: "-1 Away with this") + issue.downvotes_in_percent.should == 100 + end + + it "should count fractions for multiple +1 and -1 notes correctly" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.notes << create(:note, note: "+1 I want this") + issue.notes << create(:note, note: "-1 This is bad") + issue.notes << create(:note, note: "+1 me too") + issue.downvotes_in_percent.should == 25 + end + end +end diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb new file mode 100644 index 00000000..60261c7a --- /dev/null +++ b/spec/routing/admin_routing_spec.rb @@ -0,0 +1,166 @@ +require 'spec_helper' + +# team_update_admin_user PUT /admin/users/:id/team_update(.:format) admin/users#team_update +# block_admin_user PUT /admin/users/:id/block(.:format) admin/users#block +# unblock_admin_user PUT /admin/users/:id/unblock(.:format) admin/users#unblock +# admin_users GET /admin/users(.:format) admin/users#index +# POST /admin/users(.:format) admin/users#create +# new_admin_user GET /admin/users/new(.:format) admin/users#new +# edit_admin_user GET /admin/users/:id/edit(.:format) admin/users#edit +# admin_user GET /admin/users/:id(.:format) admin/users#show +# PUT /admin/users/:id(.:format) admin/users#update +# DELETE /admin/users/:id(.:format) admin/users#destroy +describe Admin::UsersController, "routing" do + it "to #team_update" do + put("/admin/users/1/team_update").should route_to('admin/users#team_update', id: '1') + end + + it "to #block" do + put("/admin/users/1/block").should route_to('admin/users#block', id: '1') + end + + it "to #unblock" do + put("/admin/users/1/unblock").should route_to('admin/users#unblock', id: '1') + end + + it "to #index" do + get("/admin/users").should route_to('admin/users#index') + end + + it "to #show" do + get("/admin/users/1").should route_to('admin/users#show', id: '1') + end + + it "to #create" do + post("/admin/users").should route_to('admin/users#create') + end + + it "to #new" do + get("/admin/users/new").should route_to('admin/users#new') + end + + it "to #edit" do + get("/admin/users/1/edit").should route_to('admin/users#edit', id: '1') + end + + it "to #show" do + get("/admin/users/1").should route_to('admin/users#show', id: '1') + end + + it "to #update" do + put("/admin/users/1").should route_to('admin/users#update', id: '1') + end + + it "to #destroy" do + delete("/admin/users/1").should route_to('admin/users#destroy', id: '1') + end +end + +# team_admin_project GET /admin/projects/:id/team(.:format) admin/projects#team {:id=>/[^\/]+/} +# team_update_admin_project PUT /admin/projects/:id/team_update(.:format) admin/projects#team_update {:id=>/[^\/]+/} +# admin_projects GET /admin/projects(.:format) admin/projects#index {:id=>/[^\/]+/} +# POST /admin/projects(.:format) admin/projects#create {:id=>/[^\/]+/} +# new_admin_project GET /admin/projects/new(.:format) admin/projects#new {:id=>/[^\/]+/} +# edit_admin_project GET /admin/projects/:id/edit(.:format) admin/projects#edit {:id=>/[^\/]+/} +# admin_project GET /admin/projects/:id(.:format) admin/projects#show {:id=>/[^\/]+/} +# PUT /admin/projects/:id(.:format) admin/projects#update {:id=>/[^\/]+/} +# DELETE /admin/projects/:id(.:format) admin/projects#destroy {:id=>/[^\/]+/} +describe Admin::ProjectsController, "routing" do + it "to #team" do + get("/admin/projects/gitlab/team").should route_to('admin/projects#team', id: 'gitlab') + end + + it "to #team_update" do + put("/admin/projects/gitlab/team_update").should route_to('admin/projects#team_update', id: 'gitlab') + end + + it "to #index" do + get("/admin/projects").should route_to('admin/projects#index') + end + + it "to #create" do + post("/admin/projects").should route_to('admin/projects#create') + end + + it "to #new" do + get("/admin/projects/new").should route_to('admin/projects#new') + end + + it "to #edit" do + get("/admin/projects/gitlab/edit").should route_to('admin/projects#edit', id: 'gitlab') + end + + it "to #show" do + get("/admin/projects/gitlab").should route_to('admin/projects#show', id: 'gitlab') + end + + it "to #update" do + put("/admin/projects/gitlab").should route_to('admin/projects#update', id: 'gitlab') + end + + it "to #destroy" do + delete("/admin/projects/gitlab").should route_to('admin/projects#destroy', id: 'gitlab') + end +end + +# edit_admin_team_member GET /admin/team_members/:id/edit(.:format) admin/team_members#edit +# admin_team_member PUT /admin/team_members/:id(.:format) admin/team_members#update +# DELETE /admin/team_members/:id(.:format) admin/team_members#destroy +describe Admin::TeamMembersController, "routing" do + it "to #edit" do + get("/admin/team_members/1/edit").should route_to('admin/team_members#edit', id: '1') + end + + it "to #update" do + put("/admin/team_members/1").should route_to('admin/team_members#update', id: '1') + end + + it "to #destroy" do + delete("/admin/team_members/1").should route_to('admin/team_members#destroy', id: '1') + end +end + +# admin_hook_test GET /admin/hooks/:hook_id/test(.:format) admin/hooks#test +# admin_hooks GET /admin/hooks(.:format) admin/hooks#index +# POST /admin/hooks(.:format) admin/hooks#create +# admin_hook DELETE /admin/hooks/:id(.:format) admin/hooks#destroy +describe Admin::HooksController, "routing" do + it "to #test" do + get("/admin/hooks/1/test").should route_to('admin/hooks#test', hook_id: '1') + end + + it "to #index" do + get("/admin/hooks").should route_to('admin/hooks#index') + end + + it "to #create" do + post("/admin/hooks").should route_to('admin/hooks#create') + end + + it "to #destroy" do + delete("/admin/hooks/1").should route_to('admin/hooks#destroy', id: '1') + end + +end + +# admin_logs GET /admin/logs(.:format) admin/logs#show +describe Admin::LogsController, "routing" do + it "to #show" do + get("/admin/logs").should route_to('admin/logs#show') + end +end + +# admin_resque GET /admin/resque(.:format) admin/resque#show +describe Admin::ResqueController, "routing" do + it "to #show" do + get("/admin/resque").should route_to('admin/resque#show') + end +end + +# admin_root /admin(.:format) admin/dashboard#index +describe Admin::DashboardController, "routing" do + it "to #index" do + get("/admin").should route_to('admin/dashboard#index') + end +end + diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb new file mode 100644 index 00000000..b3f9db01 --- /dev/null +++ b/spec/routing/project_routing_spec.rb @@ -0,0 +1,398 @@ +require 'spec_helper' + +# Shared examples for a resource inside a Project +# +# By default it tests all the default REST actions: index, create, new, edit, +# show, update, and destroy. You can remove actions by customizing the +# `actions` variable. +# +# It also expects a `controller` variable to be available which defines both +# the path to the resource as well as the controller name. +# +# Examples +# +# # Default behavior +# it_behaves_like "RESTful project resources" do +# let(:controller) { 'issues' } +# end +# +# # Customizing actions +# it_behaves_like "RESTful project resources" do +# let(:actions) { [:index] } +# let(:controller) { 'issues' } +# end +shared_examples "RESTful project resources" do + let(:actions) { [:index, :create, :new, :edit, :show, :update, :destroy] } + + it "to #index" do + get("/gitlabhq/#{controller}").should route_to("#{controller}#index", project_id: 'gitlabhq') if actions.include?(:index) + end + + it "to #create" do + post("/gitlabhq/#{controller}").should route_to("#{controller}#create", project_id: 'gitlabhq') if actions.include?(:create) + end + + it "to #new" do + get("/gitlabhq/#{controller}/new").should route_to("#{controller}#new", project_id: 'gitlabhq') if actions.include?(:new) + end + + it "to #edit" do + get("/gitlabhq/#{controller}/1/edit").should route_to("#{controller}#edit", project_id: 'gitlabhq', id: '1') if actions.include?(:edit) + end + + it "to #show" do + get("/gitlabhq/#{controller}/1").should route_to("#{controller}#show", project_id: 'gitlabhq', id: '1') if actions.include?(:show) + end + + it "to #update" do + put("/gitlabhq/#{controller}/1").should route_to("#{controller}#update", project_id: 'gitlabhq', id: '1') if actions.include?(:update) + end + + it "to #destroy" do + delete("/gitlabhq/#{controller}/1").should route_to("#{controller}#destroy", project_id: 'gitlabhq', id: '1') if actions.include?(:destroy) + end +end + +# projects POST /projects(.:format) projects#create +# new_project GET /projects/new(.:format) projects#new +# wall_project GET /:id/wall(.:format) projects#wall +# graph_project GET /:id/graph(.:format) projects#graph +# files_project GET /:id/files(.:format) projects#files +# edit_project GET /:id/edit(.:format) projects#edit +# project GET /:id(.:format) projects#show +# PUT /:id(.:format) projects#update +# DELETE /:id(.:format) projects#destroy +describe ProjectsController, "routing" do + it "to #create" do + post("/projects").should route_to('projects#create') + end + + it "to #new" do + get("/projects/new").should route_to('projects#new') + end + + it "to #wall" do + get("/gitlabhq/wall").should route_to('projects#wall', id: 'gitlabhq') + end + + it "to #graph" do + get("/gitlabhq/graph").should route_to('projects#graph', id: 'gitlabhq') + end + + it "to #files" do + get("/gitlabhq/files").should route_to('projects#files', id: 'gitlabhq') + end + + it "to #edit" do + get("/gitlabhq/edit").should route_to('projects#edit', id: 'gitlabhq') + end + + it "to #show" do + get("/gitlabhq").should route_to('projects#show', id: 'gitlabhq') + end + + it "to #update" do + put("/gitlabhq").should route_to('projects#update', id: 'gitlabhq') + end + + it "to #destroy" do + delete("/gitlabhq").should route_to('projects#destroy', id: 'gitlabhq') + end +end + +# pages_project_wikis GET /:project_id/wikis/pages(.:format) wikis#pages +# history_project_wiki GET /:project_id/wikis/:id/history(.:format) wikis#history +# project_wikis POST /:project_id/wikis(.:format) wikis#create +# edit_project_wiki GET /:project_id/wikis/:id/edit(.:format) wikis#edit +# project_wiki GET /:project_id/wikis/:id(.:format) wikis#show +# DELETE /:project_id/wikis/:id(.:format) wikis#destroy +describe WikisController, "routing" do + it "to #pages" do + get("/gitlabhq/wikis/pages").should route_to('wikis#pages', project_id: 'gitlabhq') + end + + it "to #history" do + get("/gitlabhq/wikis/1/history").should route_to('wikis#history', project_id: 'gitlabhq', id: '1') + end + + it_behaves_like "RESTful project resources" do + let(:actions) { [:create, :edit, :show, :destroy] } + let(:controller) { 'wikis' } + end +end + +# branches_project_repository GET /:project_id/repository/branches(.:format) repositories#branches +# tags_project_repository GET /:project_id/repository/tags(.:format) repositories#tags +# archive_project_repository GET /:project_id/repository/archive(.:format) repositories#archive +# project_repository POST /:project_id/repository(.:format) repositories#create +# new_project_repository GET /:project_id/repository/new(.:format) repositories#new +# edit_project_repository GET /:project_id/repository/edit(.:format) repositories#edit +# GET /:project_id/repository(.:format) repositories#show +# PUT /:project_id/repository(.:format) repositories#update +# DELETE /:project_id/repository(.:format) repositories#destroy +describe RepositoriesController, "routing" do + it "to #branches" do + get("/gitlabhq/repository/branches").should route_to('repositories#branches', project_id: 'gitlabhq') + end + + it "to #tags" do + get("/gitlabhq/repository/tags").should route_to('repositories#tags', project_id: 'gitlabhq') + end + + it "to #archive" do + get("/gitlabhq/repository/archive").should route_to('repositories#archive', project_id: 'gitlabhq') + end + + it "to #create" do + post("/gitlabhq/repository").should route_to('repositories#create', project_id: 'gitlabhq') + end + + it "to #new" do + get("/gitlabhq/repository/new").should route_to('repositories#new', project_id: 'gitlabhq') + end + + it "to #edit" do + get("/gitlabhq/repository/edit").should route_to('repositories#edit', project_id: 'gitlabhq') + end + + it "to #show" do + get("/gitlabhq/repository").should route_to('repositories#show', project_id: 'gitlabhq') + end + + it "to #update" do + put("/gitlabhq/repository").should route_to('repositories#update', project_id: 'gitlabhq') + end + + it "to #destroy" do + delete("/gitlabhq/repository").should route_to('repositories#destroy', project_id: 'gitlabhq') + end +end + +# project_deploy_keys GET /:project_id/deploy_keys(.:format) deploy_keys#index +# POST /:project_id/deploy_keys(.:format) deploy_keys#create +# new_project_deploy_key GET /:project_id/deploy_keys/new(.:format) deploy_keys#new +# edit_project_deploy_key GET /:project_id/deploy_keys/:id/edit(.:format) deploy_keys#edit +# project_deploy_key GET /:project_id/deploy_keys/:id(.:format) deploy_keys#show +# PUT /:project_id/deploy_keys/:id(.:format) deploy_keys#update +# DELETE /:project_id/deploy_keys/:id(.:format) deploy_keys#destroy +describe DeployKeysController, "routing" do + it_behaves_like "RESTful project resources" do + let(:controller) { 'deploy_keys' } + end +end + +# project_protected_branches GET /:project_id/protected_branches(.:format) protected_branches#index +# POST /:project_id/protected_branches(.:format) protected_branches#create +# project_protected_branch DELETE /:project_id/protected_branches/:id(.:format) protected_branches#destroy +describe ProtectedBranchesController, "routing" do + it_behaves_like "RESTful project resources" do + let(:actions) { [:index, :create, :destroy] } + let(:controller) { 'protected_branches' } + end +end + +# switch_project_refs GET /:project_id/switch(.:format) refs#switch +# tree_project_ref GET /:project_id/:id/tree(.:format) refs#tree +# logs_tree_project_ref GET /:project_id/:id/logs_tree(.:format) refs#logs_tree +# blob_project_ref GET /:project_id/:id/blob(.:format) refs#blob +# tree_file_project_ref GET /:project_id/:id/tree/:path(.:format) refs#tree +# logs_file_project_ref GET /:project_id/:id/logs_tree/:path(.:format) refs#logs_tree +# blame_file_project_ref GET /:project_id/:id/blame/:path(.:format) refs#blame +describe RefsController, "routing" do + it "to #switch" do + get("/gitlabhq/switch").should route_to('refs#switch', project_id: 'gitlabhq') + end + + it "to #tree" do + get("/gitlabhq/stable/tree").should route_to('refs#tree', project_id: 'gitlabhq', id: 'stable') + get("/gitlabhq/stable/tree/foo/bar/baz").should route_to('refs#tree', project_id: 'gitlabhq', id: 'stable', path: 'foo/bar/baz') + end + + it "to #logs_tree" do + get("/gitlabhq/stable/logs_tree").should route_to('refs#logs_tree', project_id: 'gitlabhq', id: 'stable') + get("/gitlabhq/stable/logs_tree/foo/bar/baz").should route_to('refs#logs_tree', project_id: 'gitlabhq', id: 'stable', path: 'foo/bar/baz') + end + + it "to #blob" do + get("/gitlabhq/stable/blob").should route_to('refs#blob', project_id: 'gitlabhq', id: 'stable') + end + + it "to #blame" do + get("/gitlabhq/stable/blame/foo/bar/baz").should route_to('refs#blame', project_id: 'gitlabhq', id: 'stable', path: 'foo/bar/baz') + end +end + +# diffs_project_merge_request GET /:project_id/merge_requests/:id/diffs(.:format) merge_requests#diffs +# automerge_project_merge_request GET /:project_id/merge_requests/:id/automerge(.:format) merge_requests#automerge +# automerge_check_project_merge_request GET /:project_id/merge_requests/:id/automerge_check(.:format) merge_requests#automerge_check +# raw_project_merge_request GET /:project_id/merge_requests/:id/raw(.:format) merge_requests#raw +# branch_from_project_merge_requests GET /:project_id/merge_requests/branch_from(.:format) merge_requests#branch_from +# branch_to_project_merge_requests GET /:project_id/merge_requests/branch_to(.:format) merge_requests#branch_to +# project_merge_requests GET /:project_id/merge_requests(.:format) merge_requests#index +# POST /:project_id/merge_requests(.:format) merge_requests#create +# new_project_merge_request GET /:project_id/merge_requests/new(.:format) merge_requests#new +# edit_project_merge_request GET /:project_id/merge_requests/:id/edit(.:format) merge_requests#edit +# project_merge_request GET /:project_id/merge_requests/:id(.:format) merge_requests#show +# PUT /:project_id/merge_requests/:id(.:format) merge_requests#update +# DELETE /:project_id/merge_requests/:id(.:format) merge_requests#destroy +describe MergeRequestsController, "routing" do + it "to #diffs" do + get("/gitlabhq/merge_requests/1/diffs").should route_to('merge_requests#diffs', project_id: 'gitlabhq', id: '1') + end + + it "to #automerge" do + get("/gitlabhq/merge_requests/1/automerge").should route_to('merge_requests#automerge', project_id: 'gitlabhq', id: '1') + end + + it "to #automerge_check" do + get("/gitlabhq/merge_requests/1/automerge_check").should route_to('merge_requests#automerge_check', project_id: 'gitlabhq', id: '1') + end + + it "to #raw" do + get("/gitlabhq/merge_requests/1/raw").should route_to('merge_requests#raw', project_id: 'gitlabhq', id: '1') + end + + it "to #branch_from" do + get("/gitlabhq/merge_requests/branch_from").should route_to('merge_requests#branch_from', project_id: 'gitlabhq') + end + + it "to #branch_to" do + get("/gitlabhq/merge_requests/branch_to").should route_to('merge_requests#branch_to', project_id: 'gitlabhq') + end + + it_behaves_like "RESTful project resources" do + let(:controller) { 'merge_requests' } + end +end + +# raw_project_snippet GET /:project_id/snippets/:id/raw(.:format) snippets#raw +# project_snippets GET /:project_id/snippets(.:format) snippets#index +# POST /:project_id/snippets(.:format) snippets#create +# new_project_snippet GET /:project_id/snippets/new(.:format) snippets#new +# edit_project_snippet GET /:project_id/snippets/:id/edit(.:format) snippets#edit +# project_snippet GET /:project_id/snippets/:id(.:format) snippets#show +# PUT /:project_id/snippets/:id(.:format) snippets#update +# DELETE /:project_id/snippets/:id(.:format) snippets#destroy +describe SnippetsController, "routing" do + it "to #raw" do + get("/gitlabhq/snippets/1/raw").should route_to('snippets#raw', project_id: 'gitlabhq', id: '1') + end + + it_behaves_like "RESTful project resources" do + let(:controller) { 'snippets' } + end +end + +# test_project_hook GET /:project_id/hooks/:id/test(.:format) hooks#test +# project_hooks GET /:project_id/hooks(.:format) hooks#index +# POST /:project_id/hooks(.:format) hooks#create +# project_hook DELETE /:project_id/hooks/:id(.:format) hooks#destroy +describe HooksController, "routing" do + it "to #test" do + get("/gitlabhq/hooks/1/test").should route_to('hooks#test', project_id: 'gitlabhq', id: '1') + end + + it_behaves_like "RESTful project resources" do + let(:actions) { [:index, :create, :destroy] } + let(:controller) { 'hooks' } + end +end + +# compare_project_commits GET /:project_id/commits/compare(.:format) commits#compare +# patch_project_commit GET /:project_id/commits/:id/patch(.:format) commits#patch +# project_commits GET /:project_id/commits(.:format) commits#index +# POST /:project_id/commits(.:format) commits#create +# new_project_commit GET /:project_id/commits/new(.:format) commits#new +# edit_project_commit GET /:project_id/commits/:id/edit(.:format) commits#edit +# project_commit GET /:project_id/commits/:id(.:format) commits#show +# PUT /:project_id/commits/:id(.:format) commits#update +# DELETE /:project_id/commits/:id(.:format) commits#destroy +describe CommitsController, "routing" do + it "to #compare" do + get("/gitlabhq/commits/compare").should route_to('commits#compare', project_id: 'gitlabhq') + end + + it "to #patch" do + get("/gitlabhq/commits/1/patch").should route_to('commits#patch', project_id: 'gitlabhq', id: '1') + end + + it_behaves_like "RESTful project resources" do + let(:controller) { 'commits' } + end +end + +# project_team_members GET /:project_id/team_members(.:format) team_members#index +# POST /:project_id/team_members(.:format) team_members#create +# new_project_team_member GET /:project_id/team_members/new(.:format) team_members#new +# edit_project_team_member GET /:project_id/team_members/:id/edit(.:format) team_members#edit +# project_team_member GET /:project_id/team_members/:id(.:format) team_members#show +# PUT /:project_id/team_members/:id(.:format) team_members#update +# DELETE /:project_id/team_members/:id(.:format) team_members#destroy +describe TeamMembersController, "routing" do + it_behaves_like "RESTful project resources" do + let(:controller) { 'team_members' } + end +end + +# project_milestones GET /:project_id/milestones(.:format) milestones#index +# POST /:project_id/milestones(.:format) milestones#create +# new_project_milestone GET /:project_id/milestones/new(.:format) milestones#new +# edit_project_milestone GET /:project_id/milestones/:id/edit(.:format) milestones#edit +# project_milestone GET /:project_id/milestones/:id(.:format) milestones#show +# PUT /:project_id/milestones/:id(.:format) milestones#update +# DELETE /:project_id/milestones/:id(.:format) milestones#destroy +describe MilestonesController, "routing" do + it_behaves_like "RESTful project resources" do + let(:controller) { 'milestones' } + end +end + +# project_labels GET /:project_id/labels(.:format) labels#index +describe LabelsController, "routing" do + it "to #index" do + get("/gitlabhq/labels").should route_to('labels#index', project_id: 'gitlabhq') + end +end + +# sort_project_issues POST /:project_id/issues/sort(.:format) issues#sort +# bulk_update_project_issues POST /:project_id/issues/bulk_update(.:format) issues#bulk_update +# search_project_issues GET /:project_id/issues/search(.:format) issues#search +# project_issues GET /:project_id/issues(.:format) issues#index +# POST /:project_id/issues(.:format) issues#create +# new_project_issue GET /:project_id/issues/new(.:format) issues#new +# edit_project_issue GET /:project_id/issues/:id/edit(.:format) issues#edit +# project_issue GET /:project_id/issues/:id(.:format) issues#show +# PUT /:project_id/issues/:id(.:format) issues#update +# DELETE /:project_id/issues/:id(.:format) issues#destroy +describe IssuesController, "routing" do + it "to #sort" do + post("/gitlabhq/issues/sort").should route_to('issues#sort', project_id: 'gitlabhq') + end + + it "to #bulk_update" do + post("/gitlabhq/issues/bulk_update").should route_to('issues#bulk_update', project_id: 'gitlabhq') + end + + it "to #search" do + get("/gitlabhq/issues/search").should route_to('issues#search', project_id: 'gitlabhq') + end + + it_behaves_like "RESTful project resources" do + let(:controller) { 'issues' } + end +end + +# preview_project_notes POST /:project_id/notes/preview(.:format) notes#preview +# project_notes GET /:project_id/notes(.:format) notes#index +# POST /:project_id/notes(.:format) notes#create +# project_note DELETE /:project_id/notes/:id(.:format) notes#destroy +describe NotesController, "routing" do + it "to #preview" do + post("/gitlabhq/notes/preview").should route_to('notes#preview', project_id: 'gitlabhq') + end + + it_behaves_like "RESTful project resources" do + let(:actions) { [:index, :create, :destroy] } + let(:controller) { 'notes' } + end +end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb new file mode 100644 index 00000000..cb8dbf37 --- /dev/null +++ b/spec/routing/routing_spec.rb @@ -0,0 +1,186 @@ +require 'spec_helper' + +# search GET /search(.:format) search#show +describe SearchController, "routing" do + it "to #show" do + get("/search").should route_to('search#show') + end +end + +# gitlab_api /api Gitlab::API +# resque /info/resque Resque::Server +# /:path Grack +describe "Mounted Apps", "routing" do + it "to API" do + get("/api").should be_routable + end + + it "to Resque" do + pending + get("/info/resque").should be_routable + end + + it "to Grack" do + get("/gitlabhq.git").should be_routable + end +end + +# help GET /help(.:format) help#index +# help_permissions GET /help/permissions(.:format) help#permissions +# help_workflow GET /help/workflow(.:format) help#workflow +# help_api GET /help/api(.:format) help#api +# help_web_hooks GET /help/web_hooks(.:format) help#web_hooks +# help_system_hooks GET /help/system_hooks(.:format) help#system_hooks +# help_markdown GET /help/markdown(.:format) help#markdown +# help_ssh GET /help/ssh(.:format) help#ssh +describe HelpController, "routing" do + it "to #index" do + get("/help").should route_to('help#index') + end + + it "to #permissions" do + get("/help/permissions").should route_to('help#permissions') + end + + it "to #workflow" do + get("/help/workflow").should route_to('help#workflow') + end + + it "to #api" do + get("/help/api").should route_to('help#api') + end + + it "to #web_hooks" do + get("/help/web_hooks").should route_to('help#web_hooks') + end + + it "to #system_hooks" do + get("/help/system_hooks").should route_to('help#system_hooks') + end + + it "to #markdown" do + get("/help/markdown").should route_to('help#markdown') + end + + it "to #ssh" do + get("/help/ssh").should route_to('help#ssh') + end +end + +# errors_githost GET /errors/githost(.:format) errors#githost +describe ErrorsController, "routing" do + it "to #githost" do + get("/errors/githost").should route_to('errors#githost') + end +end + +# profile_account GET /profile/account(.:format) profile#account +# profile_history GET /profile/history(.:format) profile#history +# profile_password PUT /profile/password(.:format) profile#password_update +# profile_token GET /profile/token(.:format) profile#token +# profile_reset_private_token PUT /profile/reset_private_token(.:format) profile#reset_private_token +# profile GET /profile(.:format) profile#show +# profile_design GET /profile/design(.:format) profile#design +# profile_update PUT /profile/update(.:format) profile#update +describe ProfileController, "routing" do + it "to #account" do + get("/profile/account").should route_to('profile#account') + end + + it "to #history" do + get("/profile/history").should route_to('profile#history') + end + + it "to #password_update" do + put("/profile/password").should route_to('profile#password_update') + end + + it "to #token" do + get("/profile/token").should route_to('profile#token') + end + + it "to #reset_private_token" do + put("/profile/reset_private_token").should route_to('profile#reset_private_token') + end + + it "to #show" do + get("/profile").should route_to('profile#show') + end + + it "to #design" do + get("/profile/design").should route_to('profile#design') + end + + it "to #update" do + put("/profile/update").should route_to('profile#update') + end +end + +# keys GET /keys(.:format) keys#index +# POST /keys(.:format) keys#create +# new_key GET /keys/new(.:format) keys#new +# edit_key GET /keys/:id/edit(.:format) keys#edit +# key GET /keys/:id(.:format) keys#show +# PUT /keys/:id(.:format) keys#update +# DELETE /keys/:id(.:format) keys#destroy +describe KeysController, "routing" do + it "to #index" do + get("/keys").should route_to('keys#index') + end + + it "to #create" do + post("/keys").should route_to('keys#create') + end + + it "to #new" do + get("/keys/new").should route_to('keys#new') + end + + it "to #edit" do + get("/keys/1/edit").should route_to('keys#edit', id: '1') + end + + it "to #show" do + get("/keys/1").should route_to('keys#show', id: '1') + end + + it "to #update" do + put("/keys/1").should route_to('keys#update', id: '1') + end + + it "to #destroy" do + delete("/keys/1").should route_to('keys#destroy', id: '1') + end +end + +# dashboard GET /dashboard(.:format) dashboard#index +# dashboard_issues GET /dashboard/issues(.:format) dashboard#issues +# dashboard_merge_requests GET /dashboard/merge_requests(.:format) dashboard#merge_requests +# root / dashboard#index +describe DashboardController, "routing" do + it "to #index" do + get("/dashboard").should route_to('dashboard#index') + get("/").should route_to('dashboard#index') + end + + it "to #issues" do + get("/dashboard/issues").should route_to('dashboard#issues') + end + + it "to #merge_requests" do + get("/dashboard/merge_requests").should route_to('dashboard#merge_requests') + end +end + +# new_user_session GET /users/sign_in(.:format) devise/sessions#new +# user_session POST /users/sign_in(.:format) devise/sessions#create +# destroy_user_session DELETE /users/sign_out(.:format) devise/sessions#destroy +# user_omniauth_authorize /users/auth/:provider(.:format) omniauth_callbacks#passthru +# user_omniauth_callback /users/auth/:action/callback(.:format) omniauth_callbacks#(?-mix:(?!)) +# user_password POST /users/password(.:format) devise/passwords#create +# new_user_password GET /users/password/new(.:format) devise/passwords#new +# edit_user_password GET /users/password/edit(.:format) devise/passwords#edit +# PUT /users/password(.:format) devise/passwords#update +describe "Authentication", "routing" do + # pending +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index cd931475..d381b3f1 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -9,10 +9,7 @@ require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' require 'capybara/rails' require 'capybara/rspec' -require 'capybara/dsl' require 'webmock/rspec' -require 'factories' -require 'monkeypatch' require 'email_spec' require 'headless' @@ -23,10 +20,14 @@ Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} # Use capybara-webkit Capybara.javascript_driver = :webkit +WebMock.disable_net_connect!(allow_localhost: true) + RSpec.configure do |config| config.mock_with :rspec - config.include LoginMacros + config.include LoginHelpers, type: :request + config.include GitoliteStub + config.include FactoryGirl::Syntax::Methods # If you're not using ActiveRecord, or you'd prefer not to run each of your # examples within a transaction, remove the following line or assign false @@ -38,35 +39,11 @@ RSpec.configure do |config| headless.start end - config.before :each, type: :integration do - DeviseSessionMock.disable - end - config.before do - if example.metadata[:js] - DatabaseCleaner.strategy = :truncation - Capybara::Selenium::Driver::DEFAULT_OPTIONS[:resynchronize] = true - else - DatabaseCleaner.strategy = :transaction - end - - DatabaseCleaner.start - - WebMock.disable_net_connect!(allow_localhost: true) + stub_gitolite! # !!! Observers disabled by default in tests - # - # Use next code to enable observers - # before(:each) { ActiveRecord::Base.observers.enable(:all) } - # - ActiveRecord::Base.observers.disable :all + ActiveRecord::Base.observers.disable(:all) + # ActiveRecord::Base.observers.enable(:all) end - - config.after do - DatabaseCleaner.clean - end - - config.include RSpec::Rails::RequestExampleGroup, type: :request, example_group: { - file_path: /spec\/api/ - } end diff --git a/spec/support/api.rb b/spec/support/api.rb deleted file mode 100644 index d363d8b9..00000000 --- a/spec/support/api.rb +++ /dev/null @@ -1,7 +0,0 @@ -def api_prefix - "/api/#{Gitlab::API::VERSION}" -end - -def json_response - JSON.parse(response.body) -end diff --git a/spec/support/api_helpers.rb b/spec/support/api_helpers.rb new file mode 100644 index 00000000..7d901197 --- /dev/null +++ b/spec/support/api_helpers.rb @@ -0,0 +1,34 @@ +module ApiHelpers + # Public: Prepend a request path with the path to the API + # + # path - Path to append + # user - User object - If provided, automatically appends private_token query + # string for authenticated requests + # + # Examples + # + # >> api('/issues') + # => "/api/v2/issues" + # + # >> api('/issues', User.last) + # => "/api/v2/issues?private_token=..." + # + # >> api('/issues?foo=bar', User.last) + # => "/api/v2/issues?foo=bar&private_token=..." + # + # Returns the relative path to the requested API resource + def api(path, user = nil) + "/api/#{Gitlab::API::VERSION}#{path}" + + + # Normalize query string + (path.index('?') ? '' : '?') + + + # Append private_token if given a User object + (user.respond_to?(:private_token) ? + "&private_token=#{user.private_token}" : "") + end + + def json_response + JSON.parse(response.body) + end +end diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb new file mode 100644 index 00000000..f1e072aa --- /dev/null +++ b/spec/support/db_cleaner.rb @@ -0,0 +1,18 @@ +require 'database_cleaner' + +RSpec.configure do |config| + config.before do + if example.metadata[:js] + DatabaseCleaner.strategy = :truncation + Capybara::Selenium::Driver::DEFAULT_OPTIONS[:resynchronize] = true + else + DatabaseCleaner.strategy = :transaction + end + + DatabaseCleaner.start + end + + config.after do + DatabaseCleaner.clean + end +end diff --git a/spec/support/gitolite_stub.rb b/spec/support/gitolite_stub.rb new file mode 100644 index 00000000..574bb5a1 --- /dev/null +++ b/spec/support/gitolite_stub.rb @@ -0,0 +1,21 @@ +module GitoliteStub + def stub_gitolite! + stub_gitlab_gitolite + stub_gitolite_admin + end + + def stub_gitolite_admin + gitolite_admin = double('Gitolite::GitoliteAdmin') + gitolite_admin.as_null_object + + Gitolite::GitoliteAdmin.stub(new: gitolite_admin) + end + + def stub_gitlab_gitolite + gitolite_config = double('Gitlab::GitoliteConfig') + gitolite_config.stub(apply: ->() { yield(self) }) + gitolite_config.as_null_object + + Gitlab::GitoliteConfig.stub(new: gitolite_config) + end +end diff --git a/spec/support/js_patch.rb b/spec/support/js_patch.rb deleted file mode 100644 index 0d4ab264..00000000 --- a/spec/support/js_patch.rb +++ /dev/null @@ -1,6 +0,0 @@ -module JsPatch - def confirm_js_popup - page.evaluate_script("window.alert = function(msg) { return true; }") - page.evaluate_script("window.confirm = function(msg) { return true; }") - end -end diff --git a/spec/support/login.rb b/spec/support/login.rb deleted file mode 100644 index 78a907ba..00000000 --- a/spec/support/login.rb +++ /dev/null @@ -1,30 +0,0 @@ -module LoginMacros - def login_as role - @user = User.create(email: "user#{User.count}@mail.com", - name: "John Smith", - password: "123456", - password_confirmation: "123456", - skype: 'user_skype') - - if role == :admin - @user.admin = true - @user.save! - end - - visit new_user_session_path - fill_in "user_email", with: @user.email - fill_in "user_password", with: "123456" - click_button "Sign in" - end - - def login_with(user) - visit new_user_session_path - fill_in "user_email", with: user.email - fill_in "user_password", with: "123456" - click_button "Sign in" - end - - def logout - click_link "Logout" rescue nil - end -end diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb new file mode 100644 index 00000000..769034e2 --- /dev/null +++ b/spec/support/login_helpers.rb @@ -0,0 +1,23 @@ +module LoginHelpers + # Internal: Create and log in as a user of the specified role + # + # role - User role (e.g., :admin, :user) + def login_as(role) + @user = Factory(role) + login_with(@user) + end + + # Internal: Login as the specified user + # + # user - User instance to login with + def login_with(user) + visit new_user_session_path + fill_in "user_email", with: user.email + fill_in "user_password", with: "123456" + click_button "Sign in" + end + + def logout + click_link "Logout" rescue nil + end +end diff --git a/spec/support/matchers.rb b/spec/support/matchers.rb index e0672166..809453c4 100644 --- a/spec/support/matchers.rb +++ b/spec/support/matchers.rb @@ -28,6 +28,16 @@ RSpec::Matchers.define :be_404_for do |user| end end +RSpec::Matchers.define :include_module do |expected| + match do + described_class.included_modules.include?(expected) + end + + failure_message_for_should do + "expected #{described_class} to include the #{expected} module" + end +end + module UrlAccess def url_allowed?(user, url) emulate_user(user) @@ -57,3 +67,13 @@ module UrlAccess login_with(user) if user end end + +# Extend shoulda-matchers +module Shoulda::Matchers::ActiveModel + class EnsureLengthOfMatcher + # Shortcut for is_at_least and is_at_most + def is_within(range) + is_at_least(range.min) && is_at_most(range.max) + end + end +end diff --git a/spec/support/shared_examples.rb b/spec/support/shared_examples.rb deleted file mode 100644 index 9fd207d0..00000000 --- a/spec/support/shared_examples.rb +++ /dev/null @@ -1,16 +0,0 @@ -shared_examples_for :project_side_pane do - subject { page } - it { should have_content((@project || project).name) } - it { should have_content("Commits") } - it { should have_content("Files") } -end - -shared_examples_for :tree_view do - subject { page } - - it "should have Tree View of project" do - should have_content("app") - should have_content("History") - should have_content("Gemfile") - end -end diff --git a/spec/support/stubbed_repository.rb b/spec/support/stubbed_repository.rb new file mode 100644 index 00000000..90491e43 --- /dev/null +++ b/spec/support/stubbed_repository.rb @@ -0,0 +1,31 @@ +# Stubs out all Git repository access done by models so that specs can run +# against fake repositories without Grit complaining that they don't exist. +module StubbedRepository + def path_to_repo + if new_record? || path == 'newproject' + # There are a couple Project specs and features that expect the Project's + # path to be in the returned path, so let's patronize them. + File.join(Rails.root, 'tmp', 'repositories', path) + else + # For everything else, just give it the path to one of our real seeded + # repos. + File.join(Rails.root, 'tmp', 'repositories', 'gitlabhq') + end + end + + def satellite + FakeSatellite.new + end + + class FakeSatellite + def exists? + true + end + + def create + true + end + end +end + +Project.send(:include, StubbedRepository) diff --git a/features/projects/deploy_keys.feature b/tmp/.gitkeep similarity index 100% rename from features/projects/deploy_keys.feature rename to tmp/.gitkeep diff --git a/vendor/assets/images/authbuttons/github_32.png b/vendor/assets/images/authbuttons/github_32.png new file mode 100644 index 00000000..247e52a5 Binary files /dev/null and b/vendor/assets/images/authbuttons/github_32.png differ diff --git a/vendor/assets/images/authbuttons/github_64.png b/vendor/assets/images/authbuttons/github_64.png new file mode 100644 index 00000000..fca7bf44 Binary files /dev/null and b/vendor/assets/images/authbuttons/github_64.png differ diff --git a/vendor/assets/images/authbuttons/google_32.png b/vendor/assets/images/authbuttons/google_32.png new file mode 100644 index 00000000..3909e9de Binary files /dev/null and b/vendor/assets/images/authbuttons/google_32.png differ diff --git a/vendor/assets/images/authbuttons/google_64.png b/vendor/assets/images/authbuttons/google_64.png new file mode 100644 index 00000000..e55f34f1 Binary files /dev/null and b/vendor/assets/images/authbuttons/google_64.png differ diff --git a/vendor/assets/images/authbuttons/twitter_32.png b/vendor/assets/images/authbuttons/twitter_32.png new file mode 100644 index 00000000..daadcffd Binary files /dev/null and b/vendor/assets/images/authbuttons/twitter_32.png differ diff --git a/vendor/assets/images/authbuttons/twitter_64.png b/vendor/assets/images/authbuttons/twitter_64.png new file mode 100644 index 00000000..68b74530 Binary files /dev/null and b/vendor/assets/images/authbuttons/twitter_64.png differ