Commit caf065007a49a58f8005c0a8d2cb0140719af8d2

Authored by Dmitriy Zaporozhets
2 parents 4c903035 749d3383

Merge branch 'feature/improve_deploy_keys' of /home/git/repositories/gitlab/gitlabhq

app/controllers/deploy_keys_controller.rb
... ... @@ -5,7 +5,8 @@ class DeployKeysController < ProjectResourceController
5 5 before_filter :authorize_admin_project!
6 6  
7 7 def index
8   - @keys = @project.deploy_keys.all
  8 + @enabled_keys = @project.deploy_keys.all
  9 + @available_keys = available_keys - @enabled_keys
9 10 end
10 11  
11 12 def show
... ... @@ -19,8 +20,9 @@ class DeployKeysController < ProjectResourceController
19 20 end
20 21  
21 22 def create
22   - @key = @project.deploy_keys.new(params[:key])
23   - if @key.save
  23 + @key = DeployKey.new(params[:deploy_key])
  24 +
  25 + if @key.valid? && @project.deploy_keys << @key
24 26 redirect_to project_deploy_keys_path(@project)
25 27 else
26 28 render "new"
... ... @@ -36,4 +38,22 @@ class DeployKeysController &lt; ProjectResourceController
36 38 format.js { render nothing: true }
37 39 end
38 40 end
  41 +
  42 + def enable
  43 + project.deploy_keys << available_keys.find(params[:id])
  44 +
  45 + redirect_to project_deploy_keys_path(@project)
  46 + end
  47 +
  48 + def disable
  49 + @project.deploy_keys_projects.where(deploy_key_id: params[:id]).last.destroy
  50 +
  51 + redirect_to project_deploy_keys_path(@project)
  52 + end
  53 +
  54 + protected
  55 +
  56 + def available_keys
  57 + @available_keys ||= DeployKey.in_projects(current_user.owned_projects)
  58 + end
39 59 end
... ...
app/models/deploy_key.rb 0 → 100644
... ... @@ -0,0 +1,6 @@
  1 +class DeployKey < Key
  2 + has_many :deploy_keys_projects, dependent: :destroy
  3 + has_many :projects, through: :deploy_keys_projects
  4 +
  5 + scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) }
  6 +end
... ...
app/models/deploy_keys_project.rb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +class DeployKeysProject < ActiveRecord::Base
  2 + attr_accessible :key_id, :project_id
  3 +
  4 + belongs_to :project
  5 + belongs_to :deploy_key
  6 +
  7 + validates :deploy_key_id, presence: true
  8 + validates :deploy_key_id, uniqueness: { scope: [:project_id], message: "already exists in project" }
  9 +
  10 + validates :project_id, presence: true
  11 +end
... ...
app/models/key.rb
... ... @@ -16,7 +16,6 @@ require &#39;digest/md5&#39;
16 16  
17 17 class Key < ActiveRecord::Base
18 18 belongs_to :user
19   - belongs_to :project
20 19  
21 20 attr_accessible :key, :title
22 21  
... ... @@ -29,7 +28,7 @@ class Key &lt; ActiveRecord::Base
29 28 delegate :name, :email, to: :user, prefix: true
30 29  
31 30 def strip_white_space
32   - self.key = self.key.strip unless self.key.blank?
  31 + self.key = key.strip unless key.blank?
33 32 end
34 33  
35 34 def fingerprintable_key
... ... @@ -47,20 +46,12 @@ class Key &lt; ActiveRecord::Base
47 46 errors.add(:key, "can't be fingerprinted") if $?.exitstatus != 0
48 47 end
49 48  
50   - def is_deploy_key
51   - project.present?
52   - end
53   -
54 49 # projects that has this key
55 50 def projects
56   - if is_deploy_key
57   - [project]
58   - else
59   - user.authorized_projects
60   - end
  51 + user.authorized_projects
61 52 end
62 53  
63 54 def shell_id
64   - "key-#{self.id}"
  55 + "key-#{id}"
65 56 end
66 57 end
... ...
app/models/project.rb
... ... @@ -55,7 +55,6 @@ class Project &lt; ActiveRecord::Base
55 55 has_many :users_projects, dependent: :destroy
56 56 has_many :notes, dependent: :destroy
57 57 has_many :snippets, dependent: :destroy
58   - has_many :deploy_keys, dependent: :destroy, class_name: "Key", foreign_key: "project_id"
59 58 has_many :hooks, dependent: :destroy, class_name: "ProjectHook"
60 59 has_many :protected_branches, dependent: :destroy
61 60 has_many :user_team_project_relationships, dependent: :destroy
... ... @@ -65,6 +64,9 @@ class Project &lt; ActiveRecord::Base
65 64 has_many :user_team_user_relationships, through: :user_teams
66 65 has_many :user_teams_members, through: :user_team_user_relationships
67 66  
  67 + has_many :deploy_keys_projects, dependent: :destroy
  68 + has_many :deploy_keys, through: :deploy_keys_projects
  69 +
68 70 delegate :name, to: :owner, allow_nil: true, prefix: true
69 71  
70 72 # Validations
... ...
app/models/user.rb
... ... @@ -89,7 +89,7 @@ class User &lt; ActiveRecord::Base
89 89  
90 90 has_many :personal_projects, through: :namespace, source: :projects
91 91 has_many :projects, through: :users_projects
92   - has_many :own_projects, foreign_key: :creator_id
  92 + has_many :own_projects, foreign_key: :creator_id, class_name: 'Project'
93 93 has_many :owned_projects, through: :namespaces, source: :projects
94 94  
95 95 #
... ...
app/views/deploy_keys/_deploy_key.html.haml 0 → 100644
... ... @@ -0,0 +1,25 @@
  1 +%li
  2 + .pull-right
  3 + - if @available_keys.include?(deploy_key)
  4 + = link_to enable_project_deploy_key_path(@project, deploy_key), class: 'btn btn-small', method: :put do
  5 + %i.icon-plus
  6 + Enable
  7 + - else
  8 + - if deploy_key.projects.count > 1
  9 + = link_to disable_project_deploy_key_path(@project, deploy_key), class: 'btn btn-small', method: :put do
  10 + %i.icon-off
  11 + Disable
  12 + - else
  13 + = link_to 'Remove', project_deploy_key_path(@project, deploy_key), confirm: 'Are you sure?', method: :delete, class: "btn btn-remove delete-key btn-small pull-right"
  14 +
  15 +
  16 + = link_to project_deploy_key_path(@project, deploy_key) do
  17 + %i.icon-key
  18 + %strong= deploy_key.title
  19 +
  20 + %p.light.prepend-top-10
  21 + - deploy_key.projects.map(&:name_with_namespace).each do |project_name|
  22 + %span.label= project_name
  23 + %small.pull-right
  24 + Created #{time_ago_in_words(deploy_key.created_at)} ago
  25 +
... ...
app/views/deploy_keys/_form.html.haml
... ... @@ -18,6 +18,6 @@
18 18 = link_to "here", help_ssh_path
19 19  
20 20 .actions
21   - = f.submit 'Save', class: "btn-save btn"
  21 + = f.submit 'Create', class: "btn-create btn"
22 22 = link_to "Cancel", project_deploy_keys_path(@project), class: "btn btn-cancel"
23 23  
... ...
app/views/deploy_keys/_show.html.haml
... ... @@ -1,12 +0,0 @@
1   -%tr
2   - %td
3   - %a{href: project_deploy_key_path(key.project, key)}
4   - %strong= key.title
5   - %td
6   - %span.update-author
7   - Added
8   - = time_ago_in_words(key.created_at)
9   - ago
10   - %td
11   - = link_to 'Remove', project_deploy_key_path(key.project, key), confirm: 'Are you sure?', method: :delete, class: "btn btn-remove delete-key btn-small pull-right"
12   -
app/views/deploy_keys/index.html.haml
1 1 = render "projects/settings_nav"
2 2  
3 3 %p.slead
4   - Deploy keys allow read-only access to repository. They can be used for CI, staging or production servers. A deploy key can be added to only one project. If you need to add the same key to multiple projects you can create a deploy user and add that user to multiple projects.
  4 + Deploy keys allow read-only access to repository. They can be used for CI, staging or production servers
5 5  
6   - - if can? current_user, :admin_project, @project
7   - = link_to new_project_deploy_key_path(@project), class: "btn btn-small", title: "New Deploy Key" do
8   - Add Deploy Key
9   -- if @keys.any?
10   - %table
11   - %thead
12   - %tr
13   - %th Keys
14   - %th
15   - %th
16   - - @keys.each do |key|
17   - = render(partial: 'show', locals: {key: key})
  6 +%p
  7 + You can create a deploy key or add existing one
  8 + = link_to new_project_deploy_key_path(@project), class: "btn btn-primary pull-right", title: "New Deploy Key" do
  9 + %i.icon-plus
  10 + New Deploy Key
  11 +
  12 +%hr.clearfix
  13 +
  14 +.row
  15 + .span6.enabled-keys
  16 + %h5.cgreen
  17 + Enabled deploy keys
  18 + %small for this project
  19 + %ul.bordered-list
  20 + = render @enabled_keys
  21 + .span6.available-keys
  22 + %h5
  23 + Available deploy keys
  24 + %small from projects you are able to manage
  25 + %ul.bordered-list
  26 + = render @available_keys
... ...
app/views/deploy_keys/show.html.haml
... ... @@ -12,4 +12,4 @@
12 12 %hr
13 13 %pre= @key.key
14 14 .pull-right
15   - = link_to 'Remove', project_deploy_key_path(@key.project, @key), confirm: 'Are you sure?', method: :delete, class: "btn-remove btn delete-key"
  15 + = link_to 'Remove', project_deploy_key_path(@project, @key), confirm: 'Are you sure?', method: :delete, class: "btn-remove btn delete-key"
... ...
app/views/refs/logs_tree.js.haml
1   -- @logs.each do |content_data|
  1 +- @logs.each do |content_data|
2 2 - file_name = content_data[:file_name]
3 3 - commit = content_data[:commit]
4 4  
... ...
config/routes.rb
... ... @@ -215,7 +215,13 @@ Gitlab::Application.routes.draw do
215 215 end
216 216 end
217 217  
218   - resources :deploy_keys
  218 + resources :deploy_keys do
  219 + member do
  220 + put :enable
  221 + put :disable
  222 + end
  223 + end
  224 +
219 225 resources :protected_branches, only: [:index, :create, :destroy]
220 226  
221 227 resources :refs, only: [] do
... ...
db/migrate/20130506085413_add_type_to_key.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class AddTypeToKey < ActiveRecord::Migration
  2 + def change
  3 + add_column :keys, :type, :string
  4 + end
  5 +end
... ...
db/migrate/20130506090604_create_deploy_keys_projects.rb 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +class CreateDeployKeysProjects < ActiveRecord::Migration
  2 + def change
  3 + create_table :deploy_keys_projects do |t|
  4 + t.integer :deploy_key_id, null: false
  5 + t.integer :project_id, null: false
  6 +
  7 + t.timestamps
  8 + end
  9 + end
  10 +end
... ...
db/migrate/20130506095501_remove_project_id_from_key.rb 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +class RemoveProjectIdFromKey < ActiveRecord::Migration
  2 + def up
  3 + puts 'Migrate deploy keys: '
  4 + Key.where('project_id IS NOT NULL').update_all(type: 'DeployKey')
  5 +
  6 + DeployKey.all.each do |key|
  7 + project = Project.find_by_id(key.project_id)
  8 + if project
  9 + project.deploy_keys << key
  10 + print '.'
  11 + end
  12 + end
  13 +
  14 + puts 'Done'
  15 +
  16 + remove_column :keys, :project_id
  17 + end
  18 +
  19 + def down
  20 + add_column :keys, :project_id, :integer
  21 + end
  22 +end
... ...
db/schema.rb
... ... @@ -11,7 +11,14 @@
11 11 #
12 12 # It's strongly recommended to check this file into your version control system.
13 13  
14   -ActiveRecord::Schema.define(:version => 20130410175022) do
  14 +ActiveRecord::Schema.define(:version => 20130506095501) do
  15 +
  16 + create_table "deploy_keys_projects", :force => true do |t|
  17 + t.integer "deploy_key_id", :null => false
  18 + t.integer "project_id", :null => false
  19 + t.datetime "created_at", :null => false
  20 + t.datetime "updated_at", :null => false
  21 + end
15 22  
16 23 create_table "events", :force => true do |t|
17 24 t.string "target_type"
... ... @@ -46,8 +53,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130410175022) do
46 53 t.integer "assignee_id"
47 54 t.integer "author_id"
48 55 t.integer "project_id"
49   - t.datetime "created_at"
50   - t.datetime "updated_at"
  56 + t.datetime "created_at", :null => false
  57 + t.datetime "updated_at", :null => false
51 58 t.integer "position", :default => 0
52 59 t.string "branch_name"
53 60 t.text "description"
... ... @@ -64,16 +71,15 @@ ActiveRecord::Schema.define(:version =&gt; 20130410175022) do
64 71  
65 72 create_table "keys", :force => true do |t|
66 73 t.integer "user_id"
67   - t.datetime "created_at"
68   - t.datetime "updated_at"
  74 + t.datetime "created_at", :null => false
  75 + t.datetime "updated_at", :null => false
69 76 t.text "key"
70 77 t.string "title"
71 78 t.string "identifier"
72   - t.integer "project_id"
  79 + t.string "type"
73 80 end
74 81  
75 82 add_index "keys", ["identifier"], :name => "index_keys_on_identifier"
76   - add_index "keys", ["project_id"], :name => "index_keys_on_project_id"
77 83 add_index "keys", ["user_id"], :name => "index_keys_on_user_id"
78 84  
79 85 create_table "merge_requests", :force => true do |t|
... ... @@ -83,8 +89,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130410175022) do
83 89 t.integer "author_id"
84 90 t.integer "assignee_id"
85 91 t.string "title"
86   - t.datetime "created_at"
87   - t.datetime "updated_at"
  92 + t.datetime "created_at", :null => false
  93 + t.datetime "updated_at", :null => false
88 94 t.text "st_commits", :limit => 2147483647
89 95 t.text "st_diffs", :limit => 2147483647
90 96 t.integer "milestone_id"
... ... @@ -133,8 +139,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130410175022) do
133 139 t.text "note"
134 140 t.string "noteable_type"
135 141 t.integer "author_id"
136   - t.datetime "created_at"
137   - t.datetime "updated_at"
  142 + t.datetime "created_at", :null => false
  143 + t.datetime "updated_at", :null => false
138 144 t.integer "project_id"
139 145 t.string "attachment"
140 146 t.string "line_code"
... ... @@ -152,8 +158,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130410175022) do
152 158 t.string "name"
153 159 t.string "path"
154 160 t.text "description"
155   - t.datetime "created_at"
156   - t.datetime "updated_at"
  161 + t.datetime "created_at", :null => false
  162 + t.datetime "updated_at", :null => false
157 163 t.integer "creator_id"
158 164 t.string "default_branch"
159 165 t.boolean "issues_enabled", :default => true, :null => false
... ... @@ -197,8 +203,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130410175022) do
197 203 t.text "content"
198 204 t.integer "author_id", :null => false
199 205 t.integer "project_id", :null => false
200   - t.datetime "created_at"
201   - t.datetime "updated_at"
  206 + t.datetime "created_at", :null => false
  207 + t.datetime "updated_at", :null => false
202 208 t.string "file_name"
203 209 t.datetime "expires_at"
204 210 end
... ... @@ -217,6 +223,9 @@ ActiveRecord::Schema.define(:version =&gt; 20130410175022) do
217 223 t.datetime "created_at"
218 224 end
219 225  
  226 + add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id"
  227 + add_index "taggings", ["taggable_id", "taggable_type", "context"], :name => "index_taggings_on_taggable_id_and_taggable_type_and_context"
  228 +
220 229 create_table "tags", :force => true do |t|
221 230 t.string "name"
222 231 end
... ... @@ -248,41 +257,42 @@ ActiveRecord::Schema.define(:version =&gt; 20130410175022) do
248 257 end
249 258  
250 259 create_table "users", :force => true do |t|
251   - t.string "email", :default => "", :null => false
252   - t.string "encrypted_password", :limit => 128, :default => "", :null => false
  260 + t.string "email", :default => "", :null => false
  261 + t.string "encrypted_password", :default => "", :null => false
253 262 t.string "reset_password_token"
254 263 t.datetime "reset_password_sent_at"
255 264 t.datetime "remember_created_at"
256   - t.integer "sign_in_count", :default => 0
  265 + t.integer "sign_in_count", :default => 0
257 266 t.datetime "current_sign_in_at"
258 267 t.datetime "last_sign_in_at"
259 268 t.string "current_sign_in_ip"
260 269 t.string "last_sign_in_ip"
261   - t.datetime "created_at"
262   - t.datetime "updated_at"
  270 + t.datetime "created_at", :null => false
  271 + t.datetime "updated_at", :null => false
263 272 t.string "name"
264   - t.boolean "admin", :default => false, :null => false
265   - t.integer "projects_limit", :default => 10
266   - t.string "skype", :default => "", :null => false
267   - t.string "linkedin", :default => "", :null => false
268   - t.string "twitter", :default => "", :null => false
  273 + t.boolean "admin", :default => false, :null => false
  274 + t.integer "projects_limit", :default => 10
  275 + t.string "skype", :default => "", :null => false
  276 + t.string "linkedin", :default => "", :null => false
  277 + t.string "twitter", :default => "", :null => false
269 278 t.string "authentication_token"
270   - t.integer "theme_id", :default => 1, :null => false
  279 + t.integer "theme_id", :default => 1, :null => false
271 280 t.string "bio"
272   - t.integer "failed_attempts", :default => 0
  281 + t.integer "failed_attempts", :default => 0
273 282 t.datetime "locked_at"
274 283 t.string "extern_uid"
275 284 t.string "provider"
276 285 t.string "username"
277   - t.boolean "can_create_group", :default => true, :null => false
278   - t.boolean "can_create_team", :default => true, :null => false
  286 + t.boolean "can_create_group", :default => true, :null => false
  287 + t.boolean "can_create_team", :default => true, :null => false
279 288 t.string "state"
280   - t.integer "color_scheme_id", :default => 1, :null => false
281   - t.integer "notification_level", :default => 1, :null => false
  289 + t.integer "color_scheme_id", :default => 1, :null => false
  290 + t.integer "notification_level", :default => 1, :null => false
282 291 end
283 292  
284 293 add_index "users", ["admin"], :name => "index_users_on_admin"
285 294 add_index "users", ["email"], :name => "index_users_on_email", :unique => true
  295 + add_index "users", ["extern_uid", "provider"], :name => "index_users_on_extern_uid_and_provider", :unique => true
286 296 add_index "users", ["name"], :name => "index_users_on_name"
287 297 add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true
288 298 add_index "users", ["username"], :name => "index_users_on_username"
... ... @@ -290,8 +300,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130410175022) do
290 300 create_table "users_projects", :force => true do |t|
291 301 t.integer "user_id", :null => false
292 302 t.integer "project_id", :null => false
293   - t.datetime "created_at"
294   - t.datetime "updated_at"
  303 + t.datetime "created_at", :null => false
  304 + t.datetime "updated_at", :null => false
295 305 t.integer "project_access", :default => 0, :null => false
296 306 t.integer "notification_level", :default => 3, :null => false
297 307 end
... ... @@ -303,8 +313,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130410175022) do
303 313 create_table "web_hooks", :force => true do |t|
304 314 t.string "url"
305 315 t.integer "project_id"
306   - t.datetime "created_at"
307   - t.datetime "updated_at"
  316 + t.datetime "created_at", :null => false
  317 + t.datetime "updated_at", :null => false
308 318 t.string "type", :default => "ProjectHook"
309 319 t.integer "service_id"
310 320 end
... ...
features/project/deploy_keys.feature 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +Feature: Project Deploy Keys
  2 + Background:
  3 + Given I sign in as a user
  4 + And I own project "Shop"
  5 +
  6 + Scenario: I should see deploy keys list
  7 + Given project has deploy key
  8 + When I visit project deploy keys page
  9 + Then I should see project deploy keys
  10 +
  11 + Scenario: I add new deploy key
  12 + Given I visit project deploy keys page
  13 + When I click 'New Deploy Key'
  14 + And I submit new deploy key
  15 + Then I should be on deploy keys page
  16 + And I should see newly created deploy key
  17 +
  18 + Scenario: I attach deploy key to project
  19 + Given other project has deploy key
  20 + And I visit project deploy keys page
  21 + When I click attach deploy key
  22 + Then I should be on deploy keys page
  23 + And I should see newly created deploy key
... ...
features/steps/project/deploy_keys.rb 0 → 100644
... ... @@ -0,0 +1,52 @@
  1 +class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
  2 + include SharedAuthentication
  3 + include SharedProject
  4 + include SharedPaths
  5 +
  6 + step 'project has deploy key' do
  7 + create(:deploy_keys_project, project: @project)
  8 + end
  9 +
  10 + step 'I should see project deploy keys' do
  11 + within '.enabled-keys' do
  12 + page.should have_content deploy_key.title
  13 + end
  14 + end
  15 +
  16 + step 'I click \'New Deploy Key\'' do
  17 + click_link 'New Deploy Key'
  18 + end
  19 +
  20 + step 'I submit new deploy key' do
  21 + fill_in "deploy_key_title", with: "laptop"
  22 + fill_in "deploy_key_key", with: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop"
  23 + click_button "Create"
  24 + end
  25 +
  26 + step 'I should be on deploy keys page' do
  27 + current_path.should == project_deploy_keys_path(@project)
  28 + end
  29 +
  30 + step 'I should see newly created deploy key' do
  31 + within '.enabled-keys' do
  32 + page.should have_content(deploy_key.title)
  33 + end
  34 + end
  35 +
  36 + step 'other project has deploy key' do
  37 + @second_project = create :project, namespace: current_user.namespace
  38 + create(:deploy_keys_project, project: @second_project)
  39 + end
  40 +
  41 + step 'I click attach deploy key' do
  42 + within '.available-keys' do
  43 + click_link 'Enable'
  44 + end
  45 + end
  46 +
  47 + protected
  48 +
  49 + def deploy_key
  50 + @project.deploy_keys.last
  51 + end
  52 +end
... ...
features/steps/shared/paths.rb
1 1 module SharedPaths
2 2 include Spinach::DSL
3 3  
4   - When 'I visit new project page' do
  4 + step 'I visit new project page' do
5 5 visit new_project_path
6 6 end
7 7  
... ... @@ -9,23 +9,23 @@ module SharedPaths
9 9 # Group
10 10 # ----------------------------------------
11 11  
12   - When 'I visit group page' do
  12 + step 'I visit group page' do
13 13 visit group_path(current_group)
14 14 end
15 15  
16   - When 'I visit group issues page' do
  16 + step 'I visit group issues page' do
17 17 visit issues_group_path(current_group)
18 18 end
19 19  
20   - When 'I visit group merge requests page' do
  20 + step 'I visit group merge requests page' do
21 21 visit merge_requests_group_path(current_group)
22 22 end
23 23  
24   - When 'I visit group people page' do
  24 + step 'I visit group people page' do
25 25 visit people_group_path(current_group)
26 26 end
27 27  
28   - When 'I visit group settings page' do
  28 + step 'I visit group settings page' do
29 29 visit edit_group_path(current_group)
30 30 end
31 31  
... ... @@ -33,27 +33,27 @@ module SharedPaths
33 33 # Dashboard
34 34 # ----------------------------------------
35 35  
36   - Given 'I visit dashboard page' do
  36 + step 'I visit dashboard page' do
37 37 visit dashboard_path
38 38 end
39 39  
40   - Given 'I visit dashboard projects page' do
  40 + step 'I visit dashboard projects page' do
41 41 visit projects_dashboard_path
42 42 end
43 43  
44   - Given 'I visit dashboard issues page' do
  44 + step 'I visit dashboard issues page' do
45 45 visit issues_dashboard_path
46 46 end
47 47  
48   - Given 'I visit dashboard merge requests page' do
  48 + step 'I visit dashboard merge requests page' do
49 49 visit merge_requests_dashboard_path
50 50 end
51 51  
52   - Given 'I visit dashboard search page' do
  52 + step 'I visit dashboard search page' do
53 53 visit search_path
54 54 end
55 55  
56   - Given 'I visit dashboard help page' do
  56 + step 'I visit dashboard help page' do
57 57 visit help_path
58 58 end
59 59  
... ... @@ -61,23 +61,23 @@ module SharedPaths
61 61 # Profile
62 62 # ----------------------------------------
63 63  
64   - Given 'I visit profile page' do
  64 + step 'I visit profile page' do
65 65 visit profile_path
66 66 end
67 67  
68   - Given 'I visit profile account page' do
  68 + step 'I visit profile account page' do
69 69 visit account_profile_path
70 70 end
71 71  
72   - Given 'I visit profile SSH keys page' do
  72 + step 'I visit profile SSH keys page' do
73 73 visit keys_path
74 74 end
75 75  
76   - Given 'I visit profile design page' do
  76 + step 'I visit profile design page' do
77 77 visit design_profile_path
78 78 end
79 79  
80   - Given 'I visit profile history page' do
  80 + step 'I visit profile history page' do
81 81 visit history_profile_path
82 82 end
83 83  
... ... @@ -85,35 +85,35 @@ module SharedPaths
85 85 # Admin
86 86 # ----------------------------------------
87 87  
88   - Given 'I visit admin page' do
  88 + step 'I visit admin page' do
89 89 visit admin_root_path
90 90 end
91 91  
92   - Given 'I visit admin projects page' do
  92 + step 'I visit admin projects page' do
93 93 visit admin_projects_path
94 94 end
95 95  
96   - Given 'I visit admin users page' do
  96 + step 'I visit admin users page' do
97 97 visit admin_users_path
98 98 end
99 99  
100   - Given 'I visit admin logs page' do
  100 + step 'I visit admin logs page' do
101 101 visit admin_logs_path
102 102 end
103 103  
104   - Given 'I visit admin hooks page' do
  104 + step 'I visit admin hooks page' do
105 105 visit admin_hooks_path
106 106 end
107 107  
108   - Given 'I visit admin Resque page' do
  108 + step 'I visit admin Resque page' do
109 109 visit admin_resque_path
110 110 end
111 111  
112   - And 'I visit admin groups page' do
  112 + step 'I visit admin groups page' do
113 113 visit admin_groups_path
114 114 end
115 115  
116   - When 'I visit admin teams page' do
  116 + step 'I visit admin teams page' do
117 117 visit admin_teams_path
118 118 end
119 119  
... ... @@ -121,145 +121,149 @@ module SharedPaths
121 121 # Generic Project
122 122 # ----------------------------------------
123 123  
124   - Given "I visit my project's home page" do
  124 + step "I visit my project's home page" do
125 125 visit project_path(@project)
126 126 end
127 127  
128   - Given "I visit my project's settings page" do
  128 + step "I visit my project's settings page" do
129 129 visit edit_project_path(@project)
130 130 end
131 131  
132   - Given "I visit my project's files page" do
  132 + step "I visit my project's files page" do
133 133 visit project_tree_path(@project, root_ref)
134 134 end
135 135  
136   - Given "I visit my project's commits page" do
  136 + step "I visit my project's commits page" do
137 137 visit project_commits_path(@project, root_ref, {limit: 5})
138 138 end
139 139  
140   - Given "I visit my project's commits page for a specific path" do
  140 + step "I visit my project's commits page for a specific path" do
141 141 visit project_commits_path(@project, root_ref + "/app/models/project.rb", {limit: 5})
142 142 end
143 143  
144   - Given 'I visit my projects commits stats page' do
  144 + step 'I visit my projects commits stats page' do
145 145 visit stats_project_repository_path(@project)
146 146 end
147 147  
148   - Given "I visit my project's network page" do
  148 + step "I visit my project's network page" do
149 149 # Stub Graph max_size to speed up test (10 commits vs. 650)
150 150 Network::Graph.stub(max_count: 10)
151 151  
152 152 visit project_graph_path(@project, root_ref)
153 153 end
154 154  
155   - Given "I visit my project's issues page" do
  155 + step "I visit my project's issues page" do
156 156 visit project_issues_path(@project)
157 157 end
158 158  
159   - Given "I visit my project's merge requests page" do
  159 + step "I visit my project's merge requests page" do
160 160 visit project_merge_requests_path(@project)
161 161 end
162 162  
163   - Given "I visit my project's wall page" do
  163 + step "I visit my project's wall page" do
164 164 visit project_wall_path(@project)
165 165 end
166 166  
167   - Given "I visit my project's wiki page" do
  167 + step "I visit my project's wiki page" do
168 168 visit project_wiki_path(@project, :home)
169 169 end
170 170  
171   - When 'I visit project hooks page' do
  171 + step 'I visit project hooks page' do
172 172 visit project_hooks_path(@project)
173 173 end
174 174  
  175 + step 'I visit project deploy keys page' do
  176 + visit project_deploy_keys_path(@project)
  177 + end
  178 +
175 179 # ----------------------------------------
176 180 # "Shop" Project
177 181 # ----------------------------------------
178 182  
179   - And 'I visit project "Shop" page' do
  183 + step 'I visit project "Shop" page' do
180 184 visit project_path(project)
181 185 end
182 186  
183   - When 'I visit edit project "Shop" page' do
  187 + step 'I visit edit project "Shop" page' do
184 188 visit edit_project_path(project)
185 189 end
186 190  
187   - Given 'I visit project branches page' do
  191 + step 'I visit project branches page' do
188 192 visit branches_project_repository_path(@project)
189 193 end
190 194  
191   - Given 'I visit compare refs page' do
  195 + step 'I visit compare refs page' do
192 196 visit project_compare_index_path(@project)
193 197 end
194 198  
195   - Given 'I visit project commits page' do
  199 + step 'I visit project commits page' do
196 200 visit project_commits_path(@project, root_ref, {limit: 5})
197 201 end
198 202  
199   - Given 'I visit project commits page for stable branch' do
  203 + step 'I visit project commits page for stable branch' do
200 204 visit project_commits_path(@project, 'stable', {limit: 5})
201 205 end
202 206  
203   - Given 'I visit project source page' do
  207 + step 'I visit project source page' do
204 208 visit project_tree_path(@project, root_ref)
205 209 end
206 210  
207   - Given 'I visit blob file from repo' do
  211 + step 'I visit blob file from repo' do
208 212 visit project_blob_path(@project, File.join(ValidCommit::ID, ValidCommit::BLOB_FILE_PATH))
209 213 end
210 214  
211   - Given 'I visit project source page for "8470d70"' do
  215 + step 'I visit project source page for "8470d70"' do
212 216 visit project_tree_path(@project, "8470d70")
213 217 end
214 218  
215   - Given 'I visit project tags page' do
  219 + step 'I visit project tags page' do
216 220 visit tags_project_repository_path(@project)
217 221 end
218 222  
219   - Given 'I visit project commit page' do
  223 + step 'I visit project commit page' do
220 224 visit project_commit_path(@project, ValidCommit::ID)
221 225 end
222 226  
223   - And 'I visit project "Shop" issues page' do
  227 + step 'I visit project "Shop" issues page' do
224 228 visit project_issues_path(project)
225 229 end
226 230  
227   - Given 'I visit issue page "Release 0.4"' do
  231 + step 'I visit issue page "Release 0.4"' do
228 232 issue = Issue.find_by_title("Release 0.4")
229 233 visit project_issue_path(issue.project, issue)
230 234 end
231 235  
232   - Given 'I visit project "Shop" labels page' do
  236 + step 'I visit project "Shop" labels page' do
233 237 visit project_labels_path(project)
234 238 end
235 239  
236   - Given 'I visit merge request page "Bug NS-04"' do
  240 + step 'I visit merge request page "Bug NS-04"' do
237 241 mr = MergeRequest.find_by_title("Bug NS-04")
238 242 visit project_merge_request_path(mr.project, mr)
239 243 end
240 244  
241   - Given 'I visit merge request page "Bug NS-05"' do
  245 + step 'I visit merge request page "Bug NS-05"' do
242 246 mr = MergeRequest.find_by_title("Bug NS-05")
243 247 visit project_merge_request_path(mr.project, mr)
244 248 end
245 249  
246   - And 'I visit project "Shop" merge requests page' do
  250 + step 'I visit project "Shop" merge requests page' do
247 251 visit project_merge_requests_path(project)
248 252 end
249 253  
250   - Given 'I visit project "Shop" milestones page' do
  254 + step 'I visit project "Shop" milestones page' do
251 255 visit project_milestones_path(project)
252 256 end
253 257  
254   - Then 'I visit project "Shop" team page' do
  258 + step 'I visit project "Shop" team page' do
255 259 visit project_team_index_path(project)
256 260 end
257 261  
258   - Then 'I visit project "Shop" wall page' do
  262 + step 'I visit project "Shop" wall page' do
259 263 visit project_wall_path(project)
260 264 end
261 265  
262   - Given 'I visit project wiki page' do
  266 + step 'I visit project wiki page' do
263 267 visit project_wiki_path(@project, :home)
264 268 end
265 269  
... ... @@ -267,7 +271,7 @@ module SharedPaths
267 271 # Public Projects
268 272 # ----------------------------------------
269 273  
270   - Given 'I visit the public projects area' do
  274 + step 'I visit the public projects area' do
271 275 visit public_root_path
272 276 end
273 277  
... ...
lib/api/internal.rb
... ... @@ -25,8 +25,8 @@ module Gitlab
25 25 return false unless project
26 26  
27 27  
28   - if key.is_deploy_key
29   - project == key.project && git_cmd == 'git-upload-pack'
  28 + if key.is_a? DeployKey
  29 + key.projects.include?(project) && git_cmd == 'git-upload-pack'
30 30 else
31 31 user = key.user
32 32  
... ...
lib/api/projects.rb
... ... @@ -531,8 +531,8 @@ module Gitlab
531 531 # POST /projects/:id/keys
532 532 post ":id/keys" do
533 533 attrs = attributes_for_keys [:title, :key]
534   - key = user_project.deploy_keys.new attrs
535   - if key.save
  534 + key = DeployKey.new attrs
  535 + if key.valid? && user_project.deploy_keys << key
536 536 present key, with: Entities::SSHKey
537 537 else
538 538 not_found!
... ... @@ -545,9 +545,8 @@ module Gitlab
545 545 # DELETE /projects/:id/keys/:id
546 546 delete ":id/keys/:key_id" do
547 547 key = user_project.deploy_keys.find params[:key_id]
548   - key.delete
  548 + key.destroy
549 549 end
550   -
551 550 end
552 551 end
553 552 end
... ...
lib/api/users.rb
... ... @@ -174,7 +174,7 @@ module Gitlab
174 174 delete "keys/:id" do
175 175 begin
176 176 key = current_user.keys.find params[:id]
177   - key.delete
  177 + key.destroy
178 178 rescue
179 179 end
180 180 end
... ...
lib/tasks/gitlab/check.rake
... ... @@ -24,6 +24,7 @@ namespace :gitlab do
24 24 check_init_script_up_to_date
25 25 check_satellites_exist
26 26 check_redis_version
  27 + check_git_version
27 28  
28 29 finished_checking "GitLab"
29 30 end
... ... @@ -663,4 +664,18 @@ namespace :gitlab do
663 664 puts "FAIL. Please update gitlab-shell to v#{required_version}".red
664 665 end
665 666 end
  667 +
  668 + def check_git_version
  669 + print "Git version >= 1.7.10 ? ... "
  670 +
  671 + if run_and_match("git --version", /git version 1.7.10.\d/)
  672 + puts "yes".green
  673 + else
  674 + puts "no".red
  675 + try_fixing_it(
  676 + "Update your git to a version >= 1.7.10"
  677 + )
  678 + fix_and_rerun
  679 + end
  680 + end
666 681 end
... ...
spec/factories.rb
... ... @@ -158,8 +158,7 @@ FactoryGirl.define do
158 158 "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
159 159 end
160 160  
161   - factory :deploy_key do
162   - project
  161 + factory :deploy_key, class: 'DeployKey' do
163 162 end
164 163  
165 164 factory :personal_key do
... ... @@ -222,4 +221,9 @@ FactoryGirl.define do
222 221 url
223 222 service
224 223 end
  224 +
  225 + factory :deploy_keys_project do
  226 + deploy_key
  227 + project
  228 + end
225 229 end
... ...
spec/features/projects_deploy_keys_spec.rb
... ... @@ -1,67 +0,0 @@
1   -require 'spec_helper'
2   -
3   -describe "Projects", "DeployKeys" do
4   - let(:project) { create(:project) }
5   -
6   - before do
7   - login_as :user
8   - project.team << [@user, :master]
9   - end
10   -
11   - describe "GET /keys" do
12   - before do
13   - @key = create(:key, project: project)
14   - visit project_deploy_keys_path(project)
15   - end
16   -
17   - subject { page }
18   -
19   - it { should have_content(@key.title) }
20   -
21   - describe "Destroy" do
22   - before { visit project_deploy_key_path(project, @key) }
23   -
24   - it "should remove entry" do
25   - expect {
26   - click_link "Remove"
27   - }.to change { project.deploy_keys.count }.by(-1)
28   - end
29   - end
30   - end
31   -
32   - describe "New key" do
33   - before do
34   - visit project_deploy_keys_path(project)
35   - click_link "New Deploy Key"
36   - end
37   -
38   - it "should open new key popup" do
39   - page.should have_content("New Deploy key")
40   - end
41   -
42   - describe "fill in" do
43   - before do
44   - fill_in "key_title", with: "laptop"
45   - fill_in "key_key", with: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop"
46   - end
47   -
48   - it { expect { click_button "Save" }.to change {Key.count}.by(1) }
49   -
50   - it "should add new key to table" do
51   - click_button "Save"
52   -
53   - page.should have_content "laptop"
54   - end
55   - end
56   - end
57   -
58   - describe "Show page" do
59   - before do
60   - @key = create(:key, project: project)
61   - visit project_deploy_key_path(project, @key)
62   - end
63   -
64   - it { page.should have_content @key.title }
65   - it { page.should have_content @key.key[0..10] }
66   - end
67   -end
spec/models/deploy_key_spec.rb 0 → 100644
... ... @@ -0,0 +1,25 @@
  1 +# == Schema Information
  2 +#
  3 +# Table name: keys
  4 +#
  5 +# id :integer not null, primary key
  6 +# user_id :integer
  7 +# created_at :datetime not null
  8 +# updated_at :datetime not null
  9 +# key :text
  10 +# title :string(255)
  11 +# identifier :string(255)
  12 +# project_id :integer
  13 +#
  14 +
  15 +require 'spec_helper'
  16 +
  17 +describe DeployKey do
  18 + let(:project) { create(:project) }
  19 + let(:deploy_key) { create(:deploy_key, projects: [project]) }
  20 +
  21 + describe "Associations" do
  22 + it { should have_many(:deploy_keys_projects) }
  23 + it { should have_many(:projects) }
  24 + end
  25 +end
... ...
spec/models/deploy_keys_project_spec.rb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +require 'spec_helper'
  2 +
  3 +describe DeployKeysProject do
  4 + describe "Associations" do
  5 + it { should belong_to(:deploy_key) }
  6 + it { should belong_to(:project) }
  7 + end
  8 +
  9 + describe "Validation" do
  10 + it { should validate_presence_of(:project_id) }
  11 + it { should validate_presence_of(:deploy_key_id) }
  12 + end
  13 +end
... ...
spec/models/key_spec.rb
... ... @@ -17,7 +17,6 @@ require &#39;spec_helper&#39;
17 17 describe Key do
18 18 describe "Associations" do
19 19 it { should belong_to(:user) }
20   - it { should belong_to(:project) }
21 20 end
22 21  
23 22 describe "Mass assignment" do
... ... @@ -37,32 +36,15 @@ describe Key do
37 36 end
38 37  
39 38 context "validation of uniqueness" do
  39 + let(:user) { create(:user) }
40 40  
41   - context "as a deploy key" do
42   - let!(:deploy_key) { create(:deploy_key) }
43   -
44   - it "does not accept the same key twice for a project" do
45   - key = build(:key, project: deploy_key.project)
46   - key.should_not be_valid
47   - end
48   -
49   - it "does not accept the same key for another project" do
50   - key = build(:key, project_id: 0)
51   - key.should_not be_valid
52   - end
  41 + it "accepts the key once" do
  42 + build(:key, user: user).should be_valid
53 43 end
54 44  
55   - context "as a personal key" do
56   - let(:user) { create(:user) }
57   -
58   - it "accepts the key once" do
59   - build(:key, user: user).should be_valid
60   - end
61   -
62   - it "does not accepts the key twice" do
63   - create(:key, user: user)
64   - build(:key, user: user).should_not be_valid
65   - end
  45 + it "does not accepts the key twice" do
  46 + create(:key, user: user)
  47 + build(:key, user: user).should_not be_valid
66 48 end
67 49 end
68 50  
... ...
spec/models/project_spec.rb
... ... @@ -37,7 +37,8 @@ describe Project do
37 37 it { should have_many(:users_projects).dependent(:destroy) }
38 38 it { should have_many(:notes).dependent(:destroy) }
39 39 it { should have_many(:snippets).dependent(:destroy) }
40   - it { should have_many(:deploy_keys).dependent(:destroy) }
  40 + it { should have_many(:deploy_keys_projects).dependent(:destroy) }
  41 + it { should have_many(:deploy_keys) }
41 42 it { should have_many(:hooks).dependent(:destroy) }
42 43 it { should have_many(:protected_branches).dependent(:destroy) }
43 44 it { should have_one(:forked_project_link).dependent(:destroy) }
... ...
spec/requests/api/projects_spec.rb
... ... @@ -13,7 +13,6 @@ describe Gitlab::API do
13 13 let!(:snippet) { create(:snippet, author: user, project: project, title: 'example') }
14 14 let!(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) }
15 15 let!(:users_project2) { create(:users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER) }
16   - let(:key) { create(:key, project: project) }
17 16  
18 17 before { project.team << [user, :reporter] }
19 18  
... ... @@ -636,58 +635,61 @@ describe Gitlab::API do
636 635 end
637 636 end
638 637  
639   - describe "GET /projects/:id/keys" do
640   - it "should return array of ssh keys" do
641   - project.deploy_keys << key
642   - project.save
643   - get api("/projects/#{project.id}/keys", user)
644   - response.status.should == 200
645   - json_response.should be_an Array
646   - json_response.first['title'].should == key.title
647   - end
648   - end
  638 + describe :deploy_keys do
  639 + let(:deploy_keys_project) { create(:deploy_keys_project, project: project) }
  640 + let(:deploy_key) { deploy_keys_project.deploy_key }
649 641  
650   - describe "GET /projects/:id/keys/:key_id" do
651   - it "should return a single key" do
652   - project.deploy_keys << key
653   - project.save
654   - get api("/projects/#{project.id}/keys/#{key.id}", user)
655   - response.status.should == 200
656   - json_response['title'].should == key.title
657   - end
  642 + describe "GET /projects/:id/keys" do
  643 + before { deploy_key }
658 644  
659   - it "should return 404 Not Found with invalid ID" do
660   - get api("/projects/#{project.id}/keys/404", user)
661   - response.status.should == 404
  645 + it "should return array of ssh keys" do
  646 + get api("/projects/#{project.id}/keys", user)
  647 + response.status.should == 200
  648 + json_response.should be_an Array
  649 + json_response.first['title'].should == deploy_key.title
  650 + end
662 651 end
663   - end
664 652  
665   - describe "POST /projects/:id/keys" do
666   - it "should not create an invalid ssh key" do
667   - post api("/projects/#{project.id}/keys", user), { title: "invalid key" }
668   - response.status.should == 404
669   - end
  653 + describe "GET /projects/:id/keys/:key_id" do
  654 + it "should return a single key" do
  655 + get api("/projects/#{project.id}/keys/#{deploy_key.id}", user)
  656 + response.status.should == 200
  657 + json_response['title'].should == deploy_key.title
  658 + end
670 659  
671   - it "should create new ssh key" do
672   - key_attrs = attributes_for :key
673   - expect {
674   - post api("/projects/#{project.id}/keys", user), key_attrs
675   - }.to change{ project.deploy_keys.count }.by(1)
  660 + it "should return 404 Not Found with invalid ID" do
  661 + get api("/projects/#{project.id}/keys/404", user)
  662 + response.status.should == 404
  663 + end
676 664 end
677   - end
678 665  
679   - describe "DELETE /projects/:id/keys/:key_id" do
680   - it "should delete existing key" do
681   - project.deploy_keys << key
682   - project.save
683   - expect {
684   - delete api("/projects/#{project.id}/keys/#{key.id}", user)
685   - }.to change{ project.deploy_keys.count }.by(-1)
  666 + describe "POST /projects/:id/keys" do
  667 + it "should not create an invalid ssh key" do
  668 + post api("/projects/#{project.id}/keys", user), { title: "invalid key" }
  669 + response.status.should == 404
  670 + end
  671 +
  672 + it "should create new ssh key" do
  673 + key_attrs = attributes_for :key
  674 + expect {
  675 + post api("/projects/#{project.id}/keys", user), key_attrs
  676 + }.to change{ project.deploy_keys.count }.by(1)
  677 + end
686 678 end
687 679  
688   - it "should return 404 Not Found with invalid ID" do
689   - delete api("/projects/#{project.id}/keys/404", user)
690   - response.status.should == 404
  680 + describe "DELETE /projects/:id/keys/:key_id" do
  681 + before { deploy_key }
  682 +
  683 + it "should delete existing key" do
  684 + expect {
  685 + delete api("/projects/#{project.id}/keys/#{deploy_key.id}", user)
  686 + }.to change{ project.deploy_keys.count }.by(-1)
  687 + end
  688 +
  689 + it "should return 404 Not Found with invalid ID" do
  690 + delete api("/projects/#{project.id}/keys/404", user)
  691 + response.status.should == 404
  692 + end
691 693 end
692 694 end
693 695 end
... ...