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,4 +170,139 @@ class Event < ActiveRecord::Base | ||
| 170 | "opened" | 170 | "opened" |
| 171 | end | 171 | end |
| 172 | end | 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 | end | 308 | end |
app/models/project.rb
| @@ -21,11 +21,7 @@ | @@ -21,11 +21,7 @@ | ||
| 21 | require "grit" | 21 | require "grit" |
| 22 | 22 | ||
| 23 | class Project < ActiveRecord::Base | 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 | class TransferError < StandardError; end | 26 | class TransferError < StandardError; end |
| 31 | 27 | ||
| @@ -277,4 +273,514 @@ class Project < ActiveRecord::Base | @@ -277,4 +273,514 @@ class Project < ActiveRecord::Base | ||
| 277 | creator | 273 | creator |
| 278 | end | 274 | end |
| 279 | end | 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 | end | 786 | end |
app/models/user.rb
| @@ -34,8 +34,6 @@ | @@ -34,8 +34,6 @@ | ||
| 34 | # | 34 | # |
| 35 | 35 | ||
| 36 | class User < ActiveRecord::Base | 36 | class User < ActiveRecord::Base |
| 37 | - include Account | ||
| 38 | - | ||
| 39 | devise :database_authenticatable, :token_authenticatable, :lockable, | 37 | devise :database_authenticatable, :token_authenticatable, :lockable, |
| 40 | :recoverable, :rememberable, :trackable, :validatable, :omniauthable | 38 | :recoverable, :rememberable, :trackable, :validatable, :omniauthable |
| 41 | 39 | ||
| @@ -192,4 +190,92 @@ class User < ActiveRecord::Base | @@ -192,4 +190,92 @@ class User < ActiveRecord::Base | ||
| 192 | def tm_in_personal_projects | 190 | def tm_in_personal_projects |
| 193 | personal_projects.users_projects.where(user_id: self.id) | 191 | personal_projects.users_projects.where(user_id: self.id) |
| 194 | end | 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 | end | 281 | end |
app/roles/account.rb
| @@ -1,95 +0,0 @@ | @@ -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,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,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,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,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,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,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,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,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,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,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 @@ | @@ -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 @@ | @@ -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 @@ | @@ -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 @@ | @@ -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 @@ | @@ -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 @@ | @@ -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,4 +185,14 @@ describe User do | ||
| 185 | 185 | ||
| 186 | it { User.not_in_project(@project).should == [@user, @project.owner] } | 186 | it { User.not_in_project(@project).should == [@user, @project.owner] } |
| 187 | end | 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 | end | 198 | end |
spec/roles/account_role_spec.rb
| @@ -1,13 +0,0 @@ | @@ -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,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,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,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 |