Commit cac7723451e575ce39a6930990178450a2a972f0
1 parent
1b25a8f4
Exists in
master
and in
4 other branches
Get rid of roles
Showing
27 changed files
with
1273 additions
and
1350 deletions
Show diff stats
app/models/event.rb
... | ... | @@ -170,4 +170,139 @@ class Event < ActiveRecord::Base |
170 | 170 | "opened" |
171 | 171 | end |
172 | 172 | end |
173 | + | |
174 | + def valid_push? | |
175 | + data[:ref] | |
176 | + rescue => ex | |
177 | + false | |
178 | + end | |
179 | + | |
180 | + def tag? | |
181 | + data[:ref]["refs/tags"] | |
182 | + end | |
183 | + | |
184 | + def branch? | |
185 | + data[:ref]["refs/heads"] | |
186 | + end | |
187 | + | |
188 | + def new_branch? | |
189 | + commit_from =~ /^00000/ | |
190 | + end | |
191 | + | |
192 | + def new_ref? | |
193 | + commit_from =~ /^00000/ | |
194 | + end | |
195 | + | |
196 | + def rm_ref? | |
197 | + commit_to =~ /^00000/ | |
198 | + end | |
199 | + | |
200 | + def md_ref? | |
201 | + !(rm_ref? || new_ref?) | |
202 | + end | |
203 | + | |
204 | + def commit_from | |
205 | + data[:before] | |
206 | + end | |
207 | + | |
208 | + def commit_to | |
209 | + data[:after] | |
210 | + end | |
211 | + | |
212 | + def ref_name | |
213 | + if tag? | |
214 | + tag_name | |
215 | + else | |
216 | + branch_name | |
217 | + end | |
218 | + end | |
219 | + | |
220 | + def branch_name | |
221 | + @branch_name ||= data[:ref].gsub("refs/heads/", "") | |
222 | + end | |
223 | + | |
224 | + def tag_name | |
225 | + @tag_name ||= data[:ref].gsub("refs/tags/", "") | |
226 | + end | |
227 | + | |
228 | + # Max 20 commits from push DESC | |
229 | + def commits | |
230 | + @commits ||= data[:commits].map { |commit| project.commit(commit[:id]) }.reverse | |
231 | + end | |
232 | + | |
233 | + def commits_count | |
234 | + data[:total_commits_count] || commits.count || 0 | |
235 | + end | |
236 | + | |
237 | + def ref_type | |
238 | + tag? ? "tag" : "branch" | |
239 | + end | |
240 | + | |
241 | + def push_action_name | |
242 | + if new_ref? | |
243 | + "pushed new" | |
244 | + elsif rm_ref? | |
245 | + "deleted" | |
246 | + else | |
247 | + "pushed to" | |
248 | + end | |
249 | + end | |
250 | + | |
251 | + def parent_commit | |
252 | + project.commit(commit_from) | |
253 | + rescue => ex | |
254 | + nil | |
255 | + end | |
256 | + | |
257 | + def last_commit | |
258 | + project.commit(commit_to) | |
259 | + rescue => ex | |
260 | + nil | |
261 | + end | |
262 | + | |
263 | + def push_with_commits? | |
264 | + md_ref? && commits.any? && parent_commit && last_commit | |
265 | + rescue Grit::NoSuchPathError | |
266 | + false | |
267 | + end | |
268 | + | |
269 | + def last_push_to_non_root? | |
270 | + branch? && project.default_branch != branch_name | |
271 | + end | |
272 | + | |
273 | + def note_commit_id | |
274 | + target.commit_id | |
275 | + end | |
276 | + | |
277 | + def note_short_commit_id | |
278 | + note_commit_id[0..8] | |
279 | + end | |
280 | + | |
281 | + def note_commit? | |
282 | + target.noteable_type == "Commit" | |
283 | + end | |
284 | + | |
285 | + def note_target | |
286 | + target.noteable | |
287 | + end | |
288 | + | |
289 | + def note_target_id | |
290 | + if note_commit? | |
291 | + target.commit_id | |
292 | + else | |
293 | + target.noteable_id.to_s | |
294 | + end | |
295 | + end | |
296 | + | |
297 | + def wall_note? | |
298 | + target.noteable_type.blank? | |
299 | + end | |
300 | + | |
301 | + def note_target_type | |
302 | + if target.noteable_type.present? | |
303 | + target.noteable_type.titleize | |
304 | + else | |
305 | + "Wall" | |
306 | + end.downcase | |
307 | + end | |
173 | 308 | end | ... | ... |
app/models/project.rb
... | ... | @@ -21,11 +21,7 @@ |
21 | 21 | require "grit" |
22 | 22 | |
23 | 23 | class Project < ActiveRecord::Base |
24 | - include Repository | |
25 | - include PushObserver | |
26 | - include Authority | |
27 | - include Team | |
28 | - include NamespacedProject | |
24 | + include GitHost | |
29 | 25 | |
30 | 26 | class TransferError < StandardError; end |
31 | 27 | |
... | ... | @@ -277,4 +273,514 @@ class Project < ActiveRecord::Base |
277 | 273 | creator |
278 | 274 | end |
279 | 275 | end |
276 | + | |
277 | + def team_member_by_name_or_email(name = nil, email = nil) | |
278 | + user = users.where("name like ? or email like ?", name, email).first | |
279 | + users_projects.where(user: user) if user | |
280 | + end | |
281 | + | |
282 | + # Get Team Member record by user id | |
283 | + def team_member_by_id(user_id) | |
284 | + users_projects.find_by_user_id(user_id) | |
285 | + end | |
286 | + | |
287 | + # Add user to project | |
288 | + # with passed access role | |
289 | + def add_user_to_team(user, access_role) | |
290 | + add_user_id_to_team(user.id, access_role) | |
291 | + end | |
292 | + | |
293 | + # Add multiple users to project | |
294 | + # with same access role | |
295 | + def add_users_to_team(users, access_role) | |
296 | + add_users_ids_to_team(users.map(&:id), access_role) | |
297 | + end | |
298 | + | |
299 | + # Add user to project | |
300 | + # with passed access role by user id | |
301 | + def add_user_id_to_team(user_id, access_role) | |
302 | + users_projects.create( | |
303 | + user_id: user_id, | |
304 | + project_access: access_role | |
305 | + ) | |
306 | + end | |
307 | + | |
308 | + # Add multiple users to project | |
309 | + # with same access role by user ids | |
310 | + def add_users_ids_to_team(users_ids, access_role) | |
311 | + UsersProject.bulk_import(self, users_ids, access_role) | |
312 | + end | |
313 | + | |
314 | + # Update multiple project users | |
315 | + # to same access role by user ids | |
316 | + def update_users_ids_to_role(users_ids, access_role) | |
317 | + UsersProject.bulk_update(self, users_ids, access_role) | |
318 | + end | |
319 | + | |
320 | + # Delete multiple users from project by user ids | |
321 | + def delete_users_ids_from_team(users_ids) | |
322 | + UsersProject.bulk_delete(self, users_ids) | |
323 | + end | |
324 | + | |
325 | + # Remove all users from project team | |
326 | + def truncate_team | |
327 | + UsersProject.truncate_team(self) | |
328 | + end | |
329 | + | |
330 | + # Compatible with all access rights | |
331 | + # Should be rewrited for new access rights | |
332 | + def add_access(user, *access) | |
333 | + access = if access.include?(:admin) | |
334 | + { project_access: UsersProject::MASTER } | |
335 | + elsif access.include?(:write) | |
336 | + { project_access: UsersProject::DEVELOPER } | |
337 | + else | |
338 | + { project_access: UsersProject::REPORTER } | |
339 | + end | |
340 | + opts = { user: user } | |
341 | + opts.merge!(access) | |
342 | + users_projects.create(opts) | |
343 | + end | |
344 | + | |
345 | + def reset_access(user) | |
346 | + users_projects.where(project_id: self.id, user_id: user.id).destroy if self.id | |
347 | + end | |
348 | + | |
349 | + def repository_readers | |
350 | + repository_members[UsersProject::REPORTER] | |
351 | + end | |
352 | + | |
353 | + def repository_writers | |
354 | + repository_members[UsersProject::DEVELOPER] | |
355 | + end | |
356 | + | |
357 | + def repository_masters | |
358 | + repository_members[UsersProject::MASTER] | |
359 | + end | |
360 | + | |
361 | + def repository_members | |
362 | + keys = Hash.new {|h,k| h[k] = [] } | |
363 | + UsersProject.select("keys.identifier, project_access"). | |
364 | + joins(user: :keys).where(project_id: id). | |
365 | + each {|row| keys[row.project_access] << [row.identifier] } | |
366 | + | |
367 | + keys[UsersProject::REPORTER] += deploy_keys.pluck(:identifier) | |
368 | + keys | |
369 | + end | |
370 | + | |
371 | + def allow_read_for?(user) | |
372 | + !users_projects.where(user_id: user.id).empty? | |
373 | + end | |
374 | + | |
375 | + def guest_access_for?(user) | |
376 | + !users_projects.where(user_id: user.id).empty? | |
377 | + end | |
378 | + | |
379 | + def report_access_for?(user) | |
380 | + !users_projects.where(user_id: user.id, project_access: [UsersProject::REPORTER, UsersProject::DEVELOPER, UsersProject::MASTER]).empty? | |
381 | + end | |
382 | + | |
383 | + def dev_access_for?(user) | |
384 | + !users_projects.where(user_id: user.id, project_access: [UsersProject::DEVELOPER, UsersProject::MASTER]).empty? | |
385 | + end | |
386 | + | |
387 | + def master_access_for?(user) | |
388 | + !users_projects.where(user_id: user.id, project_access: [UsersProject::MASTER]).empty? | |
389 | + end | |
390 | + | |
391 | + def transfer(new_namespace) | |
392 | + Project.transaction do | |
393 | + old_namespace = namespace | |
394 | + self.namespace = new_namespace | |
395 | + | |
396 | + old_dir = old_namespace.try(:path) || '' | |
397 | + new_dir = new_namespace.try(:path) || '' | |
398 | + | |
399 | + old_repo = if old_dir.present? | |
400 | + File.join(old_dir, self.path) | |
401 | + else | |
402 | + self.path | |
403 | + end | |
404 | + | |
405 | + if Project.where(path: self.path, namespace_id: new_namespace.try(:id)).present? | |
406 | + raise TransferError.new("Project with same path in target namespace already exists") | |
407 | + end | |
408 | + | |
409 | + Gitlab::ProjectMover.new(self, old_dir, new_dir).execute | |
410 | + | |
411 | + git_host.move_repository(old_repo, self) | |
412 | + | |
413 | + save! | |
414 | + end | |
415 | + rescue Gitlab::ProjectMover::ProjectMoveError => ex | |
416 | + raise Project::TransferError.new(ex.message) | |
417 | + end | |
418 | + | |
419 | + def name_with_namespace | |
420 | + @name_with_namespace ||= begin | |
421 | + if namespace | |
422 | + namespace.human_name + " / " + name | |
423 | + else | |
424 | + name | |
425 | + end | |
426 | + end | |
427 | + end | |
428 | + | |
429 | + def namespace_owner | |
430 | + namespace.try(:owner) | |
431 | + end | |
432 | + | |
433 | + def path_with_namespace | |
434 | + if namespace | |
435 | + namespace.path + '/' + path | |
436 | + else | |
437 | + path | |
438 | + end | |
439 | + end | |
440 | + | |
441 | + # This method will be called after each post receive and only if the provided | |
442 | + # user is present in GitLab. | |
443 | + # | |
444 | + # All callbacks for post receive should be placed here. | |
445 | + def trigger_post_receive(oldrev, newrev, ref, user) | |
446 | + data = post_receive_data(oldrev, newrev, ref, user) | |
447 | + | |
448 | + # Create push event | |
449 | + self.observe_push(data) | |
450 | + | |
451 | + if push_to_branch? ref, oldrev | |
452 | + # Close merged MR | |
453 | + self.update_merge_requests(oldrev, newrev, ref, user) | |
454 | + | |
455 | + # Execute web hooks | |
456 | + self.execute_hooks(data.dup) | |
457 | + | |
458 | + # Execute project services | |
459 | + self.execute_services(data.dup) | |
460 | + end | |
461 | + | |
462 | + # Create satellite | |
463 | + self.satellite.create unless self.satellite.exists? | |
464 | + | |
465 | + # Discover the default branch, but only if it hasn't already been set to | |
466 | + # something else | |
467 | + if default_branch.nil? | |
468 | + update_attributes(default_branch: discover_default_branch) | |
469 | + end | |
470 | + end | |
471 | + | |
472 | + def push_to_branch? ref, oldrev | |
473 | + ref_parts = ref.split('/') | |
474 | + | |
475 | + # Return if this is not a push to a branch (e.g. new commits) | |
476 | + !(ref_parts[1] !~ /heads/ || oldrev == "00000000000000000000000000000000") | |
477 | + end | |
478 | + | |
479 | + def observe_push(data) | |
480 | + Event.create( | |
481 | + project: self, | |
482 | + action: Event::Pushed, | |
483 | + data: data, | |
484 | + author_id: data[:user_id] | |
485 | + ) | |
486 | + end | |
487 | + | |
488 | + def execute_hooks(data) | |
489 | + hooks.each { |hook| hook.execute(data) } | |
490 | + end | |
491 | + | |
492 | + def execute_services(data) | |
493 | + services.each do |service| | |
494 | + | |
495 | + # Call service hook only if it is active | |
496 | + service.execute(data) if service.active | |
497 | + end | |
498 | + end | |
499 | + | |
500 | + # Produce a hash of post-receive data | |
501 | + # | |
502 | + # data = { | |
503 | + # before: String, | |
504 | + # after: String, | |
505 | + # ref: String, | |
506 | + # user_id: String, | |
507 | + # user_name: String, | |
508 | + # repository: { | |
509 | + # name: String, | |
510 | + # url: String, | |
511 | + # description: String, | |
512 | + # homepage: String, | |
513 | + # }, | |
514 | + # commits: Array, | |
515 | + # total_commits_count: Fixnum | |
516 | + # } | |
517 | + # | |
518 | + def post_receive_data(oldrev, newrev, ref, user) | |
519 | + | |
520 | + push_commits = commits_between(oldrev, newrev) | |
521 | + | |
522 | + # Total commits count | |
523 | + push_commits_count = push_commits.size | |
524 | + | |
525 | + # Get latest 20 commits ASC | |
526 | + push_commits_limited = push_commits.last(20) | |
527 | + | |
528 | + # Hash to be passed as post_receive_data | |
529 | + data = { | |
530 | + before: oldrev, | |
531 | + after: newrev, | |
532 | + ref: ref, | |
533 | + user_id: user.id, | |
534 | + user_name: user.name, | |
535 | + repository: { | |
536 | + name: name, | |
537 | + url: url_to_repo, | |
538 | + description: description, | |
539 | + homepage: web_url, | |
540 | + }, | |
541 | + commits: [], | |
542 | + total_commits_count: push_commits_count | |
543 | + } | |
544 | + | |
545 | + # For perfomance purposes maximum 20 latest commits | |
546 | + # will be passed as post receive hook data. | |
547 | + # | |
548 | + push_commits_limited.each do |commit| | |
549 | + data[:commits] << { | |
550 | + id: commit.id, | |
551 | + message: commit.safe_message, | |
552 | + timestamp: commit.date.xmlschema, | |
553 | + url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{commit.id}", | |
554 | + author: { | |
555 | + name: commit.author_name, | |
556 | + email: commit.author_email | |
557 | + } | |
558 | + } | |
559 | + end | |
560 | + | |
561 | + data | |
562 | + end | |
563 | + | |
564 | + def update_merge_requests(oldrev, newrev, ref, user) | |
565 | + return true unless ref =~ /heads/ | |
566 | + branch_name = ref.gsub("refs/heads/", "") | |
567 | + c_ids = self.commits_between(oldrev, newrev).map(&:id) | |
568 | + | |
569 | + # Update code for merge requests | |
570 | + mrs = self.merge_requests.opened.find_all_by_branch(branch_name).all | |
571 | + mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked } | |
572 | + | |
573 | + # Close merge requests | |
574 | + mrs = self.merge_requests.opened.where(target_branch: branch_name).all | |
575 | + mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) } | |
576 | + mrs.each { |merge_request| merge_request.merge!(user.id) } | |
577 | + | |
578 | + true | |
579 | + end | |
580 | + | |
581 | + def valid_repo? | |
582 | + repo | |
583 | + rescue | |
584 | + errors.add(:path, "Invalid repository path") | |
585 | + false | |
586 | + end | |
587 | + | |
588 | + def empty_repo? | |
589 | + !repo_exists? || !has_commits? | |
590 | + end | |
591 | + | |
592 | + def commit(commit_id = nil) | |
593 | + Commit.find_or_first(repo, commit_id, root_ref) | |
594 | + end | |
595 | + | |
596 | + def fresh_commits(n = 10) | |
597 | + Commit.fresh_commits(repo, n) | |
598 | + end | |
599 | + | |
600 | + def commits_with_refs(n = 20) | |
601 | + Commit.commits_with_refs(repo, n) | |
602 | + end | |
603 | + | |
604 | + def commits_since(date) | |
605 | + Commit.commits_since(repo, date) | |
606 | + end | |
607 | + | |
608 | + def commits(ref, path = nil, limit = nil, offset = nil) | |
609 | + Commit.commits(repo, ref, path, limit, offset) | |
610 | + end | |
611 | + | |
612 | + def last_commit_for(ref, path = nil) | |
613 | + commits(ref, path, 1).first | |
614 | + end | |
615 | + | |
616 | + def commits_between(from, to) | |
617 | + Commit.commits_between(repo, from, to) | |
618 | + end | |
619 | + | |
620 | + def satellite | |
621 | + @satellite ||= Gitlab::Satellite::Satellite.new(self) | |
622 | + end | |
623 | + | |
624 | + def has_post_receive_file? | |
625 | + !!hook_file | |
626 | + end | |
627 | + | |
628 | + def valid_post_receive_file? | |
629 | + valid_hook_file == hook_file | |
630 | + end | |
631 | + | |
632 | + def valid_hook_file | |
633 | + @valid_hook_file ||= File.read(Rails.root.join('lib', 'hooks', 'post-receive')) | |
634 | + end | |
635 | + | |
636 | + def hook_file | |
637 | + @hook_file ||= begin | |
638 | + hook_path = File.join(path_to_repo, 'hooks', 'post-receive') | |
639 | + File.read(hook_path) if File.exists?(hook_path) | |
640 | + end | |
641 | + end | |
642 | + | |
643 | + # Returns an Array of branch names | |
644 | + def branch_names | |
645 | + repo.branches.collect(&:name).sort | |
646 | + end | |
647 | + | |
648 | + # Returns an Array of Branches | |
649 | + def branches | |
650 | + repo.branches.sort_by(&:name) | |
651 | + end | |
652 | + | |
653 | + # Returns an Array of tag names | |
654 | + def tag_names | |
655 | + repo.tags.collect(&:name).sort.reverse | |
656 | + end | |
657 | + | |
658 | + # Returns an Array of Tags | |
659 | + def tags | |
660 | + repo.tags.sort_by(&:name).reverse | |
661 | + end | |
662 | + | |
663 | + # Returns an Array of branch and tag names | |
664 | + def ref_names | |
665 | + [branch_names + tag_names].flatten | |
666 | + end | |
667 | + | |
668 | + def repo | |
669 | + @repo ||= Grit::Repo.new(path_to_repo) | |
670 | + end | |
671 | + | |
672 | + def url_to_repo | |
673 | + git_host.url_to_repo(path_with_namespace) | |
674 | + end | |
675 | + | |
676 | + def path_to_repo | |
677 | + File.join(Gitlab.config.gitolite.repos_path, "#{path_with_namespace}.git") | |
678 | + end | |
679 | + | |
680 | + def namespace_dir | |
681 | + namespace.try(:path) || '' | |
682 | + end | |
683 | + | |
684 | + def update_repository | |
685 | + git_host.update_repository(self) | |
686 | + end | |
687 | + | |
688 | + def destroy_repository | |
689 | + git_host.remove_repository(self) | |
690 | + end | |
691 | + | |
692 | + def repo_exists? | |
693 | + @repo_exists ||= (repo && !repo.branches.empty?) | |
694 | + rescue | |
695 | + @repo_exists = false | |
696 | + end | |
697 | + | |
698 | + def heads | |
699 | + @heads ||= repo.heads | |
700 | + end | |
701 | + | |
702 | + def tree(fcommit, path = nil) | |
703 | + fcommit = commit if fcommit == :head | |
704 | + tree = fcommit.tree | |
705 | + path ? (tree / path) : tree | |
706 | + end | |
707 | + | |
708 | + def open_branches | |
709 | + if protected_branches.empty? | |
710 | + self.repo.heads | |
711 | + else | |
712 | + pnames = protected_branches.map(&:name) | |
713 | + self.repo.heads.reject { |h| pnames.include?(h.name) } | |
714 | + end.sort_by(&:name) | |
715 | + end | |
716 | + | |
717 | + # Discovers the default branch based on the repository's available branches | |
718 | + # | |
719 | + # - If no branches are present, returns nil | |
720 | + # - If one branch is present, returns its name | |
721 | + # - If two or more branches are present, returns the one that has a name | |
722 | + # matching root_ref (default_branch or 'master' if default_branch is nil) | |
723 | + def discover_default_branch | |
724 | + if branch_names.length == 0 | |
725 | + nil | |
726 | + elsif branch_names.length == 1 | |
727 | + branch_names.first | |
728 | + else | |
729 | + branch_names.select { |v| v == root_ref }.first | |
730 | + end | |
731 | + end | |
732 | + | |
733 | + def has_commits? | |
734 | + !!commit | |
735 | + rescue Grit::NoSuchPathError | |
736 | + false | |
737 | + end | |
738 | + | |
739 | + def root_ref | |
740 | + default_branch || "master" | |
741 | + end | |
742 | + | |
743 | + def root_ref?(branch) | |
744 | + root_ref == branch | |
745 | + end | |
746 | + | |
747 | + # Archive Project to .tar.gz | |
748 | + # | |
749 | + # Already packed repo archives stored at | |
750 | + # app_root/tmp/repositories/project_name/project_name-commit-id.tag.gz | |
751 | + # | |
752 | + def archive_repo(ref) | |
753 | + ref = ref || self.root_ref | |
754 | + commit = self.commit(ref) | |
755 | + return nil unless commit | |
756 | + | |
757 | + # Build file path | |
758 | + file_name = self.path + "-" + commit.id.to_s + ".tar.gz" | |
759 | + storage_path = Rails.root.join("tmp", "repositories", self.path_with_namespace) | |
760 | + file_path = File.join(storage_path, file_name) | |
761 | + | |
762 | + # Put files into a directory before archiving | |
763 | + prefix = self.path + "/" | |
764 | + | |
765 | + # Create file if not exists | |
766 | + unless File.exists?(file_path) | |
767 | + FileUtils.mkdir_p storage_path | |
768 | + file = self.repo.archive_to_file(ref, prefix, file_path) | |
769 | + end | |
770 | + | |
771 | + file_path | |
772 | + end | |
773 | + | |
774 | + def ssh_url_to_repo | |
775 | + url_to_repo | |
776 | + end | |
777 | + | |
778 | + def http_url_to_repo | |
779 | + http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') | |
780 | + end | |
781 | + | |
782 | + # Check if current branch name is marked as protected in the system | |
783 | + def protected_branch? branch_name | |
784 | + protected_branches.map(&:name).include?(branch_name) | |
785 | + end | |
280 | 786 | end | ... | ... |
app/models/user.rb
... | ... | @@ -34,8 +34,6 @@ |
34 | 34 | # |
35 | 35 | |
36 | 36 | class User < ActiveRecord::Base |
37 | - include Account | |
38 | - | |
39 | 37 | devise :database_authenticatable, :token_authenticatable, :lockable, |
40 | 38 | :recoverable, :rememberable, :trackable, :validatable, :omniauthable |
41 | 39 | |
... | ... | @@ -192,4 +190,92 @@ class User < ActiveRecord::Base |
192 | 190 | def tm_in_personal_projects |
193 | 191 | personal_projects.users_projects.where(user_id: self.id) |
194 | 192 | end |
193 | + | |
194 | + # Returns a string for use as a Gitolite user identifier | |
195 | + # | |
196 | + # Note that Gitolite 2.x requires the following pattern for users: | |
197 | + # | |
198 | + # ^@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$ | |
199 | + def identifier | |
200 | + # Replace non-word chars with underscores, then make sure it starts with | |
201 | + # valid chars | |
202 | + email.gsub(/\W/, '_').gsub(/\A([\W\_])+/, '') | |
203 | + end | |
204 | + | |
205 | + def is_admin? | |
206 | + admin | |
207 | + end | |
208 | + | |
209 | + def require_ssh_key? | |
210 | + keys.count == 0 | |
211 | + end | |
212 | + | |
213 | + def can_create_project? | |
214 | + projects_limit > personal_projects.count | |
215 | + end | |
216 | + | |
217 | + def can_create_group? | |
218 | + is_admin? | |
219 | + end | |
220 | + | |
221 | + def abilities | |
222 | + @abilities ||= begin | |
223 | + abilities = Six.new | |
224 | + abilities << Ability | |
225 | + abilities | |
226 | + end | |
227 | + end | |
228 | + | |
229 | + def can? action, subject | |
230 | + abilities.allowed?(self, action, subject) | |
231 | + end | |
232 | + | |
233 | + def last_activity_project | |
234 | + projects.first | |
235 | + end | |
236 | + | |
237 | + def first_name | |
238 | + name.split.first unless name.blank? | |
239 | + end | |
240 | + | |
241 | + def cared_merge_requests | |
242 | + MergeRequest.where("author_id = :id or assignee_id = :id", id: self.id) | |
243 | + end | |
244 | + | |
245 | + # Remove user from all projects and | |
246 | + # set blocked attribute to true | |
247 | + def block | |
248 | + users_projects.find_each do |membership| | |
249 | + return false unless membership.destroy | |
250 | + end | |
251 | + | |
252 | + self.blocked = true | |
253 | + save | |
254 | + end | |
255 | + | |
256 | + def projects_limit_percent | |
257 | + return 100 if projects_limit.zero? | |
258 | + (personal_projects.count.to_f / projects_limit) * 100 | |
259 | + end | |
260 | + | |
261 | + def recent_push project_id = nil | |
262 | + # Get push events not earlier than 2 hours ago | |
263 | + events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours) | |
264 | + events = events.where(project_id: project_id) if project_id | |
265 | + | |
266 | + # Take only latest one | |
267 | + events = events.recent.limit(1).first | |
268 | + end | |
269 | + | |
270 | + def projects_sorted_by_activity | |
271 | + authorized_projects.sorted_by_activity | |
272 | + end | |
273 | + | |
274 | + def several_namespaces? | |
275 | + namespaces.size > 1 | |
276 | + end | |
277 | + | |
278 | + def namespace_id | |
279 | + namespace.try :id | |
280 | + end | |
195 | 281 | end | ... | ... |
app/roles/account.rb
... | ... | @@ -1,95 +0,0 @@ |
1 | -# == Account role | |
2 | -# | |
3 | -# Describe behaviour of User in application | |
4 | -# | |
5 | -# Used by User | |
6 | -# | |
7 | -module Account | |
8 | - # Returns a string for use as a Gitolite user identifier | |
9 | - # | |
10 | - # Note that Gitolite 2.x requires the following pattern for users: | |
11 | - # | |
12 | - # ^@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$ | |
13 | - def identifier | |
14 | - # Replace non-word chars with underscores, then make sure it starts with | |
15 | - # valid chars | |
16 | - email.gsub(/\W/, '_').gsub(/\A([\W\_])+/, '') | |
17 | - end | |
18 | - | |
19 | - def is_admin? | |
20 | - admin | |
21 | - end | |
22 | - | |
23 | - def require_ssh_key? | |
24 | - keys.count == 0 | |
25 | - end | |
26 | - | |
27 | - def can_create_project? | |
28 | - projects_limit > personal_projects.count | |
29 | - end | |
30 | - | |
31 | - def can_create_group? | |
32 | - is_admin? | |
33 | - end | |
34 | - | |
35 | - def abilities | |
36 | - @abilities ||= begin | |
37 | - abilities = Six.new | |
38 | - abilities << Ability | |
39 | - abilities | |
40 | - end | |
41 | - end | |
42 | - | |
43 | - def can? action, subject | |
44 | - abilities.allowed?(self, action, subject) | |
45 | - end | |
46 | - | |
47 | - def last_activity_project | |
48 | - projects.first | |
49 | - end | |
50 | - | |
51 | - def first_name | |
52 | - name.split.first unless name.blank? | |
53 | - end | |
54 | - | |
55 | - def cared_merge_requests | |
56 | - MergeRequest.where("author_id = :id or assignee_id = :id", id: self.id) | |
57 | - end | |
58 | - | |
59 | - # Remove user from all projects and | |
60 | - # set blocked attribute to true | |
61 | - def block | |
62 | - users_projects.find_each do |membership| | |
63 | - return false unless membership.destroy | |
64 | - end | |
65 | - | |
66 | - self.blocked = true | |
67 | - save | |
68 | - end | |
69 | - | |
70 | - def projects_limit_percent | |
71 | - return 100 if projects_limit.zero? | |
72 | - (personal_projects.count.to_f / projects_limit) * 100 | |
73 | - end | |
74 | - | |
75 | - def recent_push project_id = nil | |
76 | - # Get push events not earlier than 2 hours ago | |
77 | - events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours) | |
78 | - events = events.where(project_id: project_id) if project_id | |
79 | - | |
80 | - # Take only latest one | |
81 | - events = events.recent.limit(1).first | |
82 | - end | |
83 | - | |
84 | - def projects_sorted_by_activity | |
85 | - authorized_projects.sorted_by_activity | |
86 | - end | |
87 | - | |
88 | - def several_namespaces? | |
89 | - namespaces.size > 1 | |
90 | - end | |
91 | - | |
92 | - def namespace_id | |
93 | - namespace.try :id | |
94 | - end | |
95 | -end |
app/roles/authority.rb
... | ... | @@ -1,68 +0,0 @@ |
1 | -# == Authority role | |
2 | -# | |
3 | -# Control access to project repository based on users role in team | |
4 | -# | |
5 | -# Used by Project | |
6 | -# | |
7 | -module Authority | |
8 | - # Compatible with all access rights | |
9 | - # Should be rewrited for new access rights | |
10 | - def add_access(user, *access) | |
11 | - access = if access.include?(:admin) | |
12 | - { project_access: UsersProject::MASTER } | |
13 | - elsif access.include?(:write) | |
14 | - { project_access: UsersProject::DEVELOPER } | |
15 | - else | |
16 | - { project_access: UsersProject::REPORTER } | |
17 | - end | |
18 | - opts = { user: user } | |
19 | - opts.merge!(access) | |
20 | - users_projects.create(opts) | |
21 | - end | |
22 | - | |
23 | - def reset_access(user) | |
24 | - users_projects.where(project_id: self.id, user_id: user.id).destroy if self.id | |
25 | - end | |
26 | - | |
27 | - def repository_readers | |
28 | - repository_members[UsersProject::REPORTER] | |
29 | - end | |
30 | - | |
31 | - def repository_writers | |
32 | - repository_members[UsersProject::DEVELOPER] | |
33 | - end | |
34 | - | |
35 | - def repository_masters | |
36 | - repository_members[UsersProject::MASTER] | |
37 | - end | |
38 | - | |
39 | - def repository_members | |
40 | - keys = Hash.new {|h,k| h[k] = [] } | |
41 | - UsersProject.select("keys.identifier, project_access"). | |
42 | - joins(user: :keys).where(project_id: id). | |
43 | - each {|row| keys[row.project_access] << [row.identifier] } | |
44 | - | |
45 | - keys[UsersProject::REPORTER] += deploy_keys.pluck(:identifier) | |
46 | - keys | |
47 | - end | |
48 | - | |
49 | - def allow_read_for?(user) | |
50 | - !users_projects.where(user_id: user.id).empty? | |
51 | - end | |
52 | - | |
53 | - def guest_access_for?(user) | |
54 | - !users_projects.where(user_id: user.id).empty? | |
55 | - end | |
56 | - | |
57 | - def report_access_for?(user) | |
58 | - !users_projects.where(user_id: user.id, project_access: [UsersProject::REPORTER, UsersProject::DEVELOPER, UsersProject::MASTER]).empty? | |
59 | - end | |
60 | - | |
61 | - def dev_access_for?(user) | |
62 | - !users_projects.where(user_id: user.id, project_access: [UsersProject::DEVELOPER, UsersProject::MASTER]).empty? | |
63 | - end | |
64 | - | |
65 | - def master_access_for?(user) | |
66 | - !users_projects.where(user_id: user.id, project_access: [UsersProject::MASTER]).empty? | |
67 | - end | |
68 | -end |
app/roles/git_host.rb
app/roles/issue_commonality.rb
... | ... | @@ -1,72 +0,0 @@ |
1 | -# == IssueCommonality role | |
2 | -# | |
3 | -# Contains common functionality shared between Issues and MergeRequests | |
4 | -# | |
5 | -# Used by Issue, MergeRequest | |
6 | -# | |
7 | -module IssueCommonality | |
8 | - extend ActiveSupport::Concern | |
9 | - | |
10 | - included do | |
11 | - belongs_to :project | |
12 | - belongs_to :author, class_name: "User" | |
13 | - belongs_to :assignee, class_name: "User" | |
14 | - belongs_to :milestone | |
15 | - has_many :notes, as: :noteable, dependent: :destroy | |
16 | - | |
17 | - validates :project, presence: true | |
18 | - validates :author, presence: true | |
19 | - validates :title, presence: true, length: { within: 0..255 } | |
20 | - validates :closed, inclusion: { in: [true, false] } | |
21 | - | |
22 | - scope :opened, where(closed: false) | |
23 | - scope :closed, where(closed: true) | |
24 | - scope :of_group, ->(group) { where(project_id: group.project_ids) } | |
25 | - scope :assigned, ->(u) { where(assignee_id: u.id)} | |
26 | - scope :recent, order("created_at DESC") | |
27 | - | |
28 | - delegate :name, | |
29 | - :email, | |
30 | - to: :author, | |
31 | - prefix: true | |
32 | - | |
33 | - delegate :name, | |
34 | - :email, | |
35 | - to: :assignee, | |
36 | - allow_nil: true, | |
37 | - prefix: true | |
38 | - | |
39 | - attr_accessor :author_id_of_changes | |
40 | - end | |
41 | - | |
42 | - module ClassMethods | |
43 | - def search(query) | |
44 | - where("title like :query", query: "%#{query}%") | |
45 | - end | |
46 | - end | |
47 | - | |
48 | - def today? | |
49 | - Date.today == created_at.to_date | |
50 | - end | |
51 | - | |
52 | - def new? | |
53 | - today? && created_at == updated_at | |
54 | - end | |
55 | - | |
56 | - def is_assigned? | |
57 | - !!assignee_id | |
58 | - end | |
59 | - | |
60 | - def is_being_reassigned? | |
61 | - assignee_id_changed? | |
62 | - end | |
63 | - | |
64 | - def is_being_closed? | |
65 | - closed_changed? && closed | |
66 | - end | |
67 | - | |
68 | - def is_being_reopened? | |
69 | - closed_changed? && !closed | |
70 | - end | |
71 | - | |
72 | -end |
app/roles/namespaced_project.rb
... | ... | @@ -1,60 +0,0 @@ |
1 | -# == NamespacedProject role | |
2 | -# | |
3 | -# Provides extra functionality for Project related to namespaces like: | |
4 | -# - transfer project between namespaces | |
5 | -# - name, path including namespece | |
6 | -# - project owner based on namespace | |
7 | -# | |
8 | -# Used by Project | |
9 | -# | |
10 | -module NamespacedProject | |
11 | - def transfer(new_namespace) | |
12 | - Project.transaction do | |
13 | - old_namespace = namespace | |
14 | - self.namespace = new_namespace | |
15 | - | |
16 | - old_dir = old_namespace.try(:path) || '' | |
17 | - new_dir = new_namespace.try(:path) || '' | |
18 | - | |
19 | - old_repo = if old_dir.present? | |
20 | - File.join(old_dir, self.path) | |
21 | - else | |
22 | - self.path | |
23 | - end | |
24 | - | |
25 | - if Project.where(path: self.path, namespace_id: new_namespace.try(:id)).present? | |
26 | - raise TransferError.new("Project with same path in target namespace already exists") | |
27 | - end | |
28 | - | |
29 | - Gitlab::ProjectMover.new(self, old_dir, new_dir).execute | |
30 | - | |
31 | - git_host.move_repository(old_repo, self) | |
32 | - | |
33 | - save! | |
34 | - end | |
35 | - rescue Gitlab::ProjectMover::ProjectMoveError => ex | |
36 | - raise Project::TransferError.new(ex.message) | |
37 | - end | |
38 | - | |
39 | - def name_with_namespace | |
40 | - @name_with_namespace ||= begin | |
41 | - if namespace | |
42 | - namespace.human_name + " / " + name | |
43 | - else | |
44 | - name | |
45 | - end | |
46 | - end | |
47 | - end | |
48 | - | |
49 | - def namespace_owner | |
50 | - namespace.try(:owner) | |
51 | - end | |
52 | - | |
53 | - def path_with_namespace | |
54 | - if namespace | |
55 | - namespace.path + '/' + path | |
56 | - else | |
57 | - path | |
58 | - end | |
59 | - end | |
60 | -end |
app/roles/note_event.rb
... | ... | @@ -1,43 +0,0 @@ |
1 | -# == NoteEvent role | |
2 | -# | |
3 | -# Extends Event model functionality by providing extra methods related to comment events | |
4 | -# | |
5 | -# Used by Event | |
6 | -# | |
7 | -module NoteEvent | |
8 | - def note_commit_id | |
9 | - target.commit_id | |
10 | - end | |
11 | - | |
12 | - def note_short_commit_id | |
13 | - note_commit_id[0..8] | |
14 | - end | |
15 | - | |
16 | - def note_commit? | |
17 | - target.noteable_type == "Commit" | |
18 | - end | |
19 | - | |
20 | - def note_target | |
21 | - target.noteable | |
22 | - end | |
23 | - | |
24 | - def note_target_id | |
25 | - if note_commit? | |
26 | - target.commit_id | |
27 | - else | |
28 | - target.noteable_id.to_s | |
29 | - end | |
30 | - end | |
31 | - | |
32 | - def wall_note? | |
33 | - target.noteable_type.blank? | |
34 | - end | |
35 | - | |
36 | - def note_target_type | |
37 | - if target.noteable_type.present? | |
38 | - target.noteable_type.titleize | |
39 | - else | |
40 | - "Wall" | |
41 | - end.downcase | |
42 | - end | |
43 | -end |
app/roles/push_event.rb
... | ... | @@ -1,106 +0,0 @@ |
1 | -# == PushEvent role | |
2 | -# | |
3 | -# Extends Event model functionality by providing extra methods related to push events | |
4 | -# | |
5 | -# Used by Event | |
6 | -# | |
7 | -module PushEvent | |
8 | - def valid_push? | |
9 | - data[:ref] | |
10 | - rescue => ex | |
11 | - false | |
12 | - end | |
13 | - | |
14 | - def tag? | |
15 | - data[:ref]["refs/tags"] | |
16 | - end | |
17 | - | |
18 | - def branch? | |
19 | - data[:ref]["refs/heads"] | |
20 | - end | |
21 | - | |
22 | - def new_branch? | |
23 | - commit_from =~ /^00000/ | |
24 | - end | |
25 | - | |
26 | - def new_ref? | |
27 | - commit_from =~ /^00000/ | |
28 | - end | |
29 | - | |
30 | - def rm_ref? | |
31 | - commit_to =~ /^00000/ | |
32 | - end | |
33 | - | |
34 | - def md_ref? | |
35 | - !(rm_ref? || new_ref?) | |
36 | - end | |
37 | - | |
38 | - def commit_from | |
39 | - data[:before] | |
40 | - end | |
41 | - | |
42 | - def commit_to | |
43 | - data[:after] | |
44 | - end | |
45 | - | |
46 | - def ref_name | |
47 | - if tag? | |
48 | - tag_name | |
49 | - else | |
50 | - branch_name | |
51 | - end | |
52 | - end | |
53 | - | |
54 | - def branch_name | |
55 | - @branch_name ||= data[:ref].gsub("refs/heads/", "") | |
56 | - end | |
57 | - | |
58 | - def tag_name | |
59 | - @tag_name ||= data[:ref].gsub("refs/tags/", "") | |
60 | - end | |
61 | - | |
62 | - # Max 20 commits from push DESC | |
63 | - def commits | |
64 | - @commits ||= data[:commits].map { |commit| project.commit(commit[:id]) }.reverse | |
65 | - end | |
66 | - | |
67 | - def commits_count | |
68 | - data[:total_commits_count] || commits.count || 0 | |
69 | - end | |
70 | - | |
71 | - def ref_type | |
72 | - tag? ? "tag" : "branch" | |
73 | - end | |
74 | - | |
75 | - def push_action_name | |
76 | - if new_ref? | |
77 | - "pushed new" | |
78 | - elsif rm_ref? | |
79 | - "deleted" | |
80 | - else | |
81 | - "pushed to" | |
82 | - end | |
83 | - end | |
84 | - | |
85 | - def parent_commit | |
86 | - project.commit(commit_from) | |
87 | - rescue => ex | |
88 | - nil | |
89 | - end | |
90 | - | |
91 | - def last_commit | |
92 | - project.commit(commit_to) | |
93 | - rescue => ex | |
94 | - nil | |
95 | - end | |
96 | - | |
97 | - def push_with_commits? | |
98 | - md_ref? && commits.any? && parent_commit && last_commit | |
99 | - rescue Grit::NoSuchPathError | |
100 | - false | |
101 | - end | |
102 | - | |
103 | - def last_push_to_non_root? | |
104 | - branch? && project.default_branch != branch_name | |
105 | - end | |
106 | -end |
app/roles/push_observer.rb
... | ... | @@ -1,149 +0,0 @@ |
1 | -# == PushObserver role | |
2 | -# | |
3 | -# Includes methods to be triggered on push to project repository. | |
4 | -# | |
5 | -# | |
6 | -# Used by Project | |
7 | -# Triggered by PostReceive job | |
8 | -# | |
9 | -module PushObserver | |
10 | - # This method will be called after each post receive and only if the provided | |
11 | - # user is present in GitLab. | |
12 | - # | |
13 | - # All callbacks for post receive should be placed here. | |
14 | - def trigger_post_receive(oldrev, newrev, ref, user) | |
15 | - data = post_receive_data(oldrev, newrev, ref, user) | |
16 | - | |
17 | - # Create push event | |
18 | - self.observe_push(data) | |
19 | - | |
20 | - if push_to_branch? ref, oldrev | |
21 | - # Close merged MR | |
22 | - self.update_merge_requests(oldrev, newrev, ref, user) | |
23 | - | |
24 | - # Execute web hooks | |
25 | - self.execute_hooks(data.dup) | |
26 | - | |
27 | - # Execute project services | |
28 | - self.execute_services(data.dup) | |
29 | - end | |
30 | - | |
31 | - # Create satellite | |
32 | - self.satellite.create unless self.satellite.exists? | |
33 | - | |
34 | - # Discover the default branch, but only if it hasn't already been set to | |
35 | - # something else | |
36 | - if default_branch.nil? | |
37 | - update_attributes(default_branch: discover_default_branch) | |
38 | - end | |
39 | - end | |
40 | - | |
41 | - def push_to_branch? ref, oldrev | |
42 | - ref_parts = ref.split('/') | |
43 | - | |
44 | - # Return if this is not a push to a branch (e.g. new commits) | |
45 | - !(ref_parts[1] !~ /heads/ || oldrev == "00000000000000000000000000000000") | |
46 | - end | |
47 | - | |
48 | - def observe_push(data) | |
49 | - Event.create( | |
50 | - project: self, | |
51 | - action: Event::Pushed, | |
52 | - data: data, | |
53 | - author_id: data[:user_id] | |
54 | - ) | |
55 | - end | |
56 | - | |
57 | - def execute_hooks(data) | |
58 | - hooks.each { |hook| hook.execute(data) } | |
59 | - end | |
60 | - | |
61 | - def execute_services(data) | |
62 | - services.each do |service| | |
63 | - | |
64 | - # Call service hook only if it is active | |
65 | - service.execute(data) if service.active | |
66 | - end | |
67 | - end | |
68 | - | |
69 | - # Produce a hash of post-receive data | |
70 | - # | |
71 | - # data = { | |
72 | - # before: String, | |
73 | - # after: String, | |
74 | - # ref: String, | |
75 | - # user_id: String, | |
76 | - # user_name: String, | |
77 | - # repository: { | |
78 | - # name: String, | |
79 | - # url: String, | |
80 | - # description: String, | |
81 | - # homepage: String, | |
82 | - # }, | |
83 | - # commits: Array, | |
84 | - # total_commits_count: Fixnum | |
85 | - # } | |
86 | - # | |
87 | - def post_receive_data(oldrev, newrev, ref, user) | |
88 | - | |
89 | - push_commits = commits_between(oldrev, newrev) | |
90 | - | |
91 | - # Total commits count | |
92 | - push_commits_count = push_commits.size | |
93 | - | |
94 | - # Get latest 20 commits ASC | |
95 | - push_commits_limited = push_commits.last(20) | |
96 | - | |
97 | - # Hash to be passed as post_receive_data | |
98 | - data = { | |
99 | - before: oldrev, | |
100 | - after: newrev, | |
101 | - ref: ref, | |
102 | - user_id: user.id, | |
103 | - user_name: user.name, | |
104 | - repository: { | |
105 | - name: name, | |
106 | - url: url_to_repo, | |
107 | - description: description, | |
108 | - homepage: web_url, | |
109 | - }, | |
110 | - commits: [], | |
111 | - total_commits_count: push_commits_count | |
112 | - } | |
113 | - | |
114 | - # For perfomance purposes maximum 20 latest commits | |
115 | - # will be passed as post receive hook data. | |
116 | - # | |
117 | - push_commits_limited.each do |commit| | |
118 | - data[:commits] << { | |
119 | - id: commit.id, | |
120 | - message: commit.safe_message, | |
121 | - timestamp: commit.date.xmlschema, | |
122 | - url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{commit.id}", | |
123 | - author: { | |
124 | - name: commit.author_name, | |
125 | - email: commit.author_email | |
126 | - } | |
127 | - } | |
128 | - end | |
129 | - | |
130 | - data | |
131 | - end | |
132 | - | |
133 | - def update_merge_requests(oldrev, newrev, ref, user) | |
134 | - return true unless ref =~ /heads/ | |
135 | - branch_name = ref.gsub("refs/heads/", "") | |
136 | - c_ids = self.commits_between(oldrev, newrev).map(&:id) | |
137 | - | |
138 | - # Update code for merge requests | |
139 | - mrs = self.merge_requests.opened.find_all_by_branch(branch_name).all | |
140 | - mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked } | |
141 | - | |
142 | - # Close merge requests | |
143 | - mrs = self.merge_requests.opened.where(target_branch: branch_name).all | |
144 | - mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) } | |
145 | - mrs.each { |merge_request| merge_request.merge!(user.id) } | |
146 | - | |
147 | - true | |
148 | - end | |
149 | -end |
app/roles/repository.rb
... | ... | @@ -1,216 +0,0 @@ |
1 | -# == Repository role | |
2 | -# | |
3 | -# Provides access to git repository resources like commits, branches etc.. | |
4 | -# Allows you to manage repository via gitolite interface(git_host) | |
5 | -# | |
6 | -# Used by Project | |
7 | -# | |
8 | -module Repository | |
9 | - include GitHost | |
10 | - | |
11 | - def valid_repo? | |
12 | - repo | |
13 | - rescue | |
14 | - errors.add(:path, "Invalid repository path") | |
15 | - false | |
16 | - end | |
17 | - | |
18 | - def empty_repo? | |
19 | - !repo_exists? || !has_commits? | |
20 | - end | |
21 | - | |
22 | - def commit(commit_id = nil) | |
23 | - Commit.find_or_first(repo, commit_id, root_ref) | |
24 | - end | |
25 | - | |
26 | - def fresh_commits(n = 10) | |
27 | - Commit.fresh_commits(repo, n) | |
28 | - end | |
29 | - | |
30 | - def commits_with_refs(n = 20) | |
31 | - Commit.commits_with_refs(repo, n) | |
32 | - end | |
33 | - | |
34 | - def commits_since(date) | |
35 | - Commit.commits_since(repo, date) | |
36 | - end | |
37 | - | |
38 | - def commits(ref, path = nil, limit = nil, offset = nil) | |
39 | - Commit.commits(repo, ref, path, limit, offset) | |
40 | - end | |
41 | - | |
42 | - def last_commit_for(ref, path = nil) | |
43 | - commits(ref, path, 1).first | |
44 | - end | |
45 | - | |
46 | - def commits_between(from, to) | |
47 | - Commit.commits_between(repo, from, to) | |
48 | - end | |
49 | - | |
50 | - def satellite | |
51 | - @satellite ||= Gitlab::Satellite::Satellite.new(self) | |
52 | - end | |
53 | - | |
54 | - def has_post_receive_file? | |
55 | - !!hook_file | |
56 | - end | |
57 | - | |
58 | - def valid_post_receive_file? | |
59 | - valid_hook_file == hook_file | |
60 | - end | |
61 | - | |
62 | - def valid_hook_file | |
63 | - @valid_hook_file ||= File.read(Rails.root.join('lib', 'hooks', 'post-receive')) | |
64 | - end | |
65 | - | |
66 | - def hook_file | |
67 | - @hook_file ||= begin | |
68 | - hook_path = File.join(path_to_repo, 'hooks', 'post-receive') | |
69 | - File.read(hook_path) if File.exists?(hook_path) | |
70 | - end | |
71 | - end | |
72 | - | |
73 | - # Returns an Array of branch names | |
74 | - def branch_names | |
75 | - repo.branches.collect(&:name).sort | |
76 | - end | |
77 | - | |
78 | - # Returns an Array of Branches | |
79 | - def branches | |
80 | - repo.branches.sort_by(&:name) | |
81 | - end | |
82 | - | |
83 | - # Returns an Array of tag names | |
84 | - def tag_names | |
85 | - repo.tags.collect(&:name).sort.reverse | |
86 | - end | |
87 | - | |
88 | - # Returns an Array of Tags | |
89 | - def tags | |
90 | - repo.tags.sort_by(&:name).reverse | |
91 | - end | |
92 | - | |
93 | - # Returns an Array of branch and tag names | |
94 | - def ref_names | |
95 | - [branch_names + tag_names].flatten | |
96 | - end | |
97 | - | |
98 | - def repo | |
99 | - @repo ||= Grit::Repo.new(path_to_repo) | |
100 | - end | |
101 | - | |
102 | - def url_to_repo | |
103 | - git_host.url_to_repo(path_with_namespace) | |
104 | - end | |
105 | - | |
106 | - def path_to_repo | |
107 | - File.join(Gitlab.config.gitolite.repos_path, "#{path_with_namespace}.git") | |
108 | - end | |
109 | - | |
110 | - def namespace_dir | |
111 | - namespace.try(:path) || '' | |
112 | - end | |
113 | - | |
114 | - def update_repository | |
115 | - git_host.update_repository(self) | |
116 | - end | |
117 | - | |
118 | - def destroy_repository | |
119 | - git_host.remove_repository(self) | |
120 | - end | |
121 | - | |
122 | - def repo_exists? | |
123 | - @repo_exists ||= (repo && !repo.branches.empty?) | |
124 | - rescue | |
125 | - @repo_exists = false | |
126 | - end | |
127 | - | |
128 | - def heads | |
129 | - @heads ||= repo.heads | |
130 | - end | |
131 | - | |
132 | - def tree(fcommit, path = nil) | |
133 | - fcommit = commit if fcommit == :head | |
134 | - tree = fcommit.tree | |
135 | - path ? (tree / path) : tree | |
136 | - end | |
137 | - | |
138 | - def open_branches | |
139 | - if protected_branches.empty? | |
140 | - self.repo.heads | |
141 | - else | |
142 | - pnames = protected_branches.map(&:name) | |
143 | - self.repo.heads.reject { |h| pnames.include?(h.name) } | |
144 | - end.sort_by(&:name) | |
145 | - end | |
146 | - | |
147 | - # Discovers the default branch based on the repository's available branches | |
148 | - # | |
149 | - # - If no branches are present, returns nil | |
150 | - # - If one branch is present, returns its name | |
151 | - # - If two or more branches are present, returns the one that has a name | |
152 | - # matching root_ref (default_branch or 'master' if default_branch is nil) | |
153 | - def discover_default_branch | |
154 | - if branch_names.length == 0 | |
155 | - nil | |
156 | - elsif branch_names.length == 1 | |
157 | - branch_names.first | |
158 | - else | |
159 | - branch_names.select { |v| v == root_ref }.first | |
160 | - end | |
161 | - end | |
162 | - | |
163 | - def has_commits? | |
164 | - !!commit | |
165 | - rescue Grit::NoSuchPathError | |
166 | - false | |
167 | - end | |
168 | - | |
169 | - def root_ref | |
170 | - default_branch || "master" | |
171 | - end | |
172 | - | |
173 | - def root_ref?(branch) | |
174 | - root_ref == branch | |
175 | - end | |
176 | - | |
177 | - # Archive Project to .tar.gz | |
178 | - # | |
179 | - # Already packed repo archives stored at | |
180 | - # app_root/tmp/repositories/project_name/project_name-commit-id.tag.gz | |
181 | - # | |
182 | - def archive_repo(ref) | |
183 | - ref = ref || self.root_ref | |
184 | - commit = self.commit(ref) | |
185 | - return nil unless commit | |
186 | - | |
187 | - # Build file path | |
188 | - file_name = self.path + "-" + commit.id.to_s + ".tar.gz" | |
189 | - storage_path = Rails.root.join("tmp", "repositories", self.path_with_namespace) | |
190 | - file_path = File.join(storage_path, file_name) | |
191 | - | |
192 | - # Put files into a directory before archiving | |
193 | - prefix = self.path + "/" | |
194 | - | |
195 | - # Create file if not exists | |
196 | - unless File.exists?(file_path) | |
197 | - FileUtils.mkdir_p storage_path | |
198 | - file = self.repo.archive_to_file(ref, prefix, file_path) | |
199 | - end | |
200 | - | |
201 | - file_path | |
202 | - end | |
203 | - | |
204 | - def ssh_url_to_repo | |
205 | - url_to_repo | |
206 | - end | |
207 | - | |
208 | - def http_url_to_repo | |
209 | - http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') | |
210 | - end | |
211 | - | |
212 | - # Check if current branch name is marked as protected in the system | |
213 | - def protected_branch? branch_name | |
214 | - protected_branches.map(&:name).include?(branch_name) | |
215 | - end | |
216 | -end |
app/roles/static_model.rb
... | ... | @@ -1,47 +0,0 @@ |
1 | -# Provides an ActiveRecord-like interface to a model whose data is not persisted to a database. | |
2 | -module StaticModel | |
3 | - extend ActiveSupport::Concern | |
4 | - | |
5 | - module ClassMethods | |
6 | - # Used by ActiveRecord's polymorphic association to set object_id | |
7 | - def primary_key | |
8 | - 'id' | |
9 | - end | |
10 | - | |
11 | - # Used by ActiveRecord's polymorphic association to set object_type | |
12 | - def base_class | |
13 | - self | |
14 | - end | |
15 | - end | |
16 | - | |
17 | - # Used by AR for fetching attributes | |
18 | - # | |
19 | - # Pass it along if we respond to it. | |
20 | - def [](key) | |
21 | - send(key) if respond_to?(key) | |
22 | - end | |
23 | - | |
24 | - def to_param | |
25 | - id | |
26 | - end | |
27 | - | |
28 | - def new_record? | |
29 | - false | |
30 | - end | |
31 | - | |
32 | - def persisted? | |
33 | - false | |
34 | - end | |
35 | - | |
36 | - def destroyed? | |
37 | - false | |
38 | - end | |
39 | - | |
40 | - def ==(other) | |
41 | - if other.is_a? StaticModel | |
42 | - id == other.id | |
43 | - else | |
44 | - super | |
45 | - end | |
46 | - end | |
47 | -end |
app/roles/team.rb
... | ... | @@ -1,63 +0,0 @@ |
1 | -# == Team role | |
2 | -# | |
3 | -# Provides functionality to manage project team | |
4 | -# - add user/users to project | |
5 | -# - update existing membership | |
6 | -# - remove users from project team | |
7 | -# | |
8 | -# Used by Project | |
9 | -# | |
10 | -module Team | |
11 | - def team_member_by_name_or_email(name = nil, email = nil) | |
12 | - user = users.where("name like ? or email like ?", name, email).first | |
13 | - users_projects.where(user: user) if user | |
14 | - end | |
15 | - | |
16 | - # Get Team Member record by user id | |
17 | - def team_member_by_id(user_id) | |
18 | - users_projects.find_by_user_id(user_id) | |
19 | - end | |
20 | - | |
21 | - # Add user to project | |
22 | - # with passed access role | |
23 | - def add_user_to_team(user, access_role) | |
24 | - add_user_id_to_team(user.id, access_role) | |
25 | - end | |
26 | - | |
27 | - # Add multiple users to project | |
28 | - # with same access role | |
29 | - def add_users_to_team(users, access_role) | |
30 | - add_users_ids_to_team(users.map(&:id), access_role) | |
31 | - end | |
32 | - | |
33 | - # Add user to project | |
34 | - # with passed access role by user id | |
35 | - def add_user_id_to_team(user_id, access_role) | |
36 | - users_projects.create( | |
37 | - user_id: user_id, | |
38 | - project_access: access_role | |
39 | - ) | |
40 | - end | |
41 | - | |
42 | - # Add multiple users to project | |
43 | - # with same access role by user ids | |
44 | - def add_users_ids_to_team(users_ids, access_role) | |
45 | - UsersProject.bulk_import(self, users_ids, access_role) | |
46 | - end | |
47 | - | |
48 | - # Update multiple project users | |
49 | - # to same access role by user ids | |
50 | - def update_users_ids_to_role(users_ids, access_role) | |
51 | - UsersProject.bulk_update(self, users_ids, access_role) | |
52 | - end | |
53 | - | |
54 | - # Delete multiple users from project by user ids | |
55 | - def delete_users_ids_from_team(users_ids) | |
56 | - UsersProject.bulk_delete(self, users_ids) | |
57 | - end | |
58 | - | |
59 | - # Remove all users from project team | |
60 | - def truncate_team | |
61 | - UsersProject.truncate_team(self) | |
62 | - end | |
63 | -end |
app/roles/votes.rb
... | ... | @@ -1,39 +0,0 @@ |
1 | -# == Votes role | |
2 | -# | |
3 | -# Provides functionality to upvote/downvote entity | |
4 | -# based on +1 and -1 notes | |
5 | -# | |
6 | -# Used for Issue and Merge Request | |
7 | -# | |
8 | -module Votes | |
9 | - # Return the number of +1 comments (upvotes) | |
10 | - def upvotes | |
11 | - notes.select(&:upvote?).size | |
12 | - end | |
13 | - | |
14 | - def upvotes_in_percent | |
15 | - if votes_count.zero? | |
16 | - 0 | |
17 | - else | |
18 | - 100.0 / votes_count * upvotes | |
19 | - end | |
20 | - end | |
21 | - | |
22 | - # Return the number of -1 comments (downvotes) | |
23 | - def downvotes | |
24 | - notes.select(&:downvote?).size | |
25 | - end | |
26 | - | |
27 | - def downvotes_in_percent | |
28 | - if votes_count.zero? | |
29 | - 0 | |
30 | - else | |
31 | - 100.0 - upvotes_in_percent | |
32 | - end | |
33 | - end | |
34 | - | |
35 | - # Return the total number of votes | |
36 | - def votes_count | |
37 | - upvotes + downvotes | |
38 | - end | |
39 | -end |
... | ... | @@ -0,0 +1,71 @@ |
1 | +# == IssueCommonality role | |
2 | +# | |
3 | +# Contains common functionality shared between Issues and MergeRequests | |
4 | +# | |
5 | +# Used by Issue, MergeRequest | |
6 | +# | |
7 | +module IssueCommonality | |
8 | + extend ActiveSupport::Concern | |
9 | + | |
10 | + included do | |
11 | + belongs_to :project | |
12 | + belongs_to :author, class_name: "User" | |
13 | + belongs_to :assignee, class_name: "User" | |
14 | + belongs_to :milestone | |
15 | + has_many :notes, as: :noteable, dependent: :destroy | |
16 | + | |
17 | + validates :project, presence: true | |
18 | + validates :author, presence: true | |
19 | + validates :title, presence: true, length: { within: 0..255 } | |
20 | + validates :closed, inclusion: { in: [true, false] } | |
21 | + | |
22 | + scope :opened, where(closed: false) | |
23 | + scope :closed, where(closed: true) | |
24 | + scope :of_group, ->(group) { where(project_id: group.project_ids) } | |
25 | + scope :assigned, ->(u) { where(assignee_id: u.id)} | |
26 | + scope :recent, order("created_at DESC") | |
27 | + | |
28 | + delegate :name, | |
29 | + :email, | |
30 | + to: :author, | |
31 | + prefix: true | |
32 | + | |
33 | + delegate :name, | |
34 | + :email, | |
35 | + to: :assignee, | |
36 | + allow_nil: true, | |
37 | + prefix: true | |
38 | + | |
39 | + attr_accessor :author_id_of_changes | |
40 | + end | |
41 | + | |
42 | + module ClassMethods | |
43 | + def search(query) | |
44 | + where("title like :query", query: "%#{query}%") | |
45 | + end | |
46 | + end | |
47 | + | |
48 | + def today? | |
49 | + Date.today == created_at.to_date | |
50 | + end | |
51 | + | |
52 | + def new? | |
53 | + today? && created_at == updated_at | |
54 | + end | |
55 | + | |
56 | + def is_assigned? | |
57 | + !!assignee_id | |
58 | + end | |
59 | + | |
60 | + def is_being_reassigned? | |
61 | + assignee_id_changed? | |
62 | + end | |
63 | + | |
64 | + def is_being_closed? | |
65 | + closed_changed? && closed | |
66 | + end | |
67 | + | |
68 | + def is_being_reopened? | |
69 | + closed_changed? && !closed | |
70 | + end | |
71 | +end | ... | ... |
... | ... | @@ -0,0 +1,47 @@ |
1 | +# Provides an ActiveRecord-like interface to a model whose data is not persisted to a database. | |
2 | +module StaticModel | |
3 | + extend ActiveSupport::Concern | |
4 | + | |
5 | + module ClassMethods | |
6 | + # Used by ActiveRecord's polymorphic association to set object_id | |
7 | + def primary_key | |
8 | + 'id' | |
9 | + end | |
10 | + | |
11 | + # Used by ActiveRecord's polymorphic association to set object_type | |
12 | + def base_class | |
13 | + self | |
14 | + end | |
15 | + end | |
16 | + | |
17 | + # Used by AR for fetching attributes | |
18 | + # | |
19 | + # Pass it along if we respond to it. | |
20 | + def [](key) | |
21 | + send(key) if respond_to?(key) | |
22 | + end | |
23 | + | |
24 | + def to_param | |
25 | + id | |
26 | + end | |
27 | + | |
28 | + def new_record? | |
29 | + false | |
30 | + end | |
31 | + | |
32 | + def persisted? | |
33 | + false | |
34 | + end | |
35 | + | |
36 | + def destroyed? | |
37 | + false | |
38 | + end | |
39 | + | |
40 | + def ==(other) | |
41 | + if other.is_a? StaticModel | |
42 | + id == other.id | |
43 | + else | |
44 | + super | |
45 | + end | |
46 | + end | |
47 | +end | ... | ... |
... | ... | @@ -0,0 +1,39 @@ |
1 | +# == Votes role | |
2 | +# | |
3 | +# Provides functionality to upvote/downvote entity | |
4 | +# based on +1 and -1 notes | |
5 | +# | |
6 | +# Used for Issue and Merge Request | |
7 | +# | |
8 | +module Votes | |
9 | + # Return the number of +1 comments (upvotes) | |
10 | + def upvotes | |
11 | + notes.select(&:upvote?).size | |
12 | + end | |
13 | + | |
14 | + def upvotes_in_percent | |
15 | + if votes_count.zero? | |
16 | + 0 | |
17 | + else | |
18 | + 100.0 / votes_count * upvotes | |
19 | + end | |
20 | + end | |
21 | + | |
22 | + # Return the number of -1 comments (downvotes) | |
23 | + def downvotes | |
24 | + notes.select(&:downvote?).size | |
25 | + end | |
26 | + | |
27 | + def downvotes_in_percent | |
28 | + if votes_count.zero? | |
29 | + 0 | |
30 | + else | |
31 | + 100.0 - upvotes_in_percent | |
32 | + end | |
33 | + end | |
34 | + | |
35 | + # Return the total number of votes | |
36 | + def votes_count | |
37 | + upvotes + downvotes | |
38 | + end | |
39 | +end | ... | ... |
... | ... | @@ -0,0 +1,70 @@ |
1 | +require 'spec_helper' | |
2 | + | |
3 | +describe Issue, "IssueCommonality" do | |
4 | + let(:issue) { create(:issue) } | |
5 | + | |
6 | + describe "Associations" do | |
7 | + it { should belong_to(:project) } | |
8 | + it { should belong_to(:author) } | |
9 | + it { should belong_to(:assignee) } | |
10 | + it { should have_many(:notes).dependent(:destroy) } | |
11 | + end | |
12 | + | |
13 | + describe "Validation" do | |
14 | + it { should validate_presence_of(:project) } | |
15 | + it { should validate_presence_of(:author) } | |
16 | + it { should validate_presence_of(:title) } | |
17 | + it { should ensure_length_of(:title).is_at_least(0).is_at_most(255) } | |
18 | + it { should ensure_inclusion_of(:closed).in_array([true, false]) } | |
19 | + end | |
20 | + | |
21 | + describe "Scope" do | |
22 | + it { described_class.should respond_to(:opened) } | |
23 | + it { described_class.should respond_to(:closed) } | |
24 | + it { described_class.should respond_to(:assigned) } | |
25 | + end | |
26 | + | |
27 | + it "has an :author_id_of_changes accessor" do | |
28 | + issue.should respond_to(:author_id_of_changes) | |
29 | + issue.should respond_to(:author_id_of_changes=) | |
30 | + end | |
31 | + | |
32 | + describe ".search" do | |
33 | + let!(:searchable_issue) { create(:issue, title: "Searchable issue") } | |
34 | + | |
35 | + it "matches by title" do | |
36 | + described_class.search('able').all.should == [searchable_issue] | |
37 | + end | |
38 | + end | |
39 | + | |
40 | + describe "#today?" do | |
41 | + it "returns true when created today" do | |
42 | + # Avoid timezone differences and just return exactly what we want | |
43 | + Date.stub(:today).and_return(issue.created_at.to_date) | |
44 | + issue.today?.should be_true | |
45 | + end | |
46 | + | |
47 | + it "returns false when not created today" do | |
48 | + Date.stub(:today).and_return(Date.yesterday) | |
49 | + issue.today?.should be_false | |
50 | + end | |
51 | + end | |
52 | + | |
53 | + describe "#new?" do | |
54 | + it "returns true when created today and record hasn't been updated" do | |
55 | + issue.stub(:today?).and_return(true) | |
56 | + issue.new?.should be_true | |
57 | + end | |
58 | + | |
59 | + it "returns false when not created today" do | |
60 | + issue.stub(:today?).and_return(false) | |
61 | + issue.new?.should be_false | |
62 | + end | |
63 | + | |
64 | + it "returns false when record has been updated" do | |
65 | + issue.stub(:today?).and_return(true) | |
66 | + issue.touch | |
67 | + issue.new?.should be_false | |
68 | + end | |
69 | + end | |
70 | +end | ... | ... |
... | ... | @@ -0,0 +1,132 @@ |
1 | +require 'spec_helper' | |
2 | + | |
3 | +describe Issue do | |
4 | + let(:issue) { create(:issue) } | |
5 | + | |
6 | + describe "#upvotes" do | |
7 | + it "with no notes has a 0/0 score" do | |
8 | + issue.upvotes.should == 0 | |
9 | + end | |
10 | + | |
11 | + it "should recognize non-+1 notes" do | |
12 | + issue.notes << create(:note, note: "No +1 here") | |
13 | + issue.should have(1).note | |
14 | + issue.notes.first.upvote?.should be_false | |
15 | + issue.upvotes.should == 0 | |
16 | + end | |
17 | + | |
18 | + it "should recognize a single +1 note" do | |
19 | + issue.notes << create(:note, note: "+1 This is awesome") | |
20 | + issue.upvotes.should == 1 | |
21 | + end | |
22 | + | |
23 | + it "should recognize multiple +1 notes" do | |
24 | + issue.notes << create(:note, note: "+1 This is awesome") | |
25 | + issue.notes << create(:note, note: "+1 I want this") | |
26 | + issue.upvotes.should == 2 | |
27 | + end | |
28 | + end | |
29 | + | |
30 | + describe "#downvotes" do | |
31 | + it "with no notes has a 0/0 score" do | |
32 | + issue.downvotes.should == 0 | |
33 | + end | |
34 | + | |
35 | + it "should recognize non--1 notes" do | |
36 | + issue.notes << create(:note, note: "Almost got a -1") | |
37 | + issue.should have(1).note | |
38 | + issue.notes.first.downvote?.should be_false | |
39 | + issue.downvotes.should == 0 | |
40 | + end | |
41 | + | |
42 | + it "should recognize a single -1 note" do | |
43 | + issue.notes << create(:note, note: "-1 This is bad") | |
44 | + issue.downvotes.should == 1 | |
45 | + end | |
46 | + | |
47 | + it "should recognize multiple -1 notes" do | |
48 | + issue.notes << create(:note, note: "-1 This is bad") | |
49 | + issue.notes << create(:note, note: "-1 Away with this") | |
50 | + issue.downvotes.should == 2 | |
51 | + end | |
52 | + end | |
53 | + | |
54 | + describe "#votes_count" do | |
55 | + it "with no notes has a 0/0 score" do | |
56 | + issue.votes_count.should == 0 | |
57 | + end | |
58 | + | |
59 | + it "should recognize non notes" do | |
60 | + issue.notes << create(:note, note: "No +1 here") | |
61 | + issue.should have(1).note | |
62 | + issue.votes_count.should == 0 | |
63 | + end | |
64 | + | |
65 | + it "should recognize a single +1 note" do | |
66 | + issue.notes << create(:note, note: "+1 This is awesome") | |
67 | + issue.votes_count.should == 1 | |
68 | + end | |
69 | + | |
70 | + it "should recognize a single -1 note" do | |
71 | + issue.notes << create(:note, note: "-1 This is bad") | |
72 | + issue.votes_count.should == 1 | |
73 | + end | |
74 | + | |
75 | + it "should recognize multiple notes" do | |
76 | + issue.notes << create(:note, note: "+1 This is awesome") | |
77 | + issue.notes << create(:note, note: "-1 This is bad") | |
78 | + issue.notes << create(:note, note: "+1 I want this") | |
79 | + issue.votes_count.should == 3 | |
80 | + end | |
81 | + end | |
82 | + | |
83 | + describe "#upvotes_in_percent" do | |
84 | + it "with no notes has a 0% score" do | |
85 | + issue.upvotes_in_percent.should == 0 | |
86 | + end | |
87 | + | |
88 | + it "should count a single 1 note as 100%" do | |
89 | + issue.notes << create(:note, note: "+1 This is awesome") | |
90 | + issue.upvotes_in_percent.should == 100 | |
91 | + end | |
92 | + | |
93 | + it "should count multiple +1 notes as 100%" do | |
94 | + issue.notes << create(:note, note: "+1 This is awesome") | |
95 | + issue.notes << create(:note, note: "+1 I want this") | |
96 | + issue.upvotes_in_percent.should == 100 | |
97 | + end | |
98 | + | |
99 | + it "should count fractions for multiple +1 and -1 notes correctly" do | |
100 | + issue.notes << create(:note, note: "+1 This is awesome") | |
101 | + issue.notes << create(:note, note: "+1 I want this") | |
102 | + issue.notes << create(:note, note: "-1 This is bad") | |
103 | + issue.notes << create(:note, note: "+1 me too") | |
104 | + issue.upvotes_in_percent.should == 75 | |
105 | + end | |
106 | + end | |
107 | + | |
108 | + describe "#downvotes_in_percent" do | |
109 | + it "with no notes has a 0% score" do | |
110 | + issue.downvotes_in_percent.should == 0 | |
111 | + end | |
112 | + | |
113 | + it "should count a single -1 note as 100%" do | |
114 | + issue.notes << create(:note, note: "-1 This is bad") | |
115 | + issue.downvotes_in_percent.should == 100 | |
116 | + end | |
117 | + | |
118 | + it "should count multiple -1 notes as 100%" do | |
119 | + issue.notes << create(:note, note: "-1 This is bad") | |
120 | + issue.notes << create(:note, note: "-1 Away with this") | |
121 | + issue.downvotes_in_percent.should == 100 | |
122 | + end | |
123 | + | |
124 | + it "should count fractions for multiple +1 and -1 notes correctly" do | |
125 | + issue.notes << create(:note, note: "+1 This is awesome") | |
126 | + issue.notes << create(:note, note: "+1 I want this") | |
127 | + issue.notes << create(:note, note: "-1 This is bad") | |
128 | + issue.notes << create(:note, note: "+1 me too") | |
129 | + issue.downvotes_in_percent.should == 25 | |
130 | + end | |
131 | + end | |
132 | +end | ... | ... |
... | ... | @@ -0,0 +1,159 @@ |
1 | +require 'spec_helper' | |
2 | + | |
3 | +describe Project, "Repository" do | |
4 | + let(:project) { create(:project) } | |
5 | + | |
6 | + describe "#empty_repo?" do | |
7 | + it "should return true if the repo doesn't exist" do | |
8 | + project.stub(repo_exists?: false, has_commits?: true) | |
9 | + project.should be_empty_repo | |
10 | + end | |
11 | + | |
12 | + it "should return true if the repo has commits" do | |
13 | + project.stub(repo_exists?: true, has_commits?: false) | |
14 | + project.should be_empty_repo | |
15 | + end | |
16 | + | |
17 | + it "should return false if the repo exists and has commits" do | |
18 | + project.stub(repo_exists?: true, has_commits?: true) | |
19 | + project.should_not be_empty_repo | |
20 | + end | |
21 | + end | |
22 | + | |
23 | + describe "#discover_default_branch" do | |
24 | + let(:master) { 'master' } | |
25 | + let(:stable) { 'stable' } | |
26 | + | |
27 | + it "returns 'master' when master exists" do | |
28 | + project.should_receive(:branch_names).at_least(:once).and_return([stable, master]) | |
29 | + project.discover_default_branch.should == 'master' | |
30 | + end | |
31 | + | |
32 | + it "returns non-master when master exists but default branch is set to something else" do | |
33 | + project.default_branch = 'stable' | |
34 | + project.should_receive(:branch_names).at_least(:once).and_return([stable, master]) | |
35 | + project.discover_default_branch.should == 'stable' | |
36 | + end | |
37 | + | |
38 | + it "returns a non-master branch when only one exists" do | |
39 | + project.should_receive(:branch_names).at_least(:once).and_return([stable]) | |
40 | + project.discover_default_branch.should == 'stable' | |
41 | + end | |
42 | + | |
43 | + it "returns nil when no branch exists" do | |
44 | + project.should_receive(:branch_names).at_least(:once).and_return([]) | |
45 | + project.discover_default_branch.should be_nil | |
46 | + end | |
47 | + end | |
48 | + | |
49 | + describe "#root_ref" do | |
50 | + it "returns default_branch when set" do | |
51 | + project.default_branch = 'stable' | |
52 | + project.root_ref.should == 'stable' | |
53 | + end | |
54 | + | |
55 | + it "returns 'master' when default_branch is nil" do | |
56 | + project.default_branch = nil | |
57 | + project.root_ref.should == 'master' | |
58 | + end | |
59 | + end | |
60 | + | |
61 | + describe "#root_ref?" do | |
62 | + it "returns true when branch is root_ref" do | |
63 | + project.default_branch = 'stable' | |
64 | + project.root_ref?('stable').should be_true | |
65 | + end | |
66 | + | |
67 | + it "returns false when branch is not root_ref" do | |
68 | + project.default_branch = nil | |
69 | + project.root_ref?('stable').should be_false | |
70 | + end | |
71 | + end | |
72 | + | |
73 | + describe :repo do | |
74 | + it "should return valid repo" do | |
75 | + project.repo.should be_kind_of(Grit::Repo) | |
76 | + end | |
77 | + | |
78 | + it "should return nil" do | |
79 | + lambda { Project.new(path: "invalid").repo }.should raise_error(Grit::NoSuchPathError) | |
80 | + end | |
81 | + | |
82 | + it "should return nil" do | |
83 | + lambda { Project.new.repo }.should raise_error(TypeError) | |
84 | + end | |
85 | + end | |
86 | + | |
87 | + describe :commit do | |
88 | + it "should return first head commit if without params" do | |
89 | + project.commit.id.should == project.repo.commits.first.id | |
90 | + end | |
91 | + | |
92 | + it "should return valid commit" do | |
93 | + project.commit(ValidCommit::ID).should be_valid_commit | |
94 | + end | |
95 | + | |
96 | + it "should return nil" do | |
97 | + project.commit("+123_4532530XYZ").should be_nil | |
98 | + end | |
99 | + end | |
100 | + | |
101 | + describe :tree do | |
102 | + before do | |
103 | + @commit = project.commit(ValidCommit::ID) | |
104 | + end | |
105 | + | |
106 | + it "should raise error w/o arguments" do | |
107 | + lambda { project.tree }.should raise_error | |
108 | + end | |
109 | + | |
110 | + it "should return root tree for commit" do | |
111 | + tree = project.tree(@commit) | |
112 | + tree.contents.size.should == ValidCommit::FILES_COUNT | |
113 | + tree.contents.map(&:name).should == ValidCommit::FILES | |
114 | + end | |
115 | + | |
116 | + it "should return root tree for commit with correct path" do | |
117 | + tree = project.tree(@commit, ValidCommit::C_FILE_PATH) | |
118 | + tree.contents.map(&:name).should == ValidCommit::C_FILES | |
119 | + end | |
120 | + | |
121 | + it "should return root tree for commit with incorrect path" do | |
122 | + project.tree(@commit, "invalid_path").should be_nil | |
123 | + end | |
124 | + end | |
125 | + | |
126 | + describe "fresh commits" do | |
127 | + let(:project) { create(:project) } | |
128 | + | |
129 | + it { project.fresh_commits(3).count.should == 3 } | |
130 | + it { project.fresh_commits.first.id.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a" } | |
131 | + it { project.fresh_commits.last.id.should == "f403da73f5e62794a0447aca879360494b08f678" } | |
132 | + end | |
133 | + | |
134 | + describe "commits_between" do | |
135 | + let(:project) { create(:project) } | |
136 | + | |
137 | + subject do | |
138 | + commits = project.commits_between("3a4b4fb4cde7809f033822a171b9feae19d41fff", | |
139 | + "8470d70da67355c9c009e4401746b1d5410af2e3") | |
140 | + commits.map { |c| c.id } | |
141 | + end | |
142 | + | |
143 | + it { should have(3).elements } | |
144 | + it { should include("f0f14c8eaba69ebddd766498a9d0b0e79becd633") } | |
145 | + it { should_not include("bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a") } | |
146 | + end | |
147 | + | |
148 | + describe :valid_repo? do | |
149 | + it "should be valid repo" do | |
150 | + project = create(:project) | |
151 | + project.valid_repo?.should be_true | |
152 | + end | |
153 | + | |
154 | + it "should be invalid repo" do | |
155 | + project = Project.new(name: "ok_name", path: "/INVALID_PATH/", path: "NEOK") | |
156 | + project.valid_repo?.should be_false | |
157 | + end | |
158 | + end | |
159 | +end | ... | ... |
spec/models/user_spec.rb
... | ... | @@ -185,4 +185,14 @@ describe User do |
185 | 185 | |
186 | 186 | it { User.not_in_project(@project).should == [@user, @project.owner] } |
187 | 187 | end |
188 | + | |
189 | + describe 'normal user' do | |
190 | + let(:user) { create(:user, name: 'John Smith') } | |
191 | + | |
192 | + it { user.is_admin?.should be_false } | |
193 | + it { user.require_ssh_key?.should be_true } | |
194 | + it { user.can_create_group?.should be_false } | |
195 | + it { user.can_create_project?.should be_true } | |
196 | + it { user.first_name.should == 'John' } | |
197 | + end | |
188 | 198 | end | ... | ... |
spec/roles/account_role_spec.rb
... | ... | @@ -1,13 +0,0 @@ |
1 | -require 'spec_helper' | |
2 | - | |
3 | -describe User, "Account" do | |
4 | - describe 'normal user' do | |
5 | - let(:user) { create(:user, name: 'John Smith') } | |
6 | - | |
7 | - it { user.is_admin?.should be_false } | |
8 | - it { user.require_ssh_key?.should be_true } | |
9 | - it { user.can_create_group?.should be_false } | |
10 | - it { user.can_create_project?.should be_true } | |
11 | - it { user.first_name.should == 'John' } | |
12 | - end | |
13 | -end |
spec/roles/issue_commonality_spec.rb
... | ... | @@ -1,70 +0,0 @@ |
1 | -require 'spec_helper' | |
2 | - | |
3 | -describe Issue, "IssueCommonality" do | |
4 | - let(:issue) { create(:issue) } | |
5 | - | |
6 | - describe "Associations" do | |
7 | - it { should belong_to(:project) } | |
8 | - it { should belong_to(:author) } | |
9 | - it { should belong_to(:assignee) } | |
10 | - it { should have_many(:notes).dependent(:destroy) } | |
11 | - end | |
12 | - | |
13 | - describe "Validation" do | |
14 | - it { should validate_presence_of(:project) } | |
15 | - it { should validate_presence_of(:author) } | |
16 | - it { should validate_presence_of(:title) } | |
17 | - it { should ensure_length_of(:title).is_at_least(0).is_at_most(255) } | |
18 | - it { should ensure_inclusion_of(:closed).in_array([true, false]) } | |
19 | - end | |
20 | - | |
21 | - describe "Scope" do | |
22 | - it { described_class.should respond_to(:opened) } | |
23 | - it { described_class.should respond_to(:closed) } | |
24 | - it { described_class.should respond_to(:assigned) } | |
25 | - end | |
26 | - | |
27 | - it "has an :author_id_of_changes accessor" do | |
28 | - issue.should respond_to(:author_id_of_changes) | |
29 | - issue.should respond_to(:author_id_of_changes=) | |
30 | - end | |
31 | - | |
32 | - describe ".search" do | |
33 | - let!(:searchable_issue) { create(:issue, title: "Searchable issue") } | |
34 | - | |
35 | - it "matches by title" do | |
36 | - described_class.search('able').all.should == [searchable_issue] | |
37 | - end | |
38 | - end | |
39 | - | |
40 | - describe "#today?" do | |
41 | - it "returns true when created today" do | |
42 | - # Avoid timezone differences and just return exactly what we want | |
43 | - Date.stub(:today).and_return(issue.created_at.to_date) | |
44 | - issue.today?.should be_true | |
45 | - end | |
46 | - | |
47 | - it "returns false when not created today" do | |
48 | - Date.stub(:today).and_return(Date.yesterday) | |
49 | - issue.today?.should be_false | |
50 | - end | |
51 | - end | |
52 | - | |
53 | - describe "#new?" do | |
54 | - it "returns true when created today and record hasn't been updated" do | |
55 | - issue.stub(:today?).and_return(true) | |
56 | - issue.new?.should be_true | |
57 | - end | |
58 | - | |
59 | - it "returns false when not created today" do | |
60 | - issue.stub(:today?).and_return(false) | |
61 | - issue.new?.should be_false | |
62 | - end | |
63 | - | |
64 | - it "returns false when record has been updated" do | |
65 | - issue.stub(:today?).and_return(true) | |
66 | - issue.touch | |
67 | - issue.new?.should be_false | |
68 | - end | |
69 | - end | |
70 | -end |
spec/roles/repository_spec.rb
... | ... | @@ -1,159 +0,0 @@ |
1 | -require 'spec_helper' | |
2 | - | |
3 | -describe Project, "Repository" do | |
4 | - let(:project) { create(:project) } | |
5 | - | |
6 | - describe "#empty_repo?" do | |
7 | - it "should return true if the repo doesn't exist" do | |
8 | - project.stub(repo_exists?: false, has_commits?: true) | |
9 | - project.should be_empty_repo | |
10 | - end | |
11 | - | |
12 | - it "should return true if the repo has commits" do | |
13 | - project.stub(repo_exists?: true, has_commits?: false) | |
14 | - project.should be_empty_repo | |
15 | - end | |
16 | - | |
17 | - it "should return false if the repo exists and has commits" do | |
18 | - project.stub(repo_exists?: true, has_commits?: true) | |
19 | - project.should_not be_empty_repo | |
20 | - end | |
21 | - end | |
22 | - | |
23 | - describe "#discover_default_branch" do | |
24 | - let(:master) { 'master' } | |
25 | - let(:stable) { 'stable' } | |
26 | - | |
27 | - it "returns 'master' when master exists" do | |
28 | - project.should_receive(:branch_names).at_least(:once).and_return([stable, master]) | |
29 | - project.discover_default_branch.should == 'master' | |
30 | - end | |
31 | - | |
32 | - it "returns non-master when master exists but default branch is set to something else" do | |
33 | - project.default_branch = 'stable' | |
34 | - project.should_receive(:branch_names).at_least(:once).and_return([stable, master]) | |
35 | - project.discover_default_branch.should == 'stable' | |
36 | - end | |
37 | - | |
38 | - it "returns a non-master branch when only one exists" do | |
39 | - project.should_receive(:branch_names).at_least(:once).and_return([stable]) | |
40 | - project.discover_default_branch.should == 'stable' | |
41 | - end | |
42 | - | |
43 | - it "returns nil when no branch exists" do | |
44 | - project.should_receive(:branch_names).at_least(:once).and_return([]) | |
45 | - project.discover_default_branch.should be_nil | |
46 | - end | |
47 | - end | |
48 | - | |
49 | - describe "#root_ref" do | |
50 | - it "returns default_branch when set" do | |
51 | - project.default_branch = 'stable' | |
52 | - project.root_ref.should == 'stable' | |
53 | - end | |
54 | - | |
55 | - it "returns 'master' when default_branch is nil" do | |
56 | - project.default_branch = nil | |
57 | - project.root_ref.should == 'master' | |
58 | - end | |
59 | - end | |
60 | - | |
61 | - describe "#root_ref?" do | |
62 | - it "returns true when branch is root_ref" do | |
63 | - project.default_branch = 'stable' | |
64 | - project.root_ref?('stable').should be_true | |
65 | - end | |
66 | - | |
67 | - it "returns false when branch is not root_ref" do | |
68 | - project.default_branch = nil | |
69 | - project.root_ref?('stable').should be_false | |
70 | - end | |
71 | - end | |
72 | - | |
73 | - describe :repo do | |
74 | - it "should return valid repo" do | |
75 | - project.repo.should be_kind_of(Grit::Repo) | |
76 | - end | |
77 | - | |
78 | - it "should return nil" do | |
79 | - lambda { Project.new(path: "invalid").repo }.should raise_error(Grit::NoSuchPathError) | |
80 | - end | |
81 | - | |
82 | - it "should return nil" do | |
83 | - lambda { Project.new.repo }.should raise_error(TypeError) | |
84 | - end | |
85 | - end | |
86 | - | |
87 | - describe :commit do | |
88 | - it "should return first head commit if without params" do | |
89 | - project.commit.id.should == project.repo.commits.first.id | |
90 | - end | |
91 | - | |
92 | - it "should return valid commit" do | |
93 | - project.commit(ValidCommit::ID).should be_valid_commit | |
94 | - end | |
95 | - | |
96 | - it "should return nil" do | |
97 | - project.commit("+123_4532530XYZ").should be_nil | |
98 | - end | |
99 | - end | |
100 | - | |
101 | - describe :tree do | |
102 | - before do | |
103 | - @commit = project.commit(ValidCommit::ID) | |
104 | - end | |
105 | - | |
106 | - it "should raise error w/o arguments" do | |
107 | - lambda { project.tree }.should raise_error | |
108 | - end | |
109 | - | |
110 | - it "should return root tree for commit" do | |
111 | - tree = project.tree(@commit) | |
112 | - tree.contents.size.should == ValidCommit::FILES_COUNT | |
113 | - tree.contents.map(&:name).should == ValidCommit::FILES | |
114 | - end | |
115 | - | |
116 | - it "should return root tree for commit with correct path" do | |
117 | - tree = project.tree(@commit, ValidCommit::C_FILE_PATH) | |
118 | - tree.contents.map(&:name).should == ValidCommit::C_FILES | |
119 | - end | |
120 | - | |
121 | - it "should return root tree for commit with incorrect path" do | |
122 | - project.tree(@commit, "invalid_path").should be_nil | |
123 | - end | |
124 | - end | |
125 | - | |
126 | - describe "fresh commits" do | |
127 | - let(:project) { create(:project) } | |
128 | - | |
129 | - it { project.fresh_commits(3).count.should == 3 } | |
130 | - it { project.fresh_commits.first.id.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a" } | |
131 | - it { project.fresh_commits.last.id.should == "f403da73f5e62794a0447aca879360494b08f678" } | |
132 | - end | |
133 | - | |
134 | - describe "commits_between" do | |
135 | - let(:project) { create(:project) } | |
136 | - | |
137 | - subject do | |
138 | - commits = project.commits_between("3a4b4fb4cde7809f033822a171b9feae19d41fff", | |
139 | - "8470d70da67355c9c009e4401746b1d5410af2e3") | |
140 | - commits.map { |c| c.id } | |
141 | - end | |
142 | - | |
143 | - it { should have(3).elements } | |
144 | - it { should include("f0f14c8eaba69ebddd766498a9d0b0e79becd633") } | |
145 | - it { should_not include("bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a") } | |
146 | - end | |
147 | - | |
148 | - describe :valid_repo? do | |
149 | - it "should be valid repo" do | |
150 | - project = create(:project) | |
151 | - project.valid_repo?.should be_true | |
152 | - end | |
153 | - | |
154 | - it "should be invalid repo" do | |
155 | - project = Project.new(name: "ok_name", path: "/INVALID_PATH/", path: "NEOK") | |
156 | - project.valid_repo?.should be_false | |
157 | - end | |
158 | - end | |
159 | -end |
spec/roles/votes_spec.rb
... | ... | @@ -1,132 +0,0 @@ |
1 | -require 'spec_helper' | |
2 | - | |
3 | -describe Issue do | |
4 | - let(:issue) { create(:issue) } | |
5 | - | |
6 | - describe "#upvotes" do | |
7 | - it "with no notes has a 0/0 score" do | |
8 | - issue.upvotes.should == 0 | |
9 | - end | |
10 | - | |
11 | - it "should recognize non-+1 notes" do | |
12 | - issue.notes << create(:note, note: "No +1 here") | |
13 | - issue.should have(1).note | |
14 | - issue.notes.first.upvote?.should be_false | |
15 | - issue.upvotes.should == 0 | |
16 | - end | |
17 | - | |
18 | - it "should recognize a single +1 note" do | |
19 | - issue.notes << create(:note, note: "+1 This is awesome") | |
20 | - issue.upvotes.should == 1 | |
21 | - end | |
22 | - | |
23 | - it "should recognize multiple +1 notes" do | |
24 | - issue.notes << create(:note, note: "+1 This is awesome") | |
25 | - issue.notes << create(:note, note: "+1 I want this") | |
26 | - issue.upvotes.should == 2 | |
27 | - end | |
28 | - end | |
29 | - | |
30 | - describe "#downvotes" do | |
31 | - it "with no notes has a 0/0 score" do | |
32 | - issue.downvotes.should == 0 | |
33 | - end | |
34 | - | |
35 | - it "should recognize non--1 notes" do | |
36 | - issue.notes << create(:note, note: "Almost got a -1") | |
37 | - issue.should have(1).note | |
38 | - issue.notes.first.downvote?.should be_false | |
39 | - issue.downvotes.should == 0 | |
40 | - end | |
41 | - | |
42 | - it "should recognize a single -1 note" do | |
43 | - issue.notes << create(:note, note: "-1 This is bad") | |
44 | - issue.downvotes.should == 1 | |
45 | - end | |
46 | - | |
47 | - it "should recognize multiple -1 notes" do | |
48 | - issue.notes << create(:note, note: "-1 This is bad") | |
49 | - issue.notes << create(:note, note: "-1 Away with this") | |
50 | - issue.downvotes.should == 2 | |
51 | - end | |
52 | - end | |
53 | - | |
54 | - describe "#votes_count" do | |
55 | - it "with no notes has a 0/0 score" do | |
56 | - issue.votes_count.should == 0 | |
57 | - end | |
58 | - | |
59 | - it "should recognize non notes" do | |
60 | - issue.notes << create(:note, note: "No +1 here") | |
61 | - issue.should have(1).note | |
62 | - issue.votes_count.should == 0 | |
63 | - end | |
64 | - | |
65 | - it "should recognize a single +1 note" do | |
66 | - issue.notes << create(:note, note: "+1 This is awesome") | |
67 | - issue.votes_count.should == 1 | |
68 | - end | |
69 | - | |
70 | - it "should recognize a single -1 note" do | |
71 | - issue.notes << create(:note, note: "-1 This is bad") | |
72 | - issue.votes_count.should == 1 | |
73 | - end | |
74 | - | |
75 | - it "should recognize multiple notes" do | |
76 | - issue.notes << create(:note, note: "+1 This is awesome") | |
77 | - issue.notes << create(:note, note: "-1 This is bad") | |
78 | - issue.notes << create(:note, note: "+1 I want this") | |
79 | - issue.votes_count.should == 3 | |
80 | - end | |
81 | - end | |
82 | - | |
83 | - describe "#upvotes_in_percent" do | |
84 | - it "with no notes has a 0% score" do | |
85 | - issue.upvotes_in_percent.should == 0 | |
86 | - end | |
87 | - | |
88 | - it "should count a single 1 note as 100%" do | |
89 | - issue.notes << create(:note, note: "+1 This is awesome") | |
90 | - issue.upvotes_in_percent.should == 100 | |
91 | - end | |
92 | - | |
93 | - it "should count multiple +1 notes as 100%" do | |
94 | - issue.notes << create(:note, note: "+1 This is awesome") | |
95 | - issue.notes << create(:note, note: "+1 I want this") | |
96 | - issue.upvotes_in_percent.should == 100 | |
97 | - end | |
98 | - | |
99 | - it "should count fractions for multiple +1 and -1 notes correctly" do | |
100 | - issue.notes << create(:note, note: "+1 This is awesome") | |
101 | - issue.notes << create(:note, note: "+1 I want this") | |
102 | - issue.notes << create(:note, note: "-1 This is bad") | |
103 | - issue.notes << create(:note, note: "+1 me too") | |
104 | - issue.upvotes_in_percent.should == 75 | |
105 | - end | |
106 | - end | |
107 | - | |
108 | - describe "#downvotes_in_percent" do | |
109 | - it "with no notes has a 0% score" do | |
110 | - issue.downvotes_in_percent.should == 0 | |
111 | - end | |
112 | - | |
113 | - it "should count a single -1 note as 100%" do | |
114 | - issue.notes << create(:note, note: "-1 This is bad") | |
115 | - issue.downvotes_in_percent.should == 100 | |
116 | - end | |
117 | - | |
118 | - it "should count multiple -1 notes as 100%" do | |
119 | - issue.notes << create(:note, note: "-1 This is bad") | |
120 | - issue.notes << create(:note, note: "-1 Away with this") | |
121 | - issue.downvotes_in_percent.should == 100 | |
122 | - end | |
123 | - | |
124 | - it "should count fractions for multiple +1 and -1 notes correctly" do | |
125 | - issue.notes << create(:note, note: "+1 This is awesome") | |
126 | - issue.notes << create(:note, note: "+1 I want this") | |
127 | - issue.notes << create(:note, note: "-1 This is bad") | |
128 | - issue.notes << create(:note, note: "+1 me too") | |
129 | - issue.downvotes_in_percent.should == 25 | |
130 | - end | |
131 | - end | |
132 | -end |