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 @@ @@ -0,0 +1,4 @@
  1 +load 'deploy'
  2 +load 'deploy/assets'
  3 +require 'bundler/capistrano'
  4 +load 'config/deploy'
1 -source "http://rubygems.org" 1 +source "https://rubygems.org"
2 2
3 def darwin_only(require_as) 3 def darwin_only(require_as)
4 RUBY_PLATFORM.include?('darwin') && require_as 4 RUBY_PLATFORM.include?('darwin') && require_as
@@ -103,6 +103,9 @@ gem 'settingslogic' @@ -103,6 +103,9 @@ gem 'settingslogic'
103 gem "foreman" 103 gem "foreman"
104 gem "git" 104 gem "git"
105 105
  106 +# Cache
  107 +gem "redis-rails"
  108 +
106 group :assets do 109 group :assets do
107 gem "sass-rails", "~> 3.2.5" 110 gem "sass-rails", "~> 3.2.5"
108 gem "coffee-rails", "~> 3.2.2" 111 gem "coffee-rails", "~> 3.2.2"
@@ -13,7 +13,7 @@ GIT @@ -13,7 +13,7 @@ GIT
13 raphael-rails (2.1.0) 13 raphael-rails (2.1.0)
14 14
15 GEM 15 GEM
16 - remote: http://rubygems.org/ 16 + remote: https://rubygems.org/
17 specs: 17 specs:
18 actionmailer (3.2.12) 18 actionmailer (3.2.12)
19 actionpack (= 3.2.12) 19 actionpack (= 3.2.12)
@@ -329,8 +329,24 @@ GEM @@ -329,8 +329,24 @@ GEM
329 json (~> 1.4) 329 json (~> 1.4)
330 redcarpet (2.2.2) 330 redcarpet (2.2.2)
331 redis (3.0.2) 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 redis-namespace (1.2.1) 339 redis-namespace (1.2.1)
333 redis (~> 3.0.0) 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 request_store (1.0.5) 350 request_store (1.0.5)
335 rspec (2.12.0) 351 rspec (2.12.0)
336 rspec-core (~> 2.12.0) 352 rspec-core (~> 2.12.0)
@@ -504,6 +520,7 @@ DEPENDENCIES @@ -504,6 +520,7 @@ DEPENDENCIES
504 rb-fsevent 520 rb-fsevent
505 rb-inotify 521 rb-inotify
506 redcarpet (~> 2.2.2) 522 redcarpet (~> 2.2.2)
  523 + redis-rails
507 rspec-rails (= 2.12.2) 524 rspec-rails (= 2.12.2)
508 sass-rails (~> 3.2.5) 525 sass-rails (~> 3.2.5)
509 sdoc 526 sdoc
@@ -5,14 +5,14 @@ @@ -5,14 +5,14 @@
5 ### GitLab allows you to 5 ### GitLab allows you to
6 * keep your code secure on your own server 6 * keep your code secure on your own server
7 * manage repositories, users and access permissions 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 ### GitLab is 11 ### GitLab is
12 12
13 * powered by Ruby on Rails 13 * powered by Ruby on Rails
14 * completely free and open source (MIT license) 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 ### Code status 17 ### Code status
18 18
@@ -34,28 +34,35 @@ @@ -34,28 +34,35 @@
34 34
35 ### Requirements 35 ### Requirements
36 36
37 -* Ubuntu/Debian* 37 +* Ubuntu/Debian**
38 * ruby 1.9.3+ 38 * ruby 1.9.3+
39 * MySQL 39 * MySQL
40 * git 40 * git
41 * gitlab-shell 41 * gitlab-shell
42 * redis 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 ### Installation 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 * [Vagrant virtual machine](https://github.com/gitlabhq/gitlab-vagrant-vm) 61 * [Vagrant virtual machine](https://github.com/gitlabhq/gitlab-vagrant-vm)
55 62
56 ### Starting 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 sudo service gitlab start 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,18 +70,18 @@ You can either follow the "ordinary" Installation guide to install it on a machi
63 70
64 sudo /etc/init.d/gitlab restart 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 bundle exec foreman start -p 3000 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 bundle exec rails s 79 bundle exec rails s
73 bundle exec rake sidekiq:start 80 bundle exec rake sidekiq:start
74 81
75 ### Running the tests 82 ### Running the tests
76 83
77 -* Seed the database with 84 +* Seed the database
78 85
79 bundle exec rake db:setup RAILS_ENV=test 86 bundle exec rake db:setup RAILS_ENV=test
80 bundle exec rake db:seed_fu RAILS_ENV=test 87 bundle exec rake db:seed_fu RAILS_ENV=test
app/assets/javascripts/tree.js.coffee
@@ -11,12 +11,7 @@ $ -> @@ -11,12 +11,7 @@ $ ->
11 # Make the entire tree-item row clickable, but not if clicking another link (like a commit message) 11 # Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
12 $("#tree-slider .tree-item").live 'click', (e) -> 12 $("#tree-slider .tree-item").live 'click', (e) ->
13 $('.tree-item-file-name a', this).trigger('click') if (e.target.nodeName != "A") 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 # Maintain forward/back history while browsing the file tree 15 # Maintain forward/back history while browsing the file tree
21 ((window) -> 16 ((window) ->
22 History = window.History 17 History = window.History
@@ -33,7 +28,12 @@ $ -> @@ -33,7 +28,12 @@ $ ->
33 28
34 History.Adapter.bind window, 'statechange', -> 29 History.Adapter.bind window, 'statechange', ->
35 state = History.getState() 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 )(window) 37 )(window)
38 38
39 # See if there are lines selected 39 # See if there are lines selected
app/assets/stylesheets/gitlab_bootstrap/blocks.scss
@@ -34,13 +34,6 @@ @@ -34,13 +34,6 @@
34 padding: 15px; 34 padding: 15px;
35 word-wrap: break-word; 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 .clearfix { 37 .clearfix {
45 margin: 0; 38 margin: 0;
46 } 39 }
app/assets/stylesheets/gitlab_bootstrap/common.scss
@@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
2 .cgray { color:gray } 2 .cgray { color:gray }
3 .cred { color:#D12F19 } 3 .cred { color:#D12F19 }
4 .cgreen { color:#4a2 } 4 .cgreen { color:#4a2 }
  5 +.cblue { color:#29A }
5 .cblack { color:#111 } 6 .cblack { color:#111 }
6 .cdark { color:#444 } 7 .cdark { color:#444 }
7 .cwhite { color:#fff!important } 8 .cwhite { color:#fff!important }
app/assets/stylesheets/sections/projects.scss
@@ -120,3 +120,16 @@ ul.nav.nav-projects-tabs { @@ -120,3 +120,16 @@ ul.nav.nav-projects-tabs {
120 .team_member_row form { 120 .team_member_row form {
121 margin: 0px; 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,7 +91,7 @@ class MergeRequest < ActiveRecord::Base
91 91
92 def validate_branches 92 def validate_branches
93 if target_branch == source_branch 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 end 95 end
96 end 96 end
97 97
app/models/repository.rb
1 class Repository 1 class Repository
  2 + include Gitlab::Popen
  3 +
2 # Repository directory name with namespace direcotry 4 # Repository directory name with namespace direcotry
3 # Examples: 5 # Examples:
4 # gitlab/gitolite 6 # gitlab/gitolite
@@ -147,4 +149,21 @@ class Repository @@ -147,4 +149,21 @@ class Repository
147 149
148 file_path 150 file_path
149 end 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 end 169 end
app/services/git_push_service.rb
@@ -23,6 +23,7 @@ class GitPushService @@ -23,6 +23,7 @@ class GitPushService
23 23
24 project.ensure_satellite_exists 24 project.ensure_satellite_exists
25 project.discover_default_branch 25 project.discover_default_branch
  26 + project.repository.expire_cache
26 27
27 if push_to_branch?(ref, oldrev) 28 if push_to_branch?(ref, oldrev)
28 project.update_merge_requests(oldrev, newrev, ref, @user) 29 project.update_merge_requests(oldrev, newrev, ref, @user)
app/views/devise/sessions/_new_ldap.html.haml
1 = form_tag(user_omniauth_callback_path(:ldap), :class => "login-box", :id => 'new_ldap_user' ) do 1 = form_tag(user_omniauth_callback_path(:ldap), :class => "login-box", :id => 'new_ldap_user' ) do
2 = image_tag "login-logo.png", :width => "304", :height => "66", :class => "login-logo", :alt => "Login Logo" 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 = password_field_tag :password, nil, {:class => "text bottom", :placeholder => "Password"} 4 = password_field_tag :password, nil, {:class => "text bottom", :placeholder => "Password"}
5 %br/ 5 %br/
6 = submit_tag "LDAP Sign in", :class => "btn-primary btn" 6 = submit_tag "LDAP Sign in", :class => "btn-primary btn"
app/views/events/_event.html.haml
1 - if event.proper? 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,6 +21,8 @@
21 = link_to "Milestones", "#milestones", 'data-toggle' => 'tab' 21 = link_to "Milestones", "#milestones", 'data-toggle' => 'tab'
22 %li 22 %li
23 = link_to "Notes", "#notes", 'data-toggle' => 'tab' 23 = link_to "Notes", "#notes", 'data-toggle' => 'tab'
  24 + %li
  25 + = link_to "System Hooks", "#system_hooks", 'data-toggle' => 'tab'
24 26
25 .tab-content 27 .tab-content
26 .tab-pane.active#README 28 .tab-pane.active#README
@@ -103,3 +105,12 @@ @@ -103,3 +105,12 @@
103 .file_content.wiki 105 .file_content.wiki
104 = preserve do 106 = preserve do
105 = markdown File.read(Rails.root.join("doc", "api", "notes.md")) 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,7 +22,7 @@
22 = mail_to Gitlab.config.gitlab.support_email, "support contact" 22 = mail_to Gitlab.config.gitlab.support_email, "support contact"
23 %li 23 %li
24 Use the 24 Use the
25 - = link_to "search bar", '#', onclick: "$("#search").focus();" 25 + = link_to "search bar", '#', onclick: "$('#search').focus();"
26 on the top of this page 26 on the top of this page
27 %li 27 %li
28 Ask in our 28 Ask in our
app/views/layouts/public.html.haml
@@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
10 = link_to root_path, class: "home" do 10 = link_to root_path, class: "home" do
11 %h1 GITLAB 11 %h1 GITLAB
12 %span.separator 12 %span.separator
13 - %h1.project_name Public Projects 13 + %h1.project_name Public Projects
14 .container 14 .container
15 .content 15 .content
16 .prepend-top-20 16 .prepend-top-20
app/views/projects/_form.html.haml
@@ -9,11 +9,19 @@ @@ -9,11 +9,19 @@
9 Project name is 9 Project name is
10 .input 10 .input
11 = f.text_field :name, placeholder: "Example Project", class: "xxlarge" 11 = f.text_field :name, placeholder: "Example Project", class: "xxlarge"
  12 +
  13 +
12 - unless @repository.heads.empty? 14 - unless @repository.heads.empty?
13 .clearfix 15 .clearfix
14 = f.label :default_branch, "Default Branch" 16 = f.label :default_branch, "Default Branch"
15 .input= f.select(:default_branch, @repository.heads.map(&:name), {}, style: "width:210px;") 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 %fieldset.features 26 %fieldset.features
19 %legend Features: 27 %legend Features:
app/views/projects/show.html.haml
1 = render "project_head" 1 = render "project_head"
2 = render 'clone_panel' 2 = render 'clone_panel'
3 = render "events/event_last_push", event: @last_push 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 :javascript 32 :javascript
8 $(function(){ Pager.init(20); }); 33 $(function(){ Pager.init(20); });
app/views/public/projects/index.html.haml
1 %h3.page_title 1 %h3.page_title
2 - Projects 2 + Projects (#{@projects.total_count})
3 %small with read-only access 3 %small with read-only access
4 %hr 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,9 +11,6 @@
11 - else 11 - else
12 = link_to title, '#' 12 = link_to title, '#'
13 13
14 -.clear  
15 -%div.tree_progress  
16 -  
17 %div#tree-content-holder.tree-content-holder 14 %div#tree-content-holder.tree-content-holder
18 - if tree.is_blob? 15 - if tree.is_blob?
19 = render "tree/blob", blob: tree 16 = render "tree/blob", blob: tree
@@ -40,6 +37,8 @@ @@ -40,6 +37,8 @@
40 - if tree.readme 37 - if tree.readme
41 = render "tree/readme", readme: tree.readme 38 = render "tree/readme", readme: tree.readme
42 39
  40 +%div.tree_progress
  41 +
43 - unless tree.is_blob? 42 - unless tree.is_blob?
44 :javascript 43 :javascript
45 // Load last commit log for each file in tree 44 // Load last commit log for each file in tree
config/deploy.rb.example 0 → 100644
@@ -0,0 +1,72 @@ @@ -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,7 +40,7 @@ Gitlab::Application.configure do
40 # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 40 # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
41 41
42 # Use a different cache store in production 42 # Use a different cache store in production
43 - config.cache_store = :memory_store 43 + config.cache_store = :redis_store
44 44
45 # Enable serving of images, stylesheets, and JavaScripts from an asset server 45 # Enable serving of images, stylesheets, and JavaScripts from an asset server
46 # config.action_controller.asset_host = "http://assets.example.com" 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,19 +4,19 @@ config_file = Rails.root.join('config', 'resque.yml')
4 resque_url = if File.exists?(config_file) 4 resque_url = if File.exists?(config_file)
5 YAML.load_file(config_file)[Rails.env] 5 YAML.load_file(config_file)[Rails.env]
6 else 6 else
7 - "localhost:6379" 7 + "redis://localhost:6379"
8 end 8 end
9 9
10 Sidekiq.configure_server do |config| 10 Sidekiq.configure_server do |config|
11 config.redis = { 11 config.redis = {
12 - url: "redis://#{resque_url}", 12 + url: resque_url,
13 namespace: 'resque:gitlab' 13 namespace: 'resque:gitlab'
14 } 14 }
15 end 15 end
16 16
17 Sidekiq.configure_client do |config| 17 Sidekiq.configure_client do |config|
18 config.redis = { 18 config.redis = {
19 - url: "redis://#{resque_url}", 19 + url: resque_url,
20 namespace: 'resque:gitlab' 20 namespace: 'resque:gitlab'
21 } 21 }
22 end 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,7 +166,7 @@ Gitlab::Application.routes.draw do
166 # 166 #
167 # Project Area 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 member do 170 member do
171 get "wall" 171 get "wall"
172 get "files" 172 get "files"
@@ -175,10 +175,10 @@ Gitlab::Application.routes.draw do @@ -175,10 +175,10 @@ Gitlab::Application.routes.draw do
175 resources :blob, only: [:show], constraints: {id: /.+/} 175 resources :blob, only: [:show], constraints: {id: /.+/}
176 resources :tree, only: [:show, :edit, :update], constraints: {id: /.+/} 176 resources :tree, only: [:show, :edit, :update], constraints: {id: /.+/}
177 resources :commit, only: [:show], constraints: {id: /[[:alnum:]]{6,40}/} 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 resources :compare, only: [:index, :create] 179 resources :compare, only: [:index, :create]
180 resources :blame, only: [:show], constraints: {id: /.+/} 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 match "/compare/:from...:to" => "compare#show", as: "compare", 182 match "/compare/:from...:to" => "compare#show", as: "compare",
183 :via => [:get, :post], constraints: {from: /.+/, to: /.+/} 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,13 +31,10 @@ The API uses JSON to serialize data. You don't need to specify `.json` at the en
31 31
32 ## Status codes 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 API request types: 39 API request types:
43 40
@@ -58,7 +55,7 @@ Return values: @@ -58,7 +55,7 @@ Return values:
58 * `403 Forbidden` - The request is not allowed, e.g. the user is not allowed to delete a project 55 * `403 Forbidden` - The request is not allowed, e.g. the user is not allowed to delete a project
59 * `404 Not Found` - A resource could not be accessed, e.g. an ID for a resource could not be found 56 * `404 Not Found` - A resource could not be accessed, e.g. an ID for a resource could not be found
60 * `405 Method Not Allowed` - The request is not supported 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 * `500 Server Error` - While handling the request something went wrong on the server side 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,3 +44,14 @@ Parameters:
44 + `name` (required) - The name of the group 44 + `name` (required) - The name of the group
45 + `path` (required) - The path of the group 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,11 +115,9 @@ Parameters:
115 + `merge_requests_enabled` (optional) - enabled by default 115 + `merge_requests_enabled` (optional) - enabled by default
116 + `wiki_enabled` (optional) - enabled by default 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 GUEST = 10 123 GUEST = 10
@@ -129,7 +127,30 @@ levels are recoginized: @@ -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 Get a list of project team members. 155 Get a list of project team members.
135 156
@@ -140,14 +161,12 @@ GET /projects/:id/members @@ -140,14 +161,12 @@ GET /projects/:id/members
140 Parameters: 161 Parameters:
141 162
142 + `id` (required) - The ID or NAME of a project 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 ### Get project team member 167 ### Get project team member
149 168
150 -Get a project team member. 169 +Gets a project team member.
151 170
152 ``` 171 ```
153 GET /projects/:id/members/:user_id 172 GET /projects/:id/members/:user_id
@@ -175,7 +194,7 @@ Parameters: @@ -175,7 +194,7 @@ Parameters:
175 194
176 Adds a user to a project team. This is an idempotent method and can be called multiple times 195 Adds a user to a project team. This is an idempotent method and can be called multiple times
177 with the same parameters. Adding team membership to a user that is already a member does not 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 POST /projects/:id/members 200 POST /projects/:id/members
@@ -190,7 +209,7 @@ Parameters: @@ -190,7 +209,7 @@ Parameters:
190 209
191 ### Edit project team member 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 PUT /projects/:id/members/:user_id 215 PUT /projects/:id/members/:user_id
@@ -398,81 +417,90 @@ Returns values: @@ -398,81 +417,90 @@ Returns values:
398 + `404 Not Found` if project with id or the branch with `ref_name` not found 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 Parameters: 431 Parameters:
412 432
413 + `id` (required) - The ID of the project 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 Parameters: 463 Parameters:
439 464
440 + `id` (required) - The ID of the project 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 Parameters: 487 Parameters:
456 488
457 + `id` (required) - The ID of the project 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 + `id` (required) - The ID of the project 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,7 +46,7 @@ Parameters:
46 46
47 ## Create new snippet 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 POST /projects/:id/snippets 52 POST /projects/:id/snippets
@@ -61,9 +61,9 @@ Parameters: @@ -61,9 +61,9 @@ Parameters:
61 + `code` (required) - The content of a snippet 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 PUT /projects/:id/snippets/:snippet_id 69 PUT /projects/:id/snippets/:snippet_id
@@ -96,7 +96,7 @@ Parameters: @@ -96,7 +96,7 @@ Parameters:
96 96
97 ## Snippet content 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 GET /projects/:id/snippets/:snippet_id/raw 102 GET /projects/:id/snippets/:snippet_id/raw
doc/api/system_hooks.md 0 → 100644
@@ -0,0 +1,47 @@ @@ -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 \ No newline at end of file 48 \ No newline at end of file
doc/api/users.md
@@ -235,6 +235,23 @@ Parameters: @@ -235,6 +235,23 @@ Parameters:
235 + `key` (required) - new SSH key 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 ## Delete SSH key 255 ## Delete SSH key
239 256
240 Deletes key owned by currently authenticated user. This is an idempotent function and calling it on a key that is already 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,7 +288,7 @@ a different host, you can configure its connection string via the
288 `config/resque.yml` file. 288 `config/resque.yml` file.
289 289
290 # example 290 # example
291 - production: redis.example.tld:6379 291 + production: redis://redis.example.tld:6379
292 292
293 ## Custom SSH Connection 293 ## Custom SSH Connection
294 294
features/steps/project/project_network_graph.rb
@@ -27,6 +27,7 @@ class ProjectNetworkGraph < Spinach::FeatureSteps @@ -27,6 +27,7 @@ class ProjectNetworkGraph < Spinach::FeatureSteps
27 27
28 And 'I switch ref to "stable"' do 28 And 'I switch ref to "stable"' do
29 page.select 'stable', :from => 'ref' 29 page.select 'stable', :from => 'ref'
  30 + sleep 2
30 end 31 end
31 32
32 And 'page should select "stable" in select box' do 33 And 'page should select "stable" in select box' do
@@ -44,6 +45,7 @@ class ProjectNetworkGraph < Spinach::FeatureSteps @@ -44,6 +45,7 @@ class ProjectNetworkGraph < Spinach::FeatureSteps
44 fill_in 'q', :with => '98d6492' 45 fill_in 'q', :with => '98d6492'
45 find('button').click 46 find('button').click
46 end 47 end
  48 + sleep 2
47 end 49 end
48 50
49 And 'page should have "v2.1.0" on graph' do 51 And 'page should have "v2.1.0" on graph' do
@@ -33,5 +33,6 @@ module Gitlab @@ -33,5 +33,6 @@ module Gitlab
33 mount MergeRequests 33 mount MergeRequests
34 mount Notes 34 mount Notes
35 mount Internal 35 mount Internal
  36 + mount SystemHooks
36 end 37 end
37 end 38 end
lib/api/groups.rb
@@ -56,6 +56,24 @@ module Gitlab @@ -56,6 +56,24 @@ module Gitlab
56 not_found! 56 not_found!
57 end 57 end
58 end 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 end 77 end
60 end 78 end
61 end 79 end
lib/api/merge_requests.rb
@@ -8,6 +8,8 @@ module Gitlab @@ -8,6 +8,8 @@ module Gitlab
8 def handle_merge_request_errors!(errors) 8 def handle_merge_request_errors!(errors)
9 if errors[:project_access].any? 9 if errors[:project_access].any?
10 error!(errors[:project_access], 422) 10 error!(errors[:project_access], 422)
  11 + elsif errors[:branch_conflict].any?
  12 + error!(errors[:branch_conflict], 422)
11 end 13 end
12 not_found! 14 not_found!
13 end 15 end
lib/api/projects.rb
@@ -64,6 +64,38 @@ module Gitlab @@ -64,6 +64,38 @@ module Gitlab
64 end 64 end
65 end 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 # Get a project team members 99 # Get a project team members
68 # 100 #
69 # Parameters: 101 # Parameters:
@@ -471,6 +503,49 @@ module Gitlab @@ -471,6 +503,49 @@ module Gitlab
471 present tree.data 503 present tree.data
472 end 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 end 549 end
475 end 550 end
476 end 551 end
lib/api/system_hooks.rb 0 → 100644
@@ -0,0 +1,60 @@ @@ -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 \ No newline at end of file 61 \ No newline at end of file
lib/api/users.rb
@@ -81,6 +81,26 @@ module Gitlab @@ -81,6 +81,26 @@ module Gitlab
81 end 81 end
82 end 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 # Delete user. Available only for admin 104 # Delete user. Available only for admin
85 # 105 #
86 # Example Request: 106 # Example Request:
lib/extracts_path.rb
@@ -105,12 +105,6 @@ module ExtractsPath @@ -105,12 +105,6 @@ module ExtractsPath
105 # Automatically renders `not_found!` if a valid tree path could not be 105 # Automatically renders `not_found!` if a valid tree path could not be
106 # resolved (e.g., when a user inserts an invalid path or ref). 106 # resolved (e.g., when a user inserts an invalid path or ref).
107 def assign_ref_vars 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 path = CGI::unescape(request.fullpath.dup) 108 path = CGI::unescape(request.fullpath.dup)
115 109
116 @ref, @path = extract_ref(path) 110 @ref, @path = extract_ref(path)
lib/tasks/gitlab/setup.rake
@@ -7,10 +7,12 @@ namespace :gitlab do @@ -7,10 +7,12 @@ namespace :gitlab do
7 def setup_db 7 def setup_db
8 warn_user_is_not_gitlab 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 Rake::Task["db:setup"].invoke 17 Rake::Task["db:setup"].invoke
16 Rake::Task["db:seed_fu"].invoke 18 Rake::Task["db:seed_fu"].invoke
spec/controllers/commits_controller_spec.rb
@@ -13,7 +13,7 @@ describe CommitsController do @@ -13,7 +13,7 @@ describe CommitsController do
13 describe "GET show" do 13 describe "GET show" do
14 context "as atom feed" do 14 context "as atom feed" do
15 it "should render as atom" do 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 response.should be_success 17 response.should be_success
18 response.content_type.should == 'application/atom+xml' 18 response.content_type.should == 'application/atom+xml'
19 end 19 end
spec/requests/api/groups_spec.rb
@@ -100,4 +100,27 @@ describe Gitlab::API do @@ -100,4 +100,27 @@ describe Gitlab::API do
100 end 100 end
101 end 101 end
102 end 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 end 126 end
spec/requests/api/notes_spec.rb
@@ -105,13 +105,6 @@ describe Gitlab::API do @@ -105,13 +105,6 @@ describe Gitlab::API do
105 response.status.should == 404 105 response.status.should == 404
106 end 106 end
107 end 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 end 108 end
116 109
117 describe "GET /projects/:id/noteable/:noteable_id/notes/:note_id" do 110 describe "GET /projects/:id/noteable/:noteable_id/notes/:note_id" do
@@ -180,12 +173,5 @@ describe Gitlab::API do @@ -180,12 +173,5 @@ describe Gitlab::API do
180 response.status.should == 401 173 response.status.should == 401
181 end 174 end
182 end 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 end 176 end
191 end 177 end
spec/requests/api/projects_spec.rb
@@ -6,11 +6,14 @@ describe Gitlab::API do @@ -6,11 +6,14 @@ describe Gitlab::API do
6 let(:user) { create(:user) } 6 let(:user) { create(:user) }
7 let(:user2) { create(:user) } 7 let(:user2) { create(:user) }
8 let(:user3) { create(:user) } 8 let(:user3) { create(:user) }
  9 + let(:admin) { create(:admin) }
9 let!(:project) { create(:project, namespace: user.namespace ) } 10 let!(:project) { create(:project, namespace: user.namespace ) }
10 let!(:hook) { create(:project_hook, project: project, url: "http://example.com") } 11 let!(:hook) { create(:project_hook, project: project, url: "http://example.com") }
11 let!(:snippet) { create(:snippet, author: user, project: project, title: 'example') } 12 let!(:snippet) { create(:snippet, author: user, project: project, title: 'example') }
12 let!(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) } 13 let!(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) }
13 let!(:users_project2) { create(:users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER) } 14 let!(:users_project2) { create(:users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER) }
  15 + let(:key) { create(:key, project: project) }
  16 +
14 before { project.team << [user, :reporter] } 17 before { project.team << [user, :reporter] }
15 18
16 describe "GET /projects" do 19 describe "GET /projects" do
@@ -103,6 +106,46 @@ describe Gitlab::API do @@ -103,6 +106,46 @@ describe Gitlab::API do
103 end 106 end
104 end 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 describe "GET /projects/:id" do 149 describe "GET /projects/:id" do
107 it "should return a project by id" do 150 it "should return a project by id" do
108 get api("/projects/#{project.id}", user) 151 get api("/projects/#{project.id}", user)
@@ -591,4 +634,59 @@ describe Gitlab::API do @@ -591,4 +634,59 @@ describe Gitlab::API do
591 response.status.should == 400 634 response.status.should == 400
592 end 635 end
593 end 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 end 692 end
spec/requests/api/system_hooks_spec.rb 0 → 100644
@@ -0,0 +1,69 @@ @@ -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 \ No newline at end of file 70 \ No newline at end of file
spec/requests/api/users_spec.rb
@@ -167,6 +167,22 @@ describe Gitlab::API do @@ -167,6 +167,22 @@ describe Gitlab::API do
167 end 167 end
168 end 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 describe "DELETE /users/:id" do 186 describe "DELETE /users/:id" do
171 before { admin } 187 before { admin }
172 188
spec/routing/project_routing_spec.rb
@@ -56,7 +56,6 @@ end @@ -56,7 +56,6 @@ end
56 # projects POST /projects(.:format) projects#create 56 # projects POST /projects(.:format) projects#create
57 # new_project GET /projects/new(.:format) projects#new 57 # new_project GET /projects/new(.:format) projects#new
58 # wall_project GET /:id/wall(.:format) projects#wall 58 # wall_project GET /:id/wall(.:format) projects#wall
59 -# graph_project GET /:id/graph(.:format) projects#graph  
60 # files_project GET /:id/files(.:format) projects#files 59 # files_project GET /:id/files(.:format) projects#files
61 # edit_project GET /:id/edit(.:format) projects#edit 60 # edit_project GET /:id/edit(.:format) projects#edit
62 # project GET /:id(.:format) projects#show 61 # project GET /:id(.:format) projects#show
@@ -75,10 +74,6 @@ describe ProjectsController, &quot;routing&quot; do @@ -75,10 +74,6 @@ describe ProjectsController, &quot;routing&quot; do
75 get("/gitlabhq/wall").should route_to('projects#wall', id: 'gitlabhq') 74 get("/gitlabhq/wall").should route_to('projects#wall', id: 'gitlabhq')
76 end 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 it "to #files" do 77 it "to #files" do
83 get("/gitlabhq/files").should route_to('projects#files', id: 'gitlabhq') 78 get("/gitlabhq/files").should route_to('projects#files', id: 'gitlabhq')
84 end 79 end
@@ -202,6 +197,7 @@ describe RefsController, &quot;routing&quot; do @@ -202,6 +197,7 @@ describe RefsController, &quot;routing&quot; do
202 it "to #logs_tree" do 197 it "to #logs_tree" do
203 get("/gitlabhq/refs/stable/logs_tree").should route_to('refs#logs_tree', project_id: 'gitlabhq', id: 'stable') 198 get("/gitlabhq/refs/stable/logs_tree").should route_to('refs#logs_tree', project_id: 'gitlabhq', id: 'stable')
204 get("/gitlabhq/refs/stable/logs_tree/foo/bar/baz").should route_to('refs#logs_tree', project_id: 'gitlabhq', id: 'stable', path: 'foo/bar/baz') 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 end 201 end
206 end 202 end
207 203
@@ -301,6 +297,10 @@ describe CommitsController, &quot;routing&quot; do @@ -301,6 +297,10 @@ describe CommitsController, &quot;routing&quot; do
301 let(:actions) { [:show] } 297 let(:actions) { [:show] }
302 let(:controller) { 'commits' } 298 let(:controller) { 'commits' }
303 end 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 end 304 end
305 305
306 # project_team_members GET /:project_id/team_members(.:format) team_members#index 306 # project_team_members GET /:project_id/team_members(.:format) team_members#index
@@ -385,6 +385,7 @@ end @@ -385,6 +385,7 @@ end
385 describe BlameController, "routing" do 385 describe BlameController, "routing" do
386 it "to #show" do 386 it "to #show" do
387 get("/gitlabhq/blame/master/app/models/project.rb").should route_to('blame#show', project_id: 'gitlabhq', id: 'master/app/models/project.rb') 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 end 389 end
389 end 390 end
390 391
@@ -393,6 +394,7 @@ describe BlobController, &quot;routing&quot; do @@ -393,6 +394,7 @@ describe BlobController, &quot;routing&quot; do
393 it "to #show" do 394 it "to #show" do
394 get("/gitlabhq/blob/master/app/models/project.rb").should route_to('blob#show', project_id: 'gitlabhq', id: 'master/app/models/project.rb') 395 get("/gitlabhq/blob/master/app/models/project.rb").should route_to('blob#show', project_id: 'gitlabhq', id: 'master/app/models/project.rb')
395 get("/gitlabhq/blob/master/app/models/compare.rb").should route_to('blob#show', project_id: 'gitlabhq', id: 'master/app/models/compare.rb') 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 end 398 end
397 end 399 end
398 400
@@ -400,6 +402,7 @@ end @@ -400,6 +402,7 @@ end
400 describe TreeController, "routing" do 402 describe TreeController, "routing" do
401 it "to #show" do 403 it "to #show" do
402 get("/gitlabhq/tree/master/app/models/project.rb").should route_to('tree#show', project_id: 'gitlabhq', id: 'master/app/models/project.rb') 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 end 406 end
404 end 407 end
405 408
@@ -420,3 +423,10 @@ describe CompareController, &quot;routing&quot; do @@ -420,3 +423,10 @@ describe CompareController, &quot;routing&quot; do
420 get("/gitlabhq/compare/issue/1234...stable").should route_to('compare#show', project_id: 'gitlabhq', from: 'issue/1234', to: 'stable') 423 get("/gitlabhq/compare/issue/1234...stable").should route_to('compare#show', project_id: 'gitlabhq', from: 'issue/1234', to: 'stable')
421 end 424 end
422 end 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,6 +43,11 @@ class GitLabTestRepo &lt; Repository
43 def repo 43 def repo
44 @repo ||= Grit::Repo.new(Rails.root.join('tmp', 'repositories', 'gitlabhq')) 44 @repo ||= Grit::Repo.new(Rails.root.join('tmp', 'repositories', 'gitlabhq'))
45 end 45 end
  46 +
  47 + # patch repo size (in mb)
  48 + def size
  49 + 12.45
  50 + end
46 end 51 end
47 52
48 module Gitlab 53 module Gitlab