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