Commit 3374027e3a4e4eb040e59294a9ced9d7886a71e2

Authored by Sebastian Ziebell
2 parents 39114d25 9c2a6e20

Merge branch 'master' into fixes/api, code clean up and tests fixed

Conflicts:
	doc/api/projects.md
	spec/requests/api/projects_spec.rb
Capfile.example 0 → 100644
... ... @@ -0,0 +1,4 @@
  1 +load 'deploy'
  2 +load 'deploy/assets'
  3 +require 'bundler/capistrano'
  4 +load 'config/deploy'
... ...
Gemfile
1   -source "http://rubygems.org"
  1 +source "https://rubygems.org"
2 2  
3 3 def darwin_only(require_as)
4 4 RUBY_PLATFORM.include?('darwin') && require_as
... ... @@ -103,6 +103,9 @@ gem 'settingslogic'
103 103 gem "foreman"
104 104 gem "git"
105 105  
  106 +# Cache
  107 +gem "redis-rails"
  108 +
106 109 group :assets do
107 110 gem "sass-rails", "~> 3.2.5"
108 111 gem "coffee-rails", "~> 3.2.2"
... ...
Gemfile.lock
... ... @@ -13,7 +13,7 @@ GIT
13 13 raphael-rails (2.1.0)
14 14  
15 15 GEM
16   - remote: http://rubygems.org/
  16 + remote: https://rubygems.org/
17 17 specs:
18 18 actionmailer (3.2.12)
19 19 actionpack (= 3.2.12)
... ... @@ -329,8 +329,24 @@ GEM
329 329 json (~> 1.4)
330 330 redcarpet (2.2.2)
331 331 redis (3.0.2)
  332 + redis-actionpack (3.2.3)
  333 + actionpack (~> 3.2.3)
  334 + redis-rack (~> 1.4.0)
  335 + redis-store (~> 1.1.0)
  336 + redis-activesupport (3.2.3)
  337 + activesupport (~> 3.2.3)
  338 + redis-store (~> 1.1.0)
332 339 redis-namespace (1.2.1)
333 340 redis (~> 3.0.0)
  341 + redis-rack (1.4.2)
  342 + rack (~> 1.4.1)
  343 + redis-store (~> 1.1.0)
  344 + redis-rails (3.2.3)
  345 + redis-actionpack (~> 3.2.3)
  346 + redis-activesupport (~> 3.2.3)
  347 + redis-store (~> 1.1.0)
  348 + redis-store (1.1.3)
  349 + redis (>= 2.2.0)
334 350 request_store (1.0.5)
335 351 rspec (2.12.0)
336 352 rspec-core (~> 2.12.0)
... ... @@ -504,6 +520,7 @@ DEPENDENCIES
504 520 rb-fsevent
505 521 rb-inotify
506 522 redcarpet (~> 2.2.2)
  523 + redis-rails
507 524 rspec-rails (= 2.12.2)
508 525 sass-rails (~> 3.2.5)
509 526 sdoc
... ...
README.md
... ... @@ -5,14 +5,14 @@
5 5 ### GitLab allows you to
6 6 * keep your code secure on your own server
7 7 * manage repositories, users and access permissions
8   - * communicate though issues, line-comments and wiki's
9   - * perform code reviews with merge requests
  8 + * communicate through issues, line-comments and wiki pages
  9 + * perform code review with merge requests
10 10  
11 11 ### GitLab is
12 12  
13 13 * powered by Ruby on Rails
14 14 * completely free and open source (MIT license)
15   -* used by 10.000 organization to keep their code secure
  15 +* used by 10.000 organizations to keep their code secure
16 16  
17 17 ### Code status
18 18  
... ... @@ -34,28 +34,35 @@
34 34  
35 35 ### Requirements
36 36  
37   -* Ubuntu/Debian*
  37 +* Ubuntu/Debian**
38 38 * ruby 1.9.3+
39 39 * MySQL
40 40 * git
41 41 * gitlab-shell
42 42 * redis
43 43  
44   -* More details are in the [requirements doc](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/requirements.md)
  44 +** More details are in the [requirements doc](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/requirements.md)
45 45  
46 46 ### Installation
47 47  
48   -You can either follow the "ordinary" Installation guide to install it on a machine or use the Vagrant virtual machine. The Installation guide is recommended to set up a production server. The Vargrant virtual machine is recommended for development since it makes it much easier to set up all the dependencies for integration testing.
  48 +#### For production
49 49  
50   -* [Installation guide for latest stable release](https://github.com/gitlabhq/gitlabhq/blob/4-2-stable/doc/install/installation.md)
  50 +Follow the installation guide for production server.
51 51  
52   -* [Installation guide for the current master branch](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md)
  52 +* [Installation guide for latest stable release (4.2)](https://github.com/gitlabhq/gitlabhq/blob/4-2-stable/doc/install/installation.md) - **Recommended**
  53 +
  54 +* [Installation guide for the current master branch (5.0)](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md)
  55 +
  56 +
  57 +#### For development
  58 +
  59 +If you want to contribute, please first read our [Contributing Guidelines](https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md) and then we suggest you to use the Vagrant virtual machine project to get an environment working sandboxed and with all dependencies.
53 60  
54 61 * [Vagrant virtual machine](https://github.com/gitlabhq/gitlab-vagrant-vm)
55 62  
56 63 ### Starting
57 64  
58   -1. The Installation guide contains instructions to download an init script and run that on boot. With the init script you can also start GitLab with:
  65 +1. The Installation guide contains instructions to download an init script and run that on boot. With the init script you can also start GitLab
59 66  
60 67 sudo service gitlab start
61 68  
... ... @@ -63,18 +70,18 @@ You can either follow the "ordinary" Installation guide to install it on a machi
63 70  
64 71 sudo /etc/init.d/gitlab restart
65 72  
66   -2. Start it with [Foreman](https://github.com/ddollar/foreman) in development model
  73 +2. Start it with [Foreman](https://github.com/ddollar/foreman) in development mode
67 74  
68 75 bundle exec foreman start -p 3000
69 76  
70   -3. Start it manually in development mode
  77 + or start it manually
71 78  
72 79 bundle exec rails s
73 80 bundle exec rake sidekiq:start
74 81  
75 82 ### Running the tests
76 83  
77   -* Seed the database with
  84 +* Seed the database
78 85  
79 86 bundle exec rake db:setup RAILS_ENV=test
80 87 bundle exec rake db:seed_fu RAILS_ENV=test
... ...
app/assets/javascripts/tree.js.coffee
... ... @@ -11,12 +11,7 @@ $ ->
11 11 # Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
12 12 $("#tree-slider .tree-item").live 'click', (e) ->
13 13 $('.tree-item-file-name a', this).trigger('click') if (e.target.nodeName != "A")
14   -
15   - # Show/Hide the loading spinner
16   - $('#tree-slider .tree-item-file-name a, .breadcrumb a, .project-refs-form').live
17   - "ajax:beforeSend": -> $('.tree_progress').addClass("loading")
18   - "ajax:complete": -> $('.tree_progress').removeClass("loading")
19   -
  14 +
20 15 # Maintain forward/back history while browsing the file tree
21 16 ((window) ->
22 17 History = window.History
... ... @@ -33,7 +28,12 @@ $ ->
33 28  
34 29 History.Adapter.bind window, 'statechange', ->
35 30 state = History.getState()
36   - window.ajaxGet(state.url)
  31 + $.ajax({
  32 + url: state.url,
  33 + dataType: 'script',
  34 + beforeSend: -> $('.tree_progress').addClass("loading"),
  35 + complete: -> $('.tree_progress').removeClass("loading")
  36 + })
37 37 )(window)
38 38  
39 39 # See if there are lines selected
... ...
app/assets/stylesheets/gitlab_bootstrap/blocks.scss
... ... @@ -34,13 +34,6 @@
34 34 padding: 15px;
35 35 word-wrap: break-word;
36 36  
37   - pre {
38   - background: none !important;
39   - margin: 0;
40   - border: none;
41   - padding: 0;
42   - }
43   -
44 37 .clearfix {
45 38 margin: 0;
46 39 }
... ...
app/assets/stylesheets/gitlab_bootstrap/common.scss
... ... @@ -2,6 +2,7 @@
2 2 .cgray { color:gray }
3 3 .cred { color:#D12F19 }
4 4 .cgreen { color:#4a2 }
  5 +.cblue { color:#29A }
5 6 .cblack { color:#111 }
6 7 .cdark { color:#444 }
7 8 .cwhite { color:#fff!important }
... ...
app/assets/stylesheets/sections/projects.scss
... ... @@ -120,3 +120,16 @@ ul.nav.nav-projects-tabs {
120 120 .team_member_row form {
121 121 margin: 0px;
122 122 }
  123 +
  124 +.public-projects {
  125 + li {
  126 + margin-top: 8px;
  127 + margin-bottom: 5px;
  128 + border-bottom: 1px solid #eee;
  129 +
  130 + .description {
  131 + margin-left: 22px;
  132 + color: #aaa;
  133 + }
  134 + }
  135 +}
... ...
app/models/merge_request.rb
... ... @@ -91,7 +91,7 @@ class MergeRequest < ActiveRecord::Base
91 91  
92 92 def validate_branches
93 93 if target_branch == source_branch
94   - errors.add :base, "You can not use same branch for source and target branches"
  94 + errors.add :branch_conflict, "You can not use same branch for source and target branches"
95 95 end
96 96 end
97 97  
... ...
app/models/repository.rb
1 1 class Repository
  2 + include Gitlab::Popen
  3 +
2 4 # Repository directory name with namespace direcotry
3 5 # Examples:
4 6 # gitlab/gitolite
... ... @@ -147,4 +149,21 @@ class Repository
147 149  
148 150 file_path
149 151 end
  152 +
  153 + # Return repo size in megabytes
  154 + # Cached in redis
  155 + def size
  156 + Rails.cache.fetch(cache_key(:size)) do
  157 + size = popen('du -s', path_to_repo).first.strip.to_i
  158 + (size.to_f / 1024).round(2)
  159 + end
  160 + end
  161 +
  162 + def expire_cache
  163 + Rails.cache.delete(cache_key(:size))
  164 + end
  165 +
  166 + def cache_key(type)
  167 + "#{type}:#{path_with_namespace}"
  168 + end
150 169 end
... ...
app/services/git_push_service.rb
... ... @@ -23,6 +23,7 @@ class GitPushService
23 23  
24 24 project.ensure_satellite_exists
25 25 project.discover_default_branch
  26 + project.repository.expire_cache
26 27  
27 28 if push_to_branch?(ref, oldrev)
28 29 project.update_merge_requests(oldrev, newrev, ref, @user)
... ...
app/views/devise/sessions/_new_ldap.html.haml
1 1 = form_tag(user_omniauth_callback_path(:ldap), :class => "login-box", :id => 'new_ldap_user' ) do
2 2 = image_tag "login-logo.png", :width => "304", :height => "66", :class => "login-logo", :alt => "Login Logo"
3   - = text_field_tag :username, nil, {:class => "text top", :placeholder => "LDAP Login"}
  3 + = text_field_tag :username, nil, {:class => "text top", :placeholder => "LDAP Login", :autofocus => "autofocus"}
4 4 = password_field_tag :password, nil, {:class => "text bottom", :placeholder => "Password"}
5 5 %br/
6 6 = submit_tag "LDAP Sign in", :class => "btn-primary btn"
... ...
app/views/events/_event.html.haml
1 1 - if event.proper?
2   - %div.event-item
3   - %span.cgray.pull-right
4   - #{time_ago_in_words(event.created_at)} ago.
  2 + = cache event do
  3 + %div.event-item
  4 + %span.cgray.pull-right
  5 + #{time_ago_in_words(event.created_at)} ago.
5 6  
6   - = image_tag gravatar_icon(event.author_email), class: "avatar s24"
  7 + = image_tag gravatar_icon(event.author_email), class: "avatar s24"
7 8  
8   - - if event.push?
9   - = render "events/event/push", event: event
10   - .clearfix
11   - - elsif event.note?
12   - = render "events/event/note", event: event
13   - - else
14   - = render "events/event/common", event: event
  9 + - if event.push?
  10 + = render "events/event/push", event: event
  11 + .clearfix
  12 + - elsif event.note?
  13 + = render "events/event/note", event: event
  14 + - else
  15 + = render "events/event/common", event: event
15 16  
... ...
app/views/help/api.html.haml
... ... @@ -21,6 +21,8 @@
21 21 = link_to "Milestones", "#milestones", 'data-toggle' => 'tab'
22 22 %li
23 23 = link_to "Notes", "#notes", 'data-toggle' => 'tab'
  24 + %li
  25 + = link_to "System Hooks", "#system_hooks", 'data-toggle' => 'tab'
24 26  
25 27 .tab-content
26 28 .tab-pane.active#README
... ... @@ -103,3 +105,12 @@
103 105 .file_content.wiki
104 106 = preserve do
105 107 = markdown File.read(Rails.root.join("doc", "api", "notes.md"))
  108 +
  109 + .tab-pane#system_hooks
  110 + .file_holder
  111 + .file_title
  112 + %i.icon-file
  113 + System Hooks
  114 + .file_content.wiki
  115 + = preserve do
  116 + = markdown File.read(Rails.root.join("doc", "api", "system_hooks.md"))
... ...
app/views/help/index.html.haml
... ... @@ -22,7 +22,7 @@
22 22 = mail_to Gitlab.config.gitlab.support_email, "support contact"
23 23 %li
24 24 Use the
25   - = link_to "search bar", '#', onclick: "$("#search").focus();"
  25 + = link_to "search bar", '#', onclick: "$('#search').focus();"
26 26 on the top of this page
27 27 %li
28 28 Ask in our
... ...
app/views/layouts/public.html.haml
... ... @@ -10,7 +10,7 @@
10 10 = link_to root_path, class: "home" do
11 11 %h1 GITLAB
12 12 %span.separator
13   - %h1.project_name Public Projects
  13 + %h1.project_name Public Projects
14 14 .container
15 15 .content
16 16 .prepend-top-20
... ...
app/views/projects/_form.html.haml
... ... @@ -9,11 +9,19 @@
9 9 Project name is
10 10 .input
11 11 = f.text_field :name, placeholder: "Example Project", class: "xxlarge"
  12 +
  13 +
12 14 - unless @repository.heads.empty?
13 15 .clearfix
14 16 = f.label :default_branch, "Default Branch"
15 17 .input= f.select(:default_branch, @repository.heads.map(&:name), {}, style: "width:210px;")
16 18  
  19 + .clearfix
  20 + = f.label :description do
  21 + Project description
  22 + %span.light (optional)
  23 + .input
  24 + = f.text_area :description, placeholder: "awesome project", class: "xxlarge", rows: 3, maxlength: 250
17 25  
18 26 %fieldset.features
19 27 %legend Features:
... ...
app/views/projects/show.html.haml
1 1 = render "project_head"
2 2 = render 'clone_panel'
3 3 = render "events/event_last_push", event: @last_push
4   -.content_list= render @events
5   -.loading.hide
6 4  
  5 +.row
  6 + .span9
  7 + .content_list= render @events
  8 + .loading.hide
  9 + .span3
  10 + .ui-box.white
  11 + .padded
  12 + %h3.page_title
  13 + = @project.name
  14 + - if @project.description.present?
  15 + %p.light= @project.description
  16 +
  17 + %hr
  18 + %p
  19 + Access level:
  20 + - if @project.public
  21 + %span.cblue
  22 + %i.icon-share
  23 + Public
  24 + - else
  25 + %span.cgreen
  26 + %i.icon-lock
  27 + Private
  28 +
  29 + %p Repo Size: #{@project.repository.size} MB
  30 + %p Created at: #{@project.created_at.stamp('Aug 22, 2013')}
  31 + %p Owner: #{link_to @project.owner_name, @project.owner}
7 32 :javascript
8 33 $(function(){ Pager.init(20); });
... ...
app/views/public/projects/index.html.haml
1 1 %h3.page_title
2   - Projects
  2 + Projects (#{@projects.total_count})
3 3 %small with read-only access
4 4 %hr
5 5  
6   -%ul.unstyled
7   - - @projects.each do |project|
8   - %li.clearfix
9   - %h5
10   - %i.icon-share
11   - = project.name_with_namespace
12   - .pull-right
13   - %pre.dark.tiny git clone #{project.http_url_to_repo}
  6 +.public-projects
  7 + %ul.unstyled
  8 + - @projects.each do |project|
  9 + %li.clearfix
  10 + %h5
  11 + %i.icon-share
  12 + = project.name_with_namespace
  13 + .pull-right
  14 + %pre.dark.tiny git clone #{project.http_url_to_repo}
  15 + %p.description
  16 + = project.description
  17 + - unless @projects.present?
  18 + %h3.nothing_here_message No public projects
14 19  
15   - - unless @projects.present?
16   - %h3.nothing_here_message No public projects
17   -
18   -= paginate @projects, theme: "admin"
  20 + = paginate @projects, theme: "admin"
... ...
app/views/tree/_tree.html.haml
... ... @@ -11,9 +11,6 @@
11 11 - else
12 12 = link_to title, '#'
13 13  
14   -.clear
15   -%div.tree_progress
16   -
17 14 %div#tree-content-holder.tree-content-holder
18 15 - if tree.is_blob?
19 16 = render "tree/blob", blob: tree
... ... @@ -40,6 +37,8 @@
40 37 - if tree.readme
41 38 = render "tree/readme", readme: tree.readme
42 39  
  40 +%div.tree_progress
  41 +
43 42 - unless tree.is_blob?
44 43 :javascript
45 44 // Load last commit log for each file in tree
... ...
config/deploy.rb.example 0 → 100644
... ... @@ -0,0 +1,72 @@
  1 +set :domain, 'set application domain here'
  2 +set :db_adapter, 'mysql' # or postgres
  3 +set :mount_point, '/'
  4 +set :application, 'gitlabhq'
  5 +set :user, 'git'
  6 +set :rails_env, 'production'
  7 +set :deploy_to, "/home/#{user}/apps/#{application}"
  8 +set :bundle_without, %w[development test] + (%w[mysql postgres] - [db_adapter])
  9 +set :asset_env, "RAILS_GROUPS=assets RAILS_RELATIVE_URL_ROOT=#{mount_point.sub /\/+\Z/, ''}"
  10 +
  11 +set :use_sudo, false
  12 +default_run_options[:pty] = true
  13 +
  14 +# Or: `accurev`, `bzr`, `cvs`, `darcs`, `git`, `mercurial`, `perforce`, `subversion` or `none`
  15 +set :scm, :git
  16 +set :repository, "git@#{domain}:#{application}.git"
  17 +set :deploy_via, :remote_cache
  18 +
  19 +# Alternatively, you can deploy via copy, if you don't have gitlab in git
  20 +#set :scm, :none
  21 +#set :repository, '.'
  22 +#set :deploy_via, :copy
  23 +
  24 +server domain, :app, :web, :db, primary: true
  25 +
  26 +namespace :foreman do
  27 + desc 'Export the Procfile to Ubuntu upstart scripts'
  28 + task :export, roles: :app do
  29 + foreman_export = "foreman export upstart /etc/init -f Procfile -a #{application} -u #{user} -l #{shared_path}/log/foreman"
  30 + run "cd #{release_path} && #{sudo} #{fetch :bundle_cmd, 'bundle'} exec #{foreman_export}"
  31 + end
  32 +
  33 + desc 'Start the application services'
  34 + task :start, roles: :app do
  35 + run "#{sudo} service #{application} start"
  36 + end
  37 +
  38 + desc 'Stop the application services'
  39 + task :stop, roles: :app do
  40 + run "#{sudo} service #{application} stop"
  41 + end
  42 +
  43 + desc 'Restart the application services'
  44 + task :restart, roles: :app do
  45 + run "#{sudo} service #{application} restart"
  46 + end
  47 +end
  48 +
  49 +namespace :deploy do
  50 + desc 'Start the application services'
  51 + task :start, roles: :app do
  52 + foreman.start
  53 + end
  54 +
  55 + desc 'Stop the application services'
  56 + task :stop, roles: :app do
  57 + foreman.stop
  58 + end
  59 +
  60 + desc 'Restart the application services'
  61 + task :restart, roles: :app do
  62 + foreman.restart
  63 + end
  64 +end
  65 +
  66 +after 'deploy:cold' do
  67 + run "cd #{release_path} && #{rake} gitlab:setup force=yes RAILS_ENV=#{rails_env}"
  68 + deploy.restart
  69 +end
  70 +
  71 +after 'deploy:update', 'foreman:export' # Export foreman scripts
  72 +#after 'deploy:update', 'foreman:restart' # Restart application scripts
... ...
config/environments/production.rb
... ... @@ -40,7 +40,7 @@ Gitlab::Application.configure do
40 40 # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
41 41  
42 42 # Use a different cache store in production
43   - config.cache_store = :memory_store
  43 + config.cache_store = :redis_store
44 44  
45 45 # Enable serving of images, stylesheets, and JavaScripts from an asset server
46 46 # config.action_controller.asset_host = "http://assets.example.com"
... ...
config/initializers/4_sidekiq.rb
... ... @@ -4,19 +4,19 @@ config_file = Rails.root.join('config', 'resque.yml')
4 4 resque_url = if File.exists?(config_file)
5 5 YAML.load_file(config_file)[Rails.env]
6 6 else
7   - "localhost:6379"
  7 + "redis://localhost:6379"
8 8 end
9 9  
10 10 Sidekiq.configure_server do |config|
11 11 config.redis = {
12   - url: "redis://#{resque_url}",
  12 + url: resque_url,
13 13 namespace: 'resque:gitlab'
14 14 }
15 15 end
16 16  
17 17 Sidekiq.configure_client do |config|
18 18 config.redis = {
19   - url: "redis://#{resque_url}",
  19 + url: resque_url,
20 20 namespace: 'resque:gitlab'
21 21 }
22 22 end
... ...
config/resque.yml.example
1   -development: localhost:6379
2   -test: localhost:6379
3   -production: redis.example.com:6379
  1 +development: redis://localhost:6379
  2 +test: redis://localhost:6379
  3 +production: redis://redis.example.com:6379
... ...
config/routes.rb
... ... @@ -166,7 +166,7 @@ Gitlab::Application.routes.draw do
166 166 #
167 167 # Project Area
168 168 #
169   - resources :projects, constraints: { id: /[a-zA-Z.0-9_\-\/]+/ }, except: [:new, :create, :index], path: "/" do
  169 + resources :projects, constraints: { id: /(?:[a-zA-Z.0-9_\-]+\/)?[a-zA-Z.0-9_\-]+/ }, except: [:new, :create, :index], path: "/" do
170 170 member do
171 171 get "wall"
172 172 get "files"
... ... @@ -175,10 +175,10 @@ Gitlab::Application.routes.draw do
175 175 resources :blob, only: [:show], constraints: {id: /.+/}
176 176 resources :tree, only: [:show, :edit, :update], constraints: {id: /.+/}
177 177 resources :commit, only: [:show], constraints: {id: /[[:alnum:]]{6,40}/}
178   - resources :commits, only: [:show], constraints: {id: /.+/}
  178 + resources :commits, only: [:show], constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/}
179 179 resources :compare, only: [:index, :create]
180 180 resources :blame, only: [:show], constraints: {id: /.+/}
181   - resources :graph, only: [:show], constraints: {id: /.+/}
  181 + resources :graph, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/}
182 182 match "/compare/:from...:to" => "compare#show", as: "compare",
183 183 :via => [:get, :post], constraints: {from: /.+/, to: /.+/}
184 184  
... ...
doc/api/README.md
... ... @@ -31,13 +31,10 @@ The API uses JSON to serialize data. You don't need to specify `.json` at the en
31 31  
32 32 ## Status codes
33 33  
34   -API requests return different status codes according to
35   -
36   -The API is designed to provide status codes according to the context and how the request
37   -is handled. For example if a `GET` request is successful a status code `200 Ok`
38   -is returned. The API is designed to be RESTful.
39   -
40   -The following list gives an overview of how the API functions are designed.
  34 +The API is designed to return different status codes according to context and action. In this way
  35 +if a request results in an error the caller is able to get insight into what went wrong, e.g.
  36 +status code `400 Bad Request` is returned if a required attribute is missing from the request.
  37 +The following list gives an overview of how the API functions generally behave.
41 38  
42 39 API request types:
43 40  
... ... @@ -58,7 +55,7 @@ Return values:
58 55 * `403 Forbidden` - The request is not allowed, e.g. the user is not allowed to delete a project
59 56 * `404 Not Found` - A resource could not be accessed, e.g. an ID for a resource could not be found
60 57 * `405 Method Not Allowed` - The request is not supported
61   -* `409 Conflict` - A conflicting resource already exists, a project with same name already exists
  58 +* `409 Conflict` - A conflicting resource already exists, e.g. creating a project with a name that already exists
62 59 * `500 Server Error` - While handling the request something went wrong on the server side
63 60  
64 61  
... ...
doc/api/groups.md
... ... @@ -44,3 +44,14 @@ Parameters:
44 44 + `name` (required) - The name of the group
45 45 + `path` (required) - The path of the group
46 46  
  47 +## Transfer project to group
  48 +
  49 +Transfer a project to the Group namespace. Available only for admin
  50 +
  51 +```
  52 +POST /groups/:id/projects/:project_id
  53 +```
  54 +
  55 +Parameters:
  56 ++ `id` (required) - The ID of a group
  57 ++ `project_id (required) - The ID of a project
... ...
doc/api/projects.md
... ... @@ -115,11 +115,9 @@ Parameters:
115 115 + `merge_requests_enabled` (optional) - enabled by default
116 116 + `wiki_enabled` (optional) - enabled by default
117 117  
  118 +**Project access levels**
118 119  
119   -## Project access levels
120   -
121   -The project access levels are defined in the `user_project` class. Currently, 4
122   -levels are recoginized:
  120 +The project access levels are defined in the `user_project.rb` class. Currently, these levels are recoginized:
123 121  
124 122 ```
125 123 GUEST = 10
... ... @@ -129,7 +127,30 @@ levels are recoginized:
129 127 ```
130 128  
131 129  
132   -## List project team members
  130 +### Create project for user
  131 +
  132 +Creates a new project owned by user. Available only for admins.
  133 +
  134 +```
  135 +POST /projects/user/:user_id
  136 +```
  137 +
  138 +Parameters:
  139 +
  140 ++ `user_id` (required) - user_id of owner
  141 ++ `name` (required) - new project name
  142 ++ `description` (optional) - short project description
  143 ++ `default_branch` (optional) - 'master' by default
  144 ++ `issues_enabled` (optional) - enabled by default
  145 ++ `wall_enabled` (optional) - enabled by default
  146 ++ `merge_requests_enabled` (optional) - enabled by default
  147 ++ `wiki_enabled` (optional) - enabled by default
  148 +
  149 +
  150 +
  151 +## Team members
  152 +
  153 +### List project team members
133 154  
134 155 Get a list of project team members.
135 156  
... ... @@ -140,14 +161,12 @@ GET /projects/:id/members
140 161 Parameters:
141 162  
142 163 + `id` (required) - The ID or NAME of a project
143   -+ `query` - Query string
144   -
  164 ++ `query` (optional) - Query string to search for members
145 165  
146   -## Team members
147 166  
148 167 ### Get project team member
149 168  
150   -Get a project team member.
  169 +Gets a project team member.
151 170  
152 171 ```
153 172 GET /projects/:id/members/:user_id
... ... @@ -175,7 +194,7 @@ Parameters:
175 194  
176 195 Adds a user to a project team. This is an idempotent method and can be called multiple times
177 196 with the same parameters. Adding team membership to a user that is already a member does not
178   -affect the membership.
  197 +affect the existing membership.
179 198  
180 199 ```
181 200 POST /projects/:id/members
... ... @@ -190,7 +209,7 @@ Parameters:
190 209  
191 210 ### Edit project team member
192 211  
193   -Update project team member to specified access level.
  212 +Updates project team member to a specified access level.
194 213  
195 214 ```
196 215 PUT /projects/:id/members/:user_id
... ... @@ -398,81 +417,90 @@ Returns values:
398 417 + `404 Not Found` if project with id or the branch with `ref_name` not found
399 418  
400 419  
401   -## Snippets
402 420  
403   -### List snippets
  421 +## Deploy Keys
  422 +
  423 +### List deploy keys
404 424  
405   -Lists the snippets of a project.
  425 +Get a list of a project's deploy keys.
406 426  
407 427 ```
408   -GET /projects/:id/snippets
  428 +GET /projects/:id/keys
409 429 ```
410 430  
411 431 Parameters:
412 432  
413 433 + `id` (required) - The ID of the project
414 434  
415   -
416   -### List single snippet
417   -
418   -Lists a single snippet of a project
419   -
420   -```
421   -GET /projects/:id/snippets/:snippet_id
  435 +```json
  436 +[
  437 + {
  438 + "id": 1,
  439 + "title" : "Public key"
  440 + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
  441 + 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
  442 + soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
  443 + },
  444 + {
  445 + "id": 3,
  446 + "title" : "Another Public key"
  447 + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
  448 + 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
  449 + soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
  450 + }
  451 +]
422 452 ```
423 453  
424   -Parameters:
425   -
426   -+ `id` (required) - The ID of the project
427   -+ `snippet_id` (required) - The ID of the snippet
428 454  
  455 +### Single deploy key
429 456  
430   -### Create snippet
431   -
432   -Creates a new project snippet.
  457 +Get a single key.
433 458  
434 459 ```
435   -POST /projects/:id/snippets
  460 +GET /projects/:id/keys/:key_id
436 461 ```
437 462  
438 463 Parameters:
439 464  
440 465 + `id` (required) - The ID of the project
441   -+ `title` (required) - The title of the new snippet
442   -+ `file_name` (required) - The file name of the snippet
443   -+ `code` (required) - The content of the snippet
444   -+ `lifetime` (optional) - The expiration date of a snippet
  466 ++ `key_id` (required) - The ID of the deploy key
445 467  
  468 +```json
  469 +{
  470 + "id": 1,
  471 + "title" : "Public key"
  472 + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
  473 + 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
  474 + soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
  475 +}
  476 +```
446 477  
447   -### Update snippet
448 478  
449   -Updates an existing project snippet.
  479 +### Add deploy key
  480 +
  481 +Creates a new deploy key for a project.
450 482  
451 483 ```
452   -PUT /projects/:id/snippets/:snippet_id
  484 +POST /projects/:id/keys
453 485 ```
454 486  
455 487 Parameters:
456 488  
457 489 + `id` (required) - The ID of the project
458   -+ `snippet_id` (required) - The id of the project snippet
459   -+ `title` (optional) - The new title of the project snippet
460   -+ `file_name` (optional) - The new file name of the project snippet
461   -+ `lifetime` (optional) - The new expiration date of the snippet
462   -+ `code` (optional) - The content of the snippet
  490 ++ `title` (required) - New deploy key's title
  491 ++ `key` (required) - New deploy key
463 492  
464 493  
465   -## Delete snippet
  494 +### Delete deploy key
466 495  
467   -Deletes a project snippet. This is an idempotent function call and returns `200 Ok`
468   -even if the snippet with the id is not available.
  496 +Delete a deploy key from a project
469 497  
470 498 ```
471   -DELETE /projects/:id/snippets/:snippet_id
  499 +DELETE /projects/:id/keys/:key_id
472 500 ```
473 501  
474   -Paramaters:
  502 +Parameters:
475 503  
476 504 + `id` (required) - The ID of the project
477   -+ `snippet_id` (required) - The ID of the snippet
  505 ++ `key_id` (required) - The ID of the deploy key
478 506  
... ...
doc/api/snippets.md
... ... @@ -46,7 +46,7 @@ Parameters:
46 46  
47 47 ## Create new snippet
48 48  
49   -Creates a new project snippet.
  49 +Creates a new project snippet. The user must have permission to create new snippets.
50 50  
51 51 ```
52 52 POST /projects/:id/snippets
... ... @@ -61,9 +61,9 @@ Parameters:
61 61 + `code` (required) - The content of a snippet
62 62  
63 63  
64   -## Edit snippet
  64 +## Update snippet
65 65  
66   -Updates an existing project snippet.
  66 +Updates an existing project snippet. The user must have permission to change an existing snippet.
67 67  
68 68 ```
69 69 PUT /projects/:id/snippets/:snippet_id
... ... @@ -96,7 +96,7 @@ Parameters:
96 96  
97 97 ## Snippet content
98 98  
99   -Get a raw project snippet.
  99 +Returns the raw project snippet as plain text.
100 100  
101 101 ```
102 102 GET /projects/:id/snippets/:snippet_id/raw
... ...
doc/api/system_hooks.md 0 → 100644
... ... @@ -0,0 +1,47 @@
  1 +All methods require admin authorization.
  2 +
  3 +## List system hooks
  4 +
  5 +Get list of system hooks
  6 +
  7 +```
  8 +GET /hooks
  9 +```
  10 +
  11 +Will return hooks with status `200 OK` on success, or `404 Not found` on fail.
  12 +
  13 +## Add new system hook hook
  14 +
  15 +```
  16 +POST /hooks
  17 +```
  18 +
  19 +Parameters:
  20 +
  21 ++ `url` (required) - The hook URL
  22 +
  23 +Will return status `201 Created` on success, or `404 Not found` on fail.
  24 +
  25 +## Test system hook
  26 +
  27 +```
  28 +GET /hooks/:id
  29 +```
  30 +
  31 +Parameters:
  32 +
  33 ++ `id` (required) - The ID of hook
  34 +
  35 +Will return hook with status `200 OK` on success, or `404 Not found` on fail.
  36 +
  37 +## Delete system hook
  38 +
  39 +```
  40 +DELETE /hooks/:id
  41 +```
  42 +
  43 +Parameters:
  44 +
  45 ++ `id` (required) - The ID of hook
  46 +
  47 +Will return status `200 OK` on success, or `404 Not found` on fail.
0 48 \ No newline at end of file
... ...
doc/api/users.md
... ... @@ -235,6 +235,23 @@ Parameters:
235 235 + `key` (required) - new SSH key
236 236  
237 237  
  238 +## Add SSH key for user
  239 +
  240 +Create new key owned by specified user. Available only for admin
  241 +
  242 +```
  243 +POST /users/:id/keys
  244 +```
  245 +
  246 +Parameters:
  247 +
  248 ++ `id` (required) - id of specified user
  249 ++ `title` (required) - new SSH Key's title
  250 ++ `key` (required) - new SSH key
  251 +
  252 +Will return created key with status `201 Created` on success, or `404 Not
  253 +found` on fail.
  254 +
238 255 ## Delete SSH key
239 256  
240 257 Deletes key owned by currently authenticated user. This is an idempotent function and calling it on a key that is already
... ...
doc/install/installation.md
... ... @@ -288,7 +288,7 @@ a different host, you can configure its connection string via the
288 288 `config/resque.yml` file.
289 289  
290 290 # example
291   - production: redis.example.tld:6379
  291 + production: redis://redis.example.tld:6379
292 292  
293 293 ## Custom SSH Connection
294 294  
... ...
features/steps/project/project_network_graph.rb
... ... @@ -27,6 +27,7 @@ class ProjectNetworkGraph < Spinach::FeatureSteps
27 27  
28 28 And 'I switch ref to "stable"' do
29 29 page.select 'stable', :from => 'ref'
  30 + sleep 2
30 31 end
31 32  
32 33 And 'page should select "stable" in select box' do
... ... @@ -44,6 +45,7 @@ class ProjectNetworkGraph < Spinach::FeatureSteps
44 45 fill_in 'q', :with => '98d6492'
45 46 find('button').click
46 47 end
  48 + sleep 2
47 49 end
48 50  
49 51 And 'page should have "v2.1.0" on graph' do
... ...
lib/api.rb
... ... @@ -33,5 +33,6 @@ module Gitlab
33 33 mount MergeRequests
34 34 mount Notes
35 35 mount Internal
  36 + mount SystemHooks
36 37 end
37 38 end
... ...
lib/api/groups.rb
... ... @@ -56,6 +56,24 @@ module Gitlab
56 56 not_found!
57 57 end
58 58 end
  59 +
  60 + # Transfer a project to the Group namespace
  61 + #
  62 + # Parameters:
  63 + # id - group id
  64 + # project_id - project id
  65 + # Example Request:
  66 + # POST /groups/:id/projects/:project_id
  67 + post ":id/projects/:project_id" do
  68 + authenticated_as_admin!
  69 + @group = Group.find(params[:id])
  70 + project = Project.find(params[:project_id])
  71 + if project.transfer(@group)
  72 + present @group
  73 + else
  74 + not_found!
  75 + end
  76 + end
59 77 end
60 78 end
61 79 end
... ...
lib/api/merge_requests.rb
... ... @@ -8,6 +8,8 @@ module Gitlab
8 8 def handle_merge_request_errors!(errors)
9 9 if errors[:project_access].any?
10 10 error!(errors[:project_access], 422)
  11 + elsif errors[:branch_conflict].any?
  12 + error!(errors[:branch_conflict], 422)
11 13 end
12 14 not_found!
13 15 end
... ...
lib/api/projects.rb
... ... @@ -64,6 +64,38 @@ module Gitlab
64 64 end
65 65 end
66 66  
  67 + # Create new project for a specified user. Only available to admin users.
  68 + #
  69 + # Parameters:
  70 + # user_id (required) - The ID of a user
  71 + # name (required) - name for new project
  72 + # description (optional) - short project description
  73 + # default_branch (optional) - 'master' by default
  74 + # issues_enabled (optional) - enabled by default
  75 + # wall_enabled (optional) - enabled by default
  76 + # merge_requests_enabled (optional) - enabled by default
  77 + # wiki_enabled (optional) - enabled by default
  78 + # Example Request
  79 + # POST /projects/user/:user_id
  80 + post "user/:user_id" do
  81 + authenticated_as_admin!
  82 + user = User.find(params[:user_id])
  83 + attrs = attributes_for_keys [:name,
  84 + :description,
  85 + :default_branch,
  86 + :issues_enabled,
  87 + :wall_enabled,
  88 + :merge_requests_enabled,
  89 + :wiki_enabled]
  90 + @project = ::Projects::CreateContext.new(user, attrs).execute
  91 + if @project.saved?
  92 + present @project, with: Entities::Project
  93 + else
  94 + not_found!
  95 + end
  96 + end
  97 +
  98 +
67 99 # Get a project team members
68 100 #
69 101 # Parameters:
... ... @@ -471,6 +503,49 @@ module Gitlab
471 503 present tree.data
472 504 end
473 505  
  506 + # Get a specific project's keys
  507 + #
  508 + # Example Request:
  509 + # GET /projects/:id/keys
  510 + get ":id/keys" do
  511 + present user_project.deploy_keys, with: Entities::SSHKey
  512 + end
  513 +
  514 + # Get single key owned by currently authenticated user
  515 + #
  516 + # Example Request:
  517 + # GET /projects/:id/keys/:id
  518 + get ":id/keys/:key_id" do
  519 + key = user_project.deploy_keys.find params[:key_id]
  520 + present key, with: Entities::SSHKey
  521 + end
  522 +
  523 + # Add new ssh key to currently authenticated user
  524 + #
  525 + # Parameters:
  526 + # key (required) - New SSH Key
  527 + # title (required) - New SSH Key's title
  528 + # Example Request:
  529 + # POST /projects/:id/keys
  530 + post ":id/keys" do
  531 + attrs = attributes_for_keys [:title, :key]
  532 + key = user_project.deploy_keys.new attrs
  533 + if key.save
  534 + present key, with: Entities::SSHKey
  535 + else
  536 + not_found!
  537 + end
  538 + end
  539 +
  540 + # Delete existed ssh key of currently authenticated user
  541 + #
  542 + # Example Request:
  543 + # DELETE /projects/:id/keys/:id
  544 + delete ":id/keys/:key_id" do
  545 + key = user_project.deploy_keys.find params[:key_id]
  546 + key.delete
  547 + end
  548 +
474 549 end
475 550 end
476 551 end
... ...
lib/api/system_hooks.rb 0 → 100644
... ... @@ -0,0 +1,60 @@
  1 +module Gitlab
  2 + # Hooks API
  3 + class SystemHooks < Grape::API
  4 + before { authenticated_as_admin! }
  5 +
  6 + resource :hooks do
  7 + # Get the list of system hooks
  8 + #
  9 + # Example Request:
  10 + # GET /hooks
  11 + get do
  12 + @hooks = SystemHook.all
  13 + present @hooks, with: Entities::Hook
  14 + end
  15 +
  16 + # Create new system hook
  17 + #
  18 + # Parameters:
  19 + # url (required) - url for system hook
  20 + # Example Request
  21 + # POST /hooks
  22 + post do
  23 + attrs = attributes_for_keys [:url]
  24 + @hook = SystemHook.new attrs
  25 + if @hook.save
  26 + present @hook, with: Entities::Hook
  27 + else
  28 + not_found!
  29 + end
  30 + end
  31 +
  32 + # Test a hook
  33 + #
  34 + # Example Request
  35 + # GET /hooks/:id
  36 + get ":id" do
  37 + @hook = SystemHook.find(params[:id])
  38 + data = {
  39 + event_name: "project_create",
  40 + name: "Ruby",
  41 + path: "ruby",
  42 + project_id: 1,
  43 + owner_name: "Someone",
  44 + owner_email: "example@gitlabhq.com"
  45 + }
  46 + @hook.execute(data)
  47 + data
  48 + end
  49 +
  50 + # Delete a hook
  51 + #
  52 + # Example Request:
  53 + # DELETE /hooks/:id
  54 + delete ":id" do
  55 + @hook = SystemHook.find(params[:id])
  56 + @hook.destroy
  57 + end
  58 + end
  59 + end
  60 +end
0 61 \ No newline at end of file
... ...
lib/api/users.rb
... ... @@ -81,6 +81,26 @@ module Gitlab
81 81 end
82 82 end
83 83  
  84 + # Add ssh key to a specified user. Only available to admin users.
  85 + #
  86 + # Parameters:
  87 + # id (required) - The ID of a user
  88 + # key (required) - New SSH Key
  89 + # title (required) - New SSH Key's title
  90 + # Example Request:
  91 + # POST /users/:id/keys
  92 + post ":id/keys" do
  93 + authenticated_as_admin!
  94 + user = User.find(params[:id])
  95 + attrs = attributes_for_keys [:title, :key]
  96 + key = user.keys.new attrs
  97 + if key.save
  98 + present key, with: Entities::SSHKey
  99 + else
  100 + not_found!
  101 + end
  102 + end
  103 +
84 104 # Delete user. Available only for admin
85 105 #
86 106 # Example Request:
... ...
lib/extracts_path.rb
... ... @@ -105,12 +105,6 @@ module ExtractsPath
105 105 # Automatically renders `not_found!` if a valid tree path could not be
106 106 # resolved (e.g., when a user inserts an invalid path or ref).
107 107 def assign_ref_vars
108   - # Handle formats embedded in the id
109   - if params[:id].ends_with?('.atom')
110   - params[:id].gsub!(/\.atom$/, '')
111   - request.format = :atom
112   - end
113   -
114 108 path = CGI::unescape(request.fullpath.dup)
115 109  
116 110 @ref, @path = extract_ref(path)
... ...
lib/tasks/gitlab/setup.rake
... ... @@ -7,10 +7,12 @@ namespace :gitlab do
7 7 def setup_db
8 8 warn_user_is_not_gitlab
9 9  
10   - puts "This will create the necessary database tables and seed the database."
11   - puts "You will lose any previous data stored in the database."
12   - ask_to_continue
13   - puts ""
  10 + unless ENV['force'] == 'yes'
  11 + puts "This will create the necessary database tables and seed the database."
  12 + puts "You will lose any previous data stored in the database."
  13 + ask_to_continue
  14 + puts ""
  15 + end
14 16  
15 17 Rake::Task["db:setup"].invoke
16 18 Rake::Task["db:seed_fu"].invoke
... ...
spec/controllers/commits_controller_spec.rb
... ... @@ -13,7 +13,7 @@ describe CommitsController do
13 13 describe "GET show" do
14 14 context "as atom feed" do
15 15 it "should render as atom" do
16   - get :show, project_id: project.path, id: "master.atom"
  16 + get :show, project_id: project.path, id: "master", format: "atom"
17 17 response.should be_success
18 18 response.content_type.should == 'application/atom+xml'
19 19 end
... ...
spec/requests/api/groups_spec.rb
... ... @@ -100,4 +100,27 @@ describe Gitlab::API do
100 100 end
101 101 end
102 102 end
  103 +
  104 + describe "POST /groups/:id/projects/:project_id" do
  105 + let(:project) { create(:project) }
  106 + before(:each) do
  107 + project.stub!(:transfer).and_return(true)
  108 + Project.stub(:find).and_return(project)
  109 + end
  110 +
  111 +
  112 + context "when authenticated as user" do
  113 + it "should not transfer project to group" do
  114 + post api("/groups/#{group1.id}/projects/#{project.id}", user2)
  115 + response.status.should == 403
  116 + end
  117 + end
  118 +
  119 + context "when authenticated as admin" do
  120 + it "should transfer project to group" do
  121 + project.should_receive(:transfer)
  122 + post api("/groups/#{group1.id}/projects/#{project.id}", admin)
  123 + end
  124 + end
  125 + end
103 126 end
... ...
spec/requests/api/notes_spec.rb
... ... @@ -105,13 +105,6 @@ describe Gitlab::API do
105 105 response.status.should == 404
106 106 end
107 107 end
108   -
109   - context "when notable is invalid" do
110   - it "should return a 404 error" do
111   - get api("/projects/#{project.id}/unknown/#{snippet.id}/notes", user)
112   - response.status.should == 404
113   - end
114   - end
115 108 end
116 109  
117 110 describe "GET /projects/:id/noteable/:noteable_id/notes/:note_id" do
... ... @@ -180,12 +173,5 @@ describe Gitlab::API do
180 173 response.status.should == 401
181 174 end
182 175 end
183   -
184   - context "when noteable is invalid" do
185   - it "should return a 404 error" do
186   - post api("/projects/#{project.id}/invalid/#{snippet.id}/notes", user)
187   - response.status.should == 404
188   - end
189   - end
190 176 end
191 177 end
... ...
spec/requests/api/projects_spec.rb
... ... @@ -6,11 +6,14 @@ describe Gitlab::API do
6 6 let(:user) { create(:user) }
7 7 let(:user2) { create(:user) }
8 8 let(:user3) { create(:user) }
  9 + let(:admin) { create(:admin) }
9 10 let!(:project) { create(:project, namespace: user.namespace ) }
10 11 let!(:hook) { create(:project_hook, project: project, url: "http://example.com") }
11 12 let!(:snippet) { create(:snippet, author: user, project: project, title: 'example') }
12 13 let!(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) }
13 14 let!(:users_project2) { create(:users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER) }
  15 + let(:key) { create(:key, project: project) }
  16 +
14 17 before { project.team << [user, :reporter] }
15 18  
16 19 describe "GET /projects" do
... ... @@ -103,6 +106,46 @@ describe Gitlab::API do
103 106 end
104 107 end
105 108  
  109 + describe "POST /projects/user/:id" do
  110 + before { admin }
  111 +
  112 + it "should create new project without path" do
  113 + expect { post api("/projects/user/#{user.id}", admin), name: 'foo' }.to change {Project.count}.by(1)
  114 + end
  115 +
  116 + it "should not create new project without name" do
  117 + expect { post api("/projects/user/#{user.id}", admin) }.to_not change {Project.count}
  118 + end
  119 +
  120 + it "should respond with 201 on success" do
  121 + post api("/projects/user/#{user.id}", admin), name: 'foo'
  122 + response.status.should == 201
  123 + end
  124 +
  125 + it "should respond with 404 on failure" do
  126 + post api("/projects/user/#{user.id}", admin)
  127 + response.status.should == 404
  128 + end
  129 +
  130 + it "should assign attributes to project" do
  131 + project = attributes_for(:project, {
  132 + description: Faker::Lorem.sentence,
  133 + default_branch: 'stable',
  134 + issues_enabled: false,
  135 + wall_enabled: false,
  136 + merge_requests_enabled: false,
  137 + wiki_enabled: false
  138 + })
  139 +
  140 + post api("/projects/user/#{user.id}", admin), project
  141 +
  142 + project.each_pair do |k,v|
  143 + next if k == :path
  144 + json_response[k.to_s].should == v
  145 + end
  146 + end
  147 + end
  148 +
106 149 describe "GET /projects/:id" do
107 150 it "should return a project by id" do
108 151 get api("/projects/#{project.id}", user)
... ... @@ -591,4 +634,59 @@ describe Gitlab::API do
591 634 response.status.should == 400
592 635 end
593 636 end
  637 +
  638 + describe "GET /projects/:id/keys" do
  639 + it "should return array of ssh keys" do
  640 + project.deploy_keys << key
  641 + project.save
  642 + get api("/projects/#{project.id}/keys", user)
  643 + response.status.should == 200
  644 + json_response.should be_an Array
  645 + json_response.first['title'].should == key.title
  646 + end
  647 + end
  648 +
  649 + describe "GET /projects/:id/keys/:key_id" do
  650 + it "should return a single key" do
  651 + project.deploy_keys << key
  652 + project.save
  653 + get api("/projects/#{project.id}/keys/#{key.id}", user)
  654 + response.status.should == 200
  655 + json_response['title'].should == key.title
  656 + end
  657 +
  658 + it "should return 404 Not Found with invalid ID" do
  659 + get api("/projects/#{project.id}/keys/404", user)
  660 + response.status.should == 404
  661 + end
  662 + end
  663 +
  664 + describe "POST /projects/:id/keys" do
  665 + it "should not create an invalid ssh key" do
  666 + post api("/projects/#{project.id}/keys", user), { title: "invalid key" }
  667 + response.status.should == 404
  668 + end
  669 +
  670 + it "should create new ssh key" do
  671 + key_attrs = attributes_for :key
  672 + expect {
  673 + post api("/projects/#{project.id}/keys", user), key_attrs
  674 + }.to change{ project.deploy_keys.count }.by(1)
  675 + end
  676 + end
  677 +
  678 + describe "DELETE /projects/:id/keys/:key_id" do
  679 + it "should delete existing key" do
  680 + project.deploy_keys << key
  681 + project.save
  682 + expect {
  683 + delete api("/projects/#{project.id}/keys/#{key.id}", user)
  684 + }.to change{ project.deploy_keys.count }.by(-1)
  685 + end
  686 +
  687 + it "should return 404 Not Found with invalid ID" do
  688 + delete api("/projects/#{project.id}/keys/404", user)
  689 + response.status.should == 404
  690 + end
  691 + end
594 692 end
... ...
spec/requests/api/system_hooks_spec.rb 0 → 100644
... ... @@ -0,0 +1,69 @@
  1 +require 'spec_helper'
  2 +
  3 +describe Gitlab::API do
  4 + include ApiHelpers
  5 +
  6 + let(:user) { create(:user) }
  7 + let(:admin) { create(:admin) }
  8 + let!(:hook) { create(:system_hook, url: "http://example.com") }
  9 +
  10 + before { stub_request(:post, hook.url) }
  11 +
  12 + describe "GET /hooks" do
  13 + context "when not an admin" do
  14 + it "should return forbidden error" do
  15 + get api("/hooks", user)
  16 + response.status.should == 403
  17 + end
  18 + end
  19 +
  20 + context "when authenticated as admin" do
  21 + it "should return an array of hooks" do
  22 + get api("/hooks", admin)
  23 + response.status.should == 200
  24 + json_response.should be_an Array
  25 + json_response.first['url'].should == hook.url
  26 + end
  27 + end
  28 + end
  29 +
  30 + describe "POST /hooks" do
  31 + it "should create new hook" do
  32 + expect {
  33 + post api("/hooks", admin), url: 'http://example.com'
  34 + }.to change { SystemHook.count }.by(1)
  35 + end
  36 +
  37 + it "should respond with 404 on failure" do
  38 + post api("/hooks", admin)
  39 + response.status.should == 404
  40 + end
  41 +
  42 + it "should not create new hook without url" do
  43 + expect {
  44 + post api("/hooks", admin)
  45 + }.to_not change { SystemHook.count }
  46 + end
  47 + end
  48 +
  49 + describe "GET /hooks/:id" do
  50 + it "should return hook by id" do
  51 + get api("/hooks/#{hook.id}", admin)
  52 + response.status.should == 200
  53 + json_response['event_name'].should == 'project_create'
  54 + end
  55 +
  56 + it "should return 404 on failure" do
  57 + get api("/hooks/404", admin)
  58 + response.status.should == 404
  59 + end
  60 + end
  61 +
  62 + describe "DELETE /hooks/:id" do
  63 + it "should delete a hook" do
  64 + expect {
  65 + delete api("/hooks/#{hook.id}", admin)
  66 + }.to change { SystemHook.count }.by(-1)
  67 + end
  68 + end
  69 +end
0 70 \ No newline at end of file
... ...
spec/requests/api/users_spec.rb
... ... @@ -167,6 +167,22 @@ describe Gitlab::API do
167 167 end
168 168 end
169 169  
  170 + describe "POST /users/:id/keys" do
  171 + before { admin }
  172 +
  173 + it "should not create invalid ssh key" do
  174 + post api("/users/#{user.id}/keys", admin), { title: "invalid key" }
  175 + response.status.should == 404
  176 + end
  177 +
  178 + it "should create ssh key" do
  179 + key_attrs = attributes_for :key
  180 + expect {
  181 + post api("/users/#{user.id}/keys", admin), key_attrs
  182 + }.to change{ user.keys.count }.by(1)
  183 + end
  184 + end
  185 +
170 186 describe "DELETE /users/:id" do
171 187 before { admin }
172 188  
... ...
spec/routing/project_routing_spec.rb
... ... @@ -56,7 +56,6 @@ end
56 56 # projects POST /projects(.:format) projects#create
57 57 # new_project GET /projects/new(.:format) projects#new
58 58 # wall_project GET /:id/wall(.:format) projects#wall
59   -# graph_project GET /:id/graph(.:format) projects#graph
60 59 # files_project GET /:id/files(.:format) projects#files
61 60 # edit_project GET /:id/edit(.:format) projects#edit
62 61 # project GET /:id(.:format) projects#show
... ... @@ -75,10 +74,6 @@ describe ProjectsController, &quot;routing&quot; do
75 74 get("/gitlabhq/wall").should route_to('projects#wall', id: 'gitlabhq')
76 75 end
77 76  
78   - it "to #graph" do
79   - get("/gitlabhq/graph/master").should route_to('graph#show', project_id: 'gitlabhq', id: 'master')
80   - end
81   -
82 77 it "to #files" do
83 78 get("/gitlabhq/files").should route_to('projects#files', id: 'gitlabhq')
84 79 end
... ... @@ -202,6 +197,7 @@ describe RefsController, &quot;routing&quot; do
202 197 it "to #logs_tree" do
203 198 get("/gitlabhq/refs/stable/logs_tree").should route_to('refs#logs_tree', project_id: 'gitlabhq', id: 'stable')
204 199 get("/gitlabhq/refs/stable/logs_tree/foo/bar/baz").should route_to('refs#logs_tree', project_id: 'gitlabhq', id: 'stable', path: 'foo/bar/baz')
  200 + get("/gitlab/gitlabhq/refs/stable/logs_tree/files.scss").should route_to('refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'stable', path: 'files.scss')
205 201 end
206 202 end
207 203  
... ... @@ -301,6 +297,10 @@ describe CommitsController, &quot;routing&quot; do
301 297 let(:actions) { [:show] }
302 298 let(:controller) { 'commits' }
303 299 end
  300 +
  301 + it "to #show" do
  302 + get("/gitlab/gitlabhq/commits/master.atom").should route_to('commits#show', project_id: 'gitlab/gitlabhq', id: "master", format: "atom")
  303 + end
304 304 end
305 305  
306 306 # project_team_members GET /:project_id/team_members(.:format) team_members#index
... ... @@ -385,6 +385,7 @@ end
385 385 describe BlameController, "routing" do
386 386 it "to #show" do
387 387 get("/gitlabhq/blame/master/app/models/project.rb").should route_to('blame#show', project_id: 'gitlabhq', id: 'master/app/models/project.rb')
  388 + get("/gitlab/gitlabhq/blame/master/files.scss").should route_to('blame#show', project_id: 'gitlab/gitlabhq', id: 'master/files.scss')
388 389 end
389 390 end
390 391  
... ... @@ -393,6 +394,7 @@ describe BlobController, &quot;routing&quot; do
393 394 it "to #show" do
394 395 get("/gitlabhq/blob/master/app/models/project.rb").should route_to('blob#show', project_id: 'gitlabhq', id: 'master/app/models/project.rb')
395 396 get("/gitlabhq/blob/master/app/models/compare.rb").should route_to('blob#show', project_id: 'gitlabhq', id: 'master/app/models/compare.rb')
  397 + get("/gitlab/gitlabhq/blob/master/files.scss").should route_to('blob#show', project_id: 'gitlab/gitlabhq', id: 'master/files.scss')
396 398 end
397 399 end
398 400  
... ... @@ -400,6 +402,7 @@ end
400 402 describe TreeController, "routing" do
401 403 it "to #show" do
402 404 get("/gitlabhq/tree/master/app/models/project.rb").should route_to('tree#show', project_id: 'gitlabhq', id: 'master/app/models/project.rb')
  405 + get("/gitlab/gitlabhq/tree/master/files.scss").should route_to('tree#show', project_id: 'gitlab/gitlabhq', id: 'master/files.scss')
403 406 end
404 407 end
405 408  
... ... @@ -420,3 +423,10 @@ describe CompareController, &quot;routing&quot; do
420 423 get("/gitlabhq/compare/issue/1234...stable").should route_to('compare#show', project_id: 'gitlabhq', from: 'issue/1234', to: 'stable')
421 424 end
422 425 end
  426 +
  427 +describe GraphController, "routing" do
  428 + it "to #show" do
  429 + get("/gitlabhq/graph/master").should route_to('graph#show', project_id: 'gitlabhq', id: 'master')
  430 + get("/gitlabhq/graph/master.json").should route_to('graph#show', project_id: 'gitlabhq', id: 'master', format: "json")
  431 + end
  432 +end
... ...
spec/support/stubbed_repository.rb
... ... @@ -43,6 +43,11 @@ class GitLabTestRepo &lt; Repository
43 43 def repo
44 44 @repo ||= Grit::Repo.new(Rails.root.join('tmp', 'repositories', 'gitlabhq'))
45 45 end
  46 +
  47 + # patch repo size (in mb)
  48 + def size
  49 + 12.45
  50 + end
46 51 end
47 52  
48 53 module Gitlab
... ...