Commit b01f8b63c2c13f8d6b9111771fb4f1422214d91c
1 parent
44209861
Exists in
master
and in
4 other branches
added NamespacedProject role. Extended project info displayed for admin. Fixed project limit
Showing
18 changed files
with
201 additions
and
131 deletions
Show diff stats
app/controllers/groups_controller.rb
... | ... | @@ -49,6 +49,7 @@ class GroupsController < ApplicationController |
49 | 49 | def people |
50 | 50 | @project = group.projects.find(params[:project_id]) if params[:project_id] |
51 | 51 | @users = @project ? @project.users : group.users |
52 | + @users.sort_by!(&:name) | |
52 | 53 | |
53 | 54 | if @project |
54 | 55 | @team_member = @project.users_projects.new | ... | ... |
app/controllers/snippets_controller.rb
... | ... | @@ -16,7 +16,7 @@ class SnippetsController < ProjectResourceController |
16 | 16 | respond_to :html |
17 | 17 | |
18 | 18 | def index |
19 | - @snippets = @project.snippets | |
19 | + @snippets = @project.snippets.fresh | |
20 | 20 | end |
21 | 21 | |
22 | 22 | def new |
... | ... | @@ -60,7 +60,7 @@ class SnippetsController < ProjectResourceController |
60 | 60 | redirect_to project_snippets_path(@project) |
61 | 61 | end |
62 | 62 | |
63 | - def raw | |
63 | + def raw | |
64 | 64 | send_data( |
65 | 65 | @snippet.content, |
66 | 66 | type: "text/plain", | ... | ... |
app/models/project.rb
... | ... | @@ -25,6 +25,7 @@ class Project < ActiveRecord::Base |
25 | 25 | include PushObserver |
26 | 26 | include Authority |
27 | 27 | include Team |
28 | + include NamespacedProject | |
28 | 29 | |
29 | 30 | class TransferError < StandardError; end |
30 | 31 | |
... | ... | @@ -178,7 +179,7 @@ class Project < ActiveRecord::Base |
178 | 179 | end |
179 | 180 | |
180 | 181 | def repo_name |
181 | - denied_paths = %w(gitolite-admin groups projects dashboard) | |
182 | + denied_paths = %w(gitolite-admin groups projects dashboard help ) | |
182 | 183 | |
183 | 184 | if denied_paths.include?(path) |
184 | 185 | errors.add(:path, "like #{path} is not allowed") |
... | ... | @@ -245,57 +246,11 @@ class Project < ActiveRecord::Base |
245 | 246 | gitlab_ci_service && gitlab_ci_service.active |
246 | 247 | end |
247 | 248 | |
248 | - def path_with_namespace | |
249 | - if namespace | |
250 | - namespace.path + '/' + path | |
251 | - else | |
252 | - path | |
253 | - end | |
254 | - end | |
255 | - | |
256 | 249 | # For compatibility with old code |
257 | 250 | def code |
258 | 251 | path |
259 | 252 | end |
260 | 253 | |
261 | - def transfer(new_namespace) | |
262 | - Project.transaction do | |
263 | - old_namespace = namespace | |
264 | - self.namespace = new_namespace | |
265 | - | |
266 | - old_dir = old_namespace.try(:path) || '' | |
267 | - new_dir = new_namespace.try(:path) || '' | |
268 | - | |
269 | - old_repo = if old_dir.present? | |
270 | - File.join(old_dir, self.path) | |
271 | - else | |
272 | - self.path | |
273 | - end | |
274 | - | |
275 | - if Project.where(path: self.path, namespace_id: new_namespace.try(:id)).present? | |
276 | - raise TransferError.new("Project with same path in target namespace already exists") | |
277 | - end | |
278 | - | |
279 | - Gitlab::ProjectMover.new(self, old_dir, new_dir).execute | |
280 | - | |
281 | - git_host.move_repository(old_repo, self) | |
282 | - | |
283 | - save! | |
284 | - end | |
285 | - rescue Gitlab::ProjectMover::ProjectMoveError => ex | |
286 | - raise TransferError.new(ex.message) | |
287 | - end | |
288 | - | |
289 | - def name_with_namespace | |
290 | - @name_with_namespace ||= begin | |
291 | - if namespace | |
292 | - namespace.human_name + " / " + name | |
293 | - else | |
294 | - name | |
295 | - end | |
296 | - end | |
297 | - end | |
298 | - | |
299 | 254 | def items_for entity |
300 | 255 | case entity |
301 | 256 | when 'issue' then |
... | ... | @@ -304,16 +259,4 @@ class Project < ActiveRecord::Base |
304 | 259 | merge_requests |
305 | 260 | end |
306 | 261 | end |
307 | - | |
308 | - def namespace_owner | |
309 | - namespace.try(:owner) | |
310 | - end | |
311 | - | |
312 | - def chief | |
313 | - if namespace | |
314 | - namespace_owner | |
315 | - else | |
316 | - owner | |
317 | - end | |
318 | - end | |
319 | 262 | end | ... | ... |
app/models/user.rb
... | ... | @@ -56,7 +56,6 @@ class User < ActiveRecord::Base |
56 | 56 | has_many :issues, foreign_key: :author_id, dependent: :destroy |
57 | 57 | has_many :notes, foreign_key: :author_id, dependent: :destroy |
58 | 58 | has_many :merge_requests, foreign_key: :author_id, dependent: :destroy |
59 | - has_many :my_own_projects, class_name: "Project", foreign_key: :owner_id | |
60 | 59 | has_many :events, class_name: "Event", foreign_key: :author_id, dependent: :destroy |
61 | 60 | has_many :recent_events, class_name: "Event", foreign_key: :author_id, order: "id DESC" |
62 | 61 | has_many :assigned_issues, class_name: "Issue", foreign_key: :assignee_id, dependent: :destroy |
... | ... | @@ -124,16 +123,4 @@ class User < ActiveRecord::Base |
124 | 123 | self.password = self.password_confirmation = Devise.friendly_token.first(8) |
125 | 124 | end |
126 | 125 | end |
127 | - | |
128 | - def authorized_groups | |
129 | - @authorized_groups ||= begin | |
130 | - groups = Group.where(id: self.projects.pluck(:namespace_id)).all | |
131 | - groups = groups + self.groups | |
132 | - groups.uniq | |
133 | - end | |
134 | - end | |
135 | - | |
136 | - def authorized_projects | |
137 | - Project.authorized_for(self) | |
138 | - end | |
139 | 126 | end | ... | ... |
app/roles/account.rb
... | ... | @@ -105,4 +105,20 @@ module Account |
105 | 105 | def namespace_id |
106 | 106 | namespace.try :id |
107 | 107 | end |
108 | + | |
109 | + def authorized_groups | |
110 | + @authorized_groups ||= begin | |
111 | + groups = Group.where(id: self.projects.pluck(:namespace_id)).all | |
112 | + groups = groups + self.groups | |
113 | + groups.uniq | |
114 | + end | |
115 | + end | |
116 | + | |
117 | + def authorized_projects | |
118 | + Project.authorized_for(self) | |
119 | + end | |
120 | + | |
121 | + def my_own_projects | |
122 | + Project.personal(self) | |
123 | + end | |
108 | 124 | end | ... | ... |
... | ... | @@ -0,0 +1,59 @@ |
1 | +module NamespacedProject | |
2 | + def transfer(new_namespace) | |
3 | + Project.transaction do | |
4 | + old_namespace = namespace | |
5 | + self.namespace = new_namespace | |
6 | + | |
7 | + old_dir = old_namespace.try(:path) || '' | |
8 | + new_dir = new_namespace.try(:path) || '' | |
9 | + | |
10 | + old_repo = if old_dir.present? | |
11 | + File.join(old_dir, self.path) | |
12 | + else | |
13 | + self.path | |
14 | + end | |
15 | + | |
16 | + if Project.where(path: self.path, namespace_id: new_namespace.try(:id)).present? | |
17 | + raise TransferError.new("Project with same path in target namespace already exists") | |
18 | + end | |
19 | + | |
20 | + Gitlab::ProjectMover.new(self, old_dir, new_dir).execute | |
21 | + | |
22 | + git_host.move_repository(old_repo, self) | |
23 | + | |
24 | + save! | |
25 | + end | |
26 | + rescue Gitlab::ProjectMover::ProjectMoveError => ex | |
27 | + raise TransferError.new(ex.message) | |
28 | + end | |
29 | + | |
30 | + def name_with_namespace | |
31 | + @name_with_namespace ||= begin | |
32 | + if namespace | |
33 | + namespace.human_name + " / " + name | |
34 | + else | |
35 | + name | |
36 | + end | |
37 | + end | |
38 | + end | |
39 | + | |
40 | + def namespace_owner | |
41 | + namespace.try(:owner) | |
42 | + end | |
43 | + | |
44 | + def chief | |
45 | + if namespace | |
46 | + namespace_owner | |
47 | + else | |
48 | + owner | |
49 | + end | |
50 | + end | |
51 | + | |
52 | + def path_with_namespace | |
53 | + if namespace | |
54 | + namespace.path + '/' + path | |
55 | + else | |
56 | + path | |
57 | + end | |
58 | + end | |
59 | +end | ... | ... |
app/views/admin/projects/_form.html.haml
... | ... | @@ -19,43 +19,47 @@ |
19 | 19 | .input |
20 | 20 | = text_field_tag :ppath, @project.path_to_repo, class: "xlarge", disabled: true |
21 | 21 | |
22 | - - unless project.new_record? | |
22 | + - if project.repo_exists? | |
23 | 23 | .clearfix |
24 | - = f.label :namespace_id | |
25 | - .input | |
26 | - = f.select :namespace_id, namespaces_options(@project.namespace_id, :all), {}, {class: 'chosen'} | |
27 | - | |
28 | - %span.cred Be careful. Changing project namespace can have unintended side effects | |
24 | + = f.label :default_branch, "Default Branch" | |
25 | + .input= f.select(:default_branch, project.heads.map(&:name), {}, style: "width:210px;") | |
29 | 26 | |
30 | - - if project.repo_exists? | |
31 | - .clearfix | |
32 | - = f.label :default_branch, "Default Branch" | |
33 | - .input= f.select(:default_branch, project.heads.map(&:name), {}, style: "width:210px;") | |
27 | + %fieldset.adv_settings | |
28 | + %legend Features: | |
34 | 29 | |
35 | - - unless project.new_record? | |
36 | - %fieldset.adv_settings | |
37 | - %legend Features: | |
30 | + .clearfix | |
31 | + = f.label :issues_enabled, "Issues" | |
32 | + .input= f.check_box :issues_enabled | |
38 | 33 | |
39 | - .clearfix | |
40 | - = f.label :issues_enabled, "Issues" | |
41 | - .input= f.check_box :issues_enabled | |
34 | + .clearfix | |
35 | + = f.label :merge_requests_enabled, "Merge Requests" | |
36 | + .input= f.check_box :merge_requests_enabled | |
42 | 37 | |
43 | - .clearfix | |
44 | - = f.label :merge_requests_enabled, "Merge Requests" | |
45 | - .input= f.check_box :merge_requests_enabled | |
38 | + .clearfix | |
39 | + = f.label :wall_enabled, "Wall" | |
40 | + .input= f.check_box :wall_enabled | |
46 | 41 | |
47 | - .clearfix | |
48 | - = f.label :wall_enabled, "Wall" | |
49 | - .input= f.check_box :wall_enabled | |
42 | + .clearfix | |
43 | + = f.label :wiki_enabled, "Wiki" | |
44 | + .input= f.check_box :wiki_enabled | |
45 | + | |
46 | + %fieldset.features | |
47 | + %legend Transfer: | |
48 | + .control-group | |
49 | + = f.label :namespace_id do | |
50 | + %span Namespace | |
51 | + .controls | |
52 | + = f.select :namespace_id, namespaces_options(@project.namespace_id, :all), {}, {class: 'chosen'} | |
53 | + %br | |
54 | + %ul.prepend-top-10.cred | |
55 | + %li Be careful. Changing project namespace can have unintended side effects | |
56 | + %li You can transfer project only to namespaces you can manage | |
57 | + %li You will need to update your local repositories to point to the new location. | |
50 | 58 | |
51 | - .clearfix | |
52 | - = f.label :wiki_enabled, "Wiki" | |
53 | - .input= f.check_box :wiki_enabled | |
54 | 59 | |
55 | - - unless project.new_record? | |
56 | - .actions | |
57 | - = f.submit 'Save Project', class: "btn save-btn" | |
58 | - = link_to 'Cancel', admin_projects_path, class: "btn cancel-btn" | |
60 | + .actions | |
61 | + = f.submit 'Save Project', class: "btn save-btn" | |
62 | + = link_to 'Cancel', admin_projects_path, class: "btn cancel-btn" | |
59 | 63 | |
60 | 64 | |
61 | 65 | ... | ... |
app/views/admin/projects/index.html.haml
app/views/admin/projects/show.html.haml
... | ... | @@ -47,9 +47,12 @@ |
47 | 47 | %tr |
48 | 48 | %td |
49 | 49 | %b |
50 | - Path: | |
50 | + Owned by: | |
51 | 51 | %td |
52 | - %code= @project.path_to_repo | |
52 | + - if @project.chief | |
53 | + = link_to @project.chief.name, admin_user_path(@project.chief) | |
54 | + - else | |
55 | + (deleted) | |
53 | 56 | %tr |
54 | 57 | %td |
55 | 58 | %b |
... | ... | @@ -59,9 +62,46 @@ |
59 | 62 | %tr |
60 | 63 | %td |
61 | 64 | %b |
65 | + Created at: | |
66 | + %td | |
67 | + = @project.created_at.stamp("March 1, 1999") | |
68 | + | |
69 | +%table.zebra-striped | |
70 | + %thead | |
71 | + %tr | |
72 | + %th Repository | |
73 | + %th | |
74 | + %tr | |
75 | + %td | |
76 | + %b | |
77 | + FS Path: | |
78 | + %td | |
79 | + %code= @project.path_to_repo | |
80 | + %tr | |
81 | + %td | |
82 | + %b | |
83 | + Smart HTTP: | |
84 | + %td | |
85 | + = link_to @project.http_url_to_repo | |
86 | + %tr | |
87 | + %td | |
88 | + %b | |
89 | + SSH: | |
90 | + %td | |
91 | + = link_to @project.ssh_url_to_repo | |
92 | + %tr | |
93 | + %td | |
94 | + %b | |
95 | + Last commit at: | |
96 | + %td | |
97 | + = last_commit(@project) | |
98 | + %tr | |
99 | + %td | |
100 | + %b | |
62 | 101 | Post Receive File: |
63 | 102 | %td |
64 | 103 | = check_box_tag :post_receive_file, 1, @project.has_post_receive_file?, disabled: true |
104 | + | |
65 | 105 | %br |
66 | 106 | %h5 |
67 | 107 | Team | ... | ... |
app/views/admin/users/index.html.haml
1 | 1 | %h3.page_title |
2 | - Users | |
2 | + Users (#{@admin_users.count}) | |
3 | 3 | = link_to 'New User', new_admin_user_path, class: "btn small right" |
4 | 4 | %br |
5 | 5 | |
... | ... | @@ -40,10 +40,13 @@ |
40 | 40 | %td= user.users_projects.count |
41 | 41 | %td= link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: "btn small" |
42 | 42 | %td.bgred |
43 | - - if user.blocked | |
44 | - = link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn small success" | |
43 | + - if user == current_user | |
44 | + %span.cred It's you! | |
45 | 45 | - else |
46 | - = link_to 'Block', block_admin_user_path(user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn small danger" | |
47 | - = link_to 'Destroy', [:admin, user], confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn small danger" | |
46 | + - if user.blocked | |
47 | + = link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn small success" | |
48 | + - else | |
49 | + = link_to 'Block', block_admin_user_path(user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn small danger" | |
50 | + = link_to 'Destroy', [:admin, user], confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn small danger" | |
48 | 51 | |
49 | 52 | = paginate @admin_users, theme: "admin" | ... | ... |
app/views/admin/users/show.html.haml
app/views/hooks/index.html.haml
... | ... | @@ -22,22 +22,21 @@ |
22 | 22 | %hr |
23 | 23 | |
24 | 24 | -if @hooks.any? |
25 | - %h3 | |
26 | - Hooks | |
27 | - %small (#{@hooks.count}) | |
25 | + %h3.page_title | |
26 | + Hooks (#{@hooks.count}) | |
28 | 27 | %br |
29 | 28 | %table |
30 | 29 | %thead |
31 | 30 | %tr |
32 | 31 | %th URL |
33 | - %th Method | |
34 | 32 | %th |
35 | 33 | - @hooks.each do |hook| |
36 | 34 | %tr |
37 | 35 | %td |
36 | + %span.badge.badge-info POST | |
38 | 37 | = link_to project_hook_path(@project, hook) do |
39 | 38 | %strong= hook.url |
40 | - = link_to 'Test Hook', test_project_hook_path(@project, hook), class: "btn small right" | |
41 | - %td POST | |
42 | 39 | %td |
43 | - = link_to 'Remove', project_hook_path(@project, hook), confirm: 'Are you sure?', method: :delete, class: "danger btn small right" | |
40 | + .right | |
41 | + = link_to 'Test Hook', test_project_hook_path(@project, hook), class: "btn small grouped" | |
42 | + = link_to 'Remove', project_hook_path(@project, hook), confirm: 'Are you sure?', method: :delete, class: "danger btn small grouped" | ... | ... |
app/views/projects/create.js.haml
app/views/projects/files.html.haml
... | ... | @@ -17,7 +17,6 @@ |
17 | 17 | = time_ago_in_words(note.created_at) |
18 | 18 | ago |
19 | 19 | - else |
20 | - .alert-message.block-message | |
21 | - %span All files attached to project wall, issues etc will be displayed here | |
20 | + %p.slead All files attached to project wall, issues etc will be displayed here | |
22 | 21 | |
23 | 22 | ... | ... |
app/views/snippets/_snippet.html.haml
1 | 1 | %tr |
2 | 2 | %td |
3 | + = image_tag gravatar_icon(snippet.author_email), class: "avatar s24" | |
3 | 4 | %a{href: project_snippet_path(snippet.project, snippet)} |
4 | 5 | %strong= truncate(snippet.title, length: 60) |
5 | 6 | %td |
6 | 7 | = snippet.file_name |
7 | 8 | %td |
8 | 9 | %span.cgray |
9 | - - if snippet.expires_at | |
10 | + - if snippet.expires_at | |
10 | 11 | = snippet.expires_at.to_date.to_s(:short) |
11 | 12 | - else |
12 | 13 | Never | ... | ... |
app/views/snippets/index.html.haml
1 | 1 | = render "projects/project_head" |
2 | 2 | |
3 | -- if can? current_user, :write_snippet, @project | |
4 | - .alert-message.block-message | |
3 | +%h3.page_title | |
4 | + Snippets | |
5 | + %small share code pastes with others out of git repository | |
6 | + | |
7 | + - if can? current_user, :write_snippet, @project | |
5 | 8 | = link_to new_project_snippet_path(@project), class: "btn small add_new right", title: "New Snippet" do |
6 | 9 | Add new snippet |
7 | - Share code pastes with others if it can't be in a git repository | |
8 | - %br | |
9 | - To add new snippet - click on button. | |
10 | - | |
10 | +%br | |
11 | 11 | %table |
12 | 12 | %thead |
13 | 13 | %tr |
14 | 14 | %th Title |
15 | 15 | %th File Name |
16 | 16 | %th Expires At |
17 | - = render @snippets.fresh | |
18 | - - if @snippets.fresh.empty? | |
17 | + = render @snippets | |
18 | + - if @snippets.empty? | |
19 | 19 | %tr |
20 | 20 | %td{colspan: 3} |
21 | 21 | %h3.nothing_here_message Nothing here. | ... | ... |
app/views/team_members/_team.html.haml
spec/models/user_spec.rb
... | ... | @@ -41,7 +41,6 @@ describe User do |
41 | 41 | it { should have_many(:users_projects).dependent(:destroy) } |
42 | 42 | it { should have_many(:projects) } |
43 | 43 | it { should have_many(:groups) } |
44 | - it { should have_many(:my_own_projects).class_name('Project') } | |
45 | 44 | it { should have_many(:keys).dependent(:destroy) } |
46 | 45 | it { should have_many(:events).class_name('Event').dependent(:destroy) } |
47 | 46 | it { should have_many(:recent_events).class_name('Event') } |
... | ... | @@ -116,4 +115,16 @@ describe User do |
116 | 115 | user.authentication_token.should_not be_blank |
117 | 116 | end |
118 | 117 | end |
118 | + | |
119 | + describe 'projects and namespaces' do | |
120 | + before do | |
121 | + ActiveRecord::Base.observers.enable(:user_observer) | |
122 | + @user = create :user | |
123 | + @project = create :project, namespace: @user.namespace | |
124 | + end | |
125 | + | |
126 | + it { @user.authorized_projects.should include(@project) } | |
127 | + it { @user.my_own_projects.should include(@project) } | |
128 | + it { @user.several_namespaces?.should be_false } | |
129 | + end | |
119 | 130 | end | ... | ... |