<commit_before># encoding: utf-8
#--
#   Copyright (C) 2012 Gitorious AS
#   Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU Affero General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU Affero General Public License for more details.
#
#   You should have received a copy of the GNU Affero General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
#++

require "test_helper"
require "ostruct"

class RepositoryTest < ActiveSupport::TestCase
  def new_repos(opts={})
    Repository.new({
      :name => "foo",
      :project => projects(:johans),
      :user => users(:johan),
      :owner => users(:johan)
    }.merge(opts))
  end

  def setup
    @repository = new_repos
    FileUtils.mkdir_p(@repository.full_repository_path, :mode => 0755)
  end

  def teardown
    clear_message_queue
  end

  should "only accept names with alphanum characters in it" do
    @repository.name = "foo bar"
    assert !@repository.valid?, "valid? should be false"

    @repository.name = "foo!bar"
    assert !@repository.valid?, "valid? should be false"

    @repository.name = "foobar"
    assert @repository.valid?

    @repository.name = "foo42"
    assert @repository.valid?
  end

  should "has a unique name within a project" do
    @repository.save
    repos = new_repos(:name => "FOO")
    assert !repos.valid?, "valid? should be false"
    assert_not_nil repos.errors[:name]

    assert new_repos(:project => projects(:moes)).valid?
  end

  should "not have a reserved name" do
    repo = new_repos(:name => Gitorious::Reservations.repository_names.first.dup)
    repo.valid?
    assert_not_nil repo.errors[:name]
    RepositoriesController.action_methods.each do |action|
      repo.name = action.dup
      repo.valid?
      assert_not_nil repo.errors[:name], "fail on #{action}"
    end
  end

  context "git urls" do
    setup do
      @host_with_user = "#{GitoriousConfig['gitorious_user']}@#{GitoriousConfig['gitorious_host']}"
      @host = "#{GitoriousConfig['gitorious_host']}"
    end

    should "has a gitdir name" do
      assert_equal "#{@repository.project.slug}/foo.git", @repository.gitdir
    end

    should "has a push url" do
      assert_equal "#{@host_with_user}:#{@repository.project.slug}/foo.git", @repository.push_url
    end

    should "has a clone url" do
      assert_equal "git://#{@host}/#{@repository.project.slug}/foo.git", @repository.clone_url
    end

    should "has a http url" do
      assert_equal "#{GitoriousConfig['scheme']}://git.#{@host}/#{@repository.project.slug}/foo.git", @repository.http_clone_url
    end

    should "use the real http cloning URL" do
      old_value = Site::HTTP_CLONING_SUBDOMAIN
      silence_warnings do
        Site::HTTP_CLONING_SUBDOMAIN = "whatever"
      end
      assert_equal "#{GitoriousConfig['scheme']}://whatever.#{@host}/#{@repository.project.slug}/foo.git", @repository.http_clone_url
      silence_warnings {Site::HTTP_CLONING_SUBDOMAIN = old_value}
    end

    should "has a clone url with the project name, if it is a mainline" do
      @repository.owner = groups(:team_thunderbird)
      @repository.kind = Repository::KIND_PROJECT_REPO
      assert_equal "git://#{@host}/#{@repository.project.slug}/foo.git", @repository.clone_url
    end

    should "have a clone url with the team/user, if it is not a mainline" do
      @repository.kind = Repository::KIND_TEAM_REPO
      url = "git://#{@host}/#{@repository.owner.to_param_with_prefix}/#{@repository.project.slug}/foo.git"
      assert_equal url, @repository.clone_url

      @repository.kind = Repository::KIND_USER_REPO
      @repository.owner = users(:johan)
      url = "git://#{@host}/#{users(:johan).to_param_with_prefix}/#{@repository.project.slug}/foo.git"
      assert_equal url, @repository.clone_url
    end

    should "has a push url with the project name, if it is a mainline" do
      @repository.owner = groups(:team_thunderbird)
      @repository.kind = Repository::KIND_PROJECT_REPO
      assert_equal "#{@host_with_user}:#{@repository.project.slug}/foo.git", @repository.push_url
    end

    should "have a push url with the team/user, if it is not a mainline" do
      @repository.owner = groups(:team_thunderbird)
      @repository.kind = Repository::KIND_TEAM_REPO
      url = "#{@host_with_user}:#{groups(:team_thunderbird).to_param_with_prefix}/#{@repository.project.slug}/foo.git"
      assert_equal url, @repository.push_url

      @repository.kind = Repository::KIND_USER_REPO
      @repository.owner = users(:johan)
      url = "#{@host_with_user}:#{users(:johan).to_param_with_prefix}/#{@repository.project.slug}/foo.git"
      assert_equal url, @repository.push_url
    end

    should "has a http clone url with the project name, if it is a mainline" do
      @repository.owner = groups(:team_thunderbird)
      @repository.kind = Repository::KIND_PROJECT_REPO
      assert_equal "#{GitoriousConfig['scheme']}://git.#{@host}/#{@repository.project.slug}/foo.git", @repository.http_clone_url
    end

    should "have a http clone url with the team/user, if it is not a mainline" do
      @repository.owner = groups(:team_thunderbird)
      @repository.kind = Repository::KIND_TEAM_REPO
      url = "#{GitoriousConfig['scheme']}://git.#{@host}/#{groups(:team_thunderbird).to_param_with_prefix}/#{@repository.project.slug}/foo.git"
      assert_equal url, @repository.http_clone_url

      @repository.owner = users(:johan)
      @repository.kind = Repository::KIND_USER_REPO
      url = "#{GitoriousConfig['scheme']}://git.#{@host}/#{users(:johan).to_param_with_prefix}/#{@repository.project.slug}/foo.git"
      assert_equal url, @repository.http_clone_url
    end

    should "has a full repository_path" do
      expected_dir = File.expand_path(File.join(RepositoryRoot.default_base_path,
        "#{@repository.full_hashed_path}.git"))
      assert_equal expected_dir, @repository.full_repository_path
    end

    should "always display SSH URLs when so instructed" do
      old_value = GitoriousConfig["always_display_ssh_url"]
      GitoriousConfig["always_display_ssh_url"] = true
      assert @repository.display_ssh_url?(users(:moe))
      GitoriousConfig["always_display_ssh_url"] = old_value
    end
  end

  should "inits the git repository" do
    path = @repository.full_repository_path
    Repository.git_backend.expects(:create).with(path).returns(true)
    Repository.create_git_repository(@repository.real_gitdir)

    assert File.exist?(path), "File.exist?(path) should be true"

    Dir.chdir(path) do
      hooks = File.join(path, "hooks")
      assert File.exist?(hooks), "File.exist?(hooks) should be true"
      assert File.symlink?(hooks), "File.symlink?(hooks) should be true"
      assert File.symlink?(File.expand_path(File.readlink(hooks))), "File.symlink?(File.expand_path(File.readlink(hooks))) should be true"
    end
  end

  should "clones a git repository" do
    source = repositories(:johans)
    target = @repository
    target_path = @repository.full_repository_path

    git_backend = mock("Git backend")
    Repository.expects(:git_backend).returns(git_backend)
    git_backend.expects(:clone).with(target.full_repository_path,
      source.full_repository_path).returns(true)
    Repository.expects(:create_hooks).returns(true)

    assert Repository.clone_git_repository(target.real_gitdir, source.real_gitdir)
  end

  should "not create hooks if the :skip_hooks option is set to true" do
    source = repositories(:johans)
    target = @repository
    target_path = @repository.full_repository_path

    git_backend = mock("Git backend")
    Repository.expects(:git_backend).returns(git_backend)
    git_backend.expects(:clone).with(target.full_repository_path,
      source.full_repository_path).returns(true)
    Repository.expects(:create_hooks).never

    Repository.clone_git_repository(target.real_gitdir, source.real_gitdir, :skip_hooks => true)
  end

  should "create the hooks" do
    hooks = "/path/to/hooks"
    path = "/path/to/repository"
    base_path = "#{Rails.root}/data/hooks"

    File.expects(:join).in_sequence.with(RepositoryRoot.default_base_path, ".hooks").returns(hooks)

    Dir.expects(:chdir).in_sequence.with(path).yields(nil)

    File.expects(:symlink?).in_sequence.with(hooks).returns(false)
    File.expects(:exist?).in_sequence.with(hooks).returns(false)
    FileUtils.expects(:ln_s).in_sequence.with(base_path, hooks)

    local_hooks = "/path/to/local/hooks"
    File.expects(:join).in_sequence.with(path, "hooks").returns(local_hooks)
    File.expects(:exist?).in_sequence.with(local_hooks).returns(true)

    Repository.create_hooks(path)
  end

  should "deletes a repository" do
    Repository.git_backend.expects(:delete!).with(@repository.full_repository_path).returns(true)
    Repository.delete_git_repository(@repository.real_gitdir)
  end

  should "knows if has commits" do
    @repository.stubs(:new_record?).returns(false)
    @repository.stubs(:ready?).returns(true)
    git_mock = mock("Grit::Git")
    @repository.stubs(:git).returns(git_mock)
    head = mock("head")
    head.stubs(:name).returns("master")
    @repository.git.expects(:heads).returns([head])
    assert @repository.has_commits?, "@repository.has_commits? should be true"
  end

  should "knows if has commits, unless its a new record" do
    @repository.stubs(:new_record?).returns(false)
    assert !@repository.has_commits?, "@repository.has_commits? should be false"
  end

  should "knows if has commits, unless its not ready" do
    @repository.stubs(:ready?).returns(false)
    assert !@repository.has_commits?, "@repository.has_commits? should be false"
  end

  should " build a new repository by cloning another one" do
    repos = Repository.new_by_cloning(@repository)
    assert_equal @repository, repos.parent
    assert_equal @repository.project, repos.project
    assert repos.new_record?, "new_record? should be true"
  end

  should "inherit merge request inclusion from its parent" do
    @repository.update_attribute(:merge_requests_enabled, true)
    clone = Repository.new_by_cloning(@repository)
    assert clone.merge_requests_enabled?
    @repository.update_attribute(:merge_requests_enabled, false)
    clone = Repository.new_by_cloning(@repository)
    assert !clone.merge_requests_enabled?
  end

  should "suggests a decent name for a cloned repository bsed on username" do
    repos = Repository.new_by_cloning(@repository, username="johan")
    assert_equal "johans-foo", repos.name
    repos = Repository.new_by_cloning(@repository, username=nil)
    assert_equal nil, repos.name
  end

  should "has it is name as its to_param value" do
    @repository.save
    assert_equal @repository.name, @repository.to_param
  end

  should "finds a repository by name or raises" do
    assert_equal repositories(:johans), Repository.find_by_name!(repositories(:johans).name)
    assert_raises(ActiveRecord::RecordNotFound) do
      Repository.find_by_name!("asdasdasd")
    end
  end

  context "find_by_path" do
    should "finds a repository by its path" do
      repo = repositories(:johans)
      path = File.join(RepositoryRoot.default_base_path,
                        projects(:johans).slug, "#{repo.name}.git")
      assert_equal repo, Repository.find_by_path(path)
    end

    should_eventually "finds a repository by its path, regardless of repository kind" do
      repo = projects(:johans).wiki_repository
      path = File.join(RepositoryRoot.default_base_path.chomp("/"),
                        projects(:johans).slug, "#{repo.name}.git")
      assert_equal repo, Repository.find_by_path(path)
    end

    should "finds a group repository by its path" do
      repo = repositories(:johans)
      repo.owner = groups(:team_thunderbird)
      repo.kind = Repository::KIND_TEAM_REPO
      repo.save!
      path = File.join(RepositoryRoot.default_base_path, repo.gitdir)
      assert_equal repo, Repository.find_by_path(path)
    end

    should "finds a user repository by its path" do
      repo = repositories(:johans)
      repo.owner = users(:johan)
      repo.kind = Repository::KIND_USER_REPO
      repo.save!
      path = File.join(RepositoryRoot.default_base_path, repo.gitdir)
      assert_equal repo, Repository.find_by_path(path)
    end

    should "scope the find by project id" do
      repo = repositories(:johans)
      repo.owner = groups(:team_thunderbird)
      repo.kind = Repository::KIND_TEAM_REPO
      repo.save!
      same_name_repo = new_repos(:name => repo.name)
      same_name_repo
      same_name_repo.project = projects(:moes)
      same_name_repo.owner = groups(:team_thunderbird)
      same_name_repo.kind = Repository::KIND_TEAM_REPO
      same_name_repo.save!
      path = File.join(RepositoryRoot.default_base_path, same_name_repo.gitdir)
      assert_equal same_name_repo, Repository.find_by_path(path)
    end
  end

  context "#to_xml" do
    should "xmlilizes git paths as well" do
      assert @repository.to_xml.include?("<clone-url>")
      assert @repository.to_xml.include?("<push-url>")
    end

    should "include a description of the kind" do
      assert_match(/<kind>mainline<\/kind>/, @repository.to_xml)
      @repository.kind = Repository::KIND_TEAM_REPO
      assert_match(/<kind>team<\/kind>/, @repository.to_xml)
    end

    should "include the project name" do
      assert_match(/<project>#{@repository.project.slug}<\/project>/, @repository.to_xml)
    end

    should "include the owner" do
      assert_match(/<owner kind="User">johan<\/owner>/, @repository.to_xml)
    end
  end

  context "can_push?" do
    should "knows if a user can write to self" do
      @repository.owner = users(:johan)
      @repository.save!
      @repository.reload
      assert can_push?(users(:johan), @repository)
      assert !can_push?(users(:mike), @repository)

      @repository.change_owner_to!(groups(:team_thunderbird))
      @repository.save!
      assert !can_push?(users(:johan), @repository)

      @repository.owner.add_member(users(:moe), Role.member)
      @repository.committerships.reload
      assert can_push?(users(:moe), @repository)
    end

    context "a wiki repository" do
      setup do
        @repository.kind = Repository::KIND_WIKI
      end

      should "be writable by everyone" do
        @repository.wiki_permissions = Repository::WIKI_WRITABLE_EVERYONE
        [:johan, :mike, :moe].each do |login|
          assert can_push?(users(login), @repository), "not writable_by #{login}"
        end
      end

      should "only be writable by project members" do
        @repository.wiki_permissions = Repository::WIKI_WRITABLE_PROJECT_MEMBERS
        assert @repository.project.member?(users(:johan))
        assert can_push?(users(:johan), @repository)

        assert !@repository.project.member?(users(:moe))
        assert !can_push?(users(:moe), @repository)
      end
    end
  end

  should "publishe a message on create and update" do
    @repository.save!

    assert_published("/queue/GitoriousRepositoryCreation", {
                       "command" => "create_git_repository",
                       "target_id" => @repository.id,
                       "arguments" => [@repository.real_gitdir]
                     })
  end

  should "publishe a message on clone" do
    @repository.parent = repositories(:johans)
    @repository.save!

    assert_published("/queue/GitoriousRepositoryCreation", {
                       "command" => "clone_git_repository",
                       "target_id" => @repository.id,
                       "arguments" => [@repository.real_gitdir,
                                       @repository.parent.real_gitdir]
                     })
  end

  should "create a notification on destroy" do
    @repository.save!
    @repository.destroy

    assert_published("/queue/GitoriousRepositoryDeletion", {
                       "command" => "delete_git_repository",
                       "arguments" => [@repository.real_gitdir]
                     })
  end

  should "has one recent commit" do
    @repository.save!
    repos_mock = mock("Git mock")
    commit_mock = stub_everything("Git::Commit mock")
    repos_mock.expects(:commits).with("master", 1).returns([commit_mock])
    @repository.stubs(:git).returns(repos_mock)
    @repository.stubs(:has_commits?).returns(true)
    heads_stub = mock("head")
    heads_stub.stubs(:name).returns("master")
    @repository.stubs(:head_candidate).returns(heads_stub)
    assert_equal commit_mock, @repository.last_commit
  end

  should "has one recent commit within a given ref" do
    @repository.save!
    repos_mock = mock("Git mock")
    commit_mock = stub_everything("Git::Commit mock")
    repos_mock.expects(:commits).with("foo", 1).returns([commit_mock])
    @repository.stubs(:git).returns(repos_mock)
    @repository.stubs(:has_commits?).returns(true)
    @repository.expects(:head_candidate).never
    assert_equal commit_mock, @repository.last_commit("foo")
  end

  context "deletion" do
    setup do
      @repository.kind = Repository::KIND_PROJECT_REPO
      @repository.project.repositories << new_repos(:name => "another")
      @repository.save!
      @repository.committerships.create!({
        :committer => users(:moe),
        :permissions => (Committership::CAN_REVIEW | Committership::CAN_COMMIT)
      })
    end

    should "be deletable by admins" do
      assert admin?(users(:johan), @repository)
      assert !admin?(users(:moe), @repository)

      assert can_delete?(users(:johan), @repository)
      assert !can_delete?(users(:moe), @repository)
    end

    should "always be deletable if admin and non-project repo" do
      @repository.kind = Repository::KIND_TEAM_REPO
      assert can_delete?(users(:johan), @repository)
      assert !can_delete?(users(:moe), @repository)
    end

    should "also be deletable by users with admin privs" do
      @repository.kind = Repository::KIND_TEAM_REPO
      cs = @repository.committerships.create_with_permissions!({
          :committer => users(:mike)
        }, Committership::CAN_ADMIN)
      assert can_delete?(users(:johan), @repository)
      assert can_delete?(users(:mike), @repository)
    end
  end

  should "have a git method that accesses the repository" do
    # FIXME: meh for stubbing internals, need to refactor that part in Grit
    File.expects(:exist?).at_least(1).with("#{@repository.full_repository_path}/.git").returns(false)
    File.expects(:exist?).at_least(1).with(@repository.full_repository_path).returns(true)
    assert_instance_of Grit::Repo, @repository.git
    assert_equal @repository.full_repository_path, @repository.git.path
  end

  should "have a head_candidate" do
    head_stub = mock("head")
    head_stub.stubs(:name).returns("master")
    git = mock("git backend")
    @repository.stubs(:git).returns(git)
    git.expects(:head).returns(head_stub)
    @repository.expects(:has_commits?).returns(true)

    assert_equal head_stub, @repository.head_candidate
  end

  should "have a head_candidate, unless it does not have commits" do
    @repository.expects(:has_commits?).returns(false)
    assert_equal nil, @repository.head_candidate
  end

  should "has paginated_commits" do
    git = mock("git")
    commits = [mock("commit"), mock("commit")]
    @repository.expects(:git).times(2).returns(git)
    git.expects(:commit_count).returns(120)
    git.expects(:commits).with("foo", 30, 30).returns(commits)
    commits = @repository.paginated_commits("foo", 2, 30)
    assert_instance_of WillPaginate::Collection, commits
  end

  should "has a count_commits_from_last_week_by_user of 0 if no commits" do
    @repository.expects(:has_commits?).returns(false)
    assert_equal 0, @repository.count_commits_from_last_week_by_user(users(:johan))
  end

  should "returns a set of users from a list of commits" do
    commits = []
    users(:johan, :moe).map do |u|
      committer = OpenStruct.new(:email => u.email)
      commits << OpenStruct.new(:committer => committer, :author => committer)
    end
    users = Repository.users_by_commits(commits)
    assert_equal users(:johan, :moe).map(&:email).sort, users.keys.sort
    assert_equal users(:johan, :moe).map(&:login).sort, users.values.map(&:login).sort
  end

  should "know if it is a normal project repository" do
    assert @repository.project_repo?, "@repository.project_repo? should be true"
  end

  should "know if it is a wiki repo" do
    @repository.kind = Repository::KIND_WIKI
    assert @repository.wiki?, "@repository.wiki? should be true"
  end

  should "has a parent, which is the owner" do
    @repository.kind = Repository::KIND_TEAM_REPO
    @repository.owner = groups(:team_thunderbird)
    assert_equal groups(:team_thunderbird), @repository.breadcrumb_parent

    @repository.kind = Repository::KIND_USER_REPO
    @repository.owner = users(:johan)
    assert_equal users(:johan), @repository.breadcrumb_parent
  end

  should "has a parent, which is the project for mainlines" do
    @repository.kind = Repository::KIND_PROJECT_REPO
    @repository.owner = groups(:team_thunderbird)
    assert_equal projects(:johans), @repository.breadcrumb_parent

    @repository.owner = users(:johan)
    assert_equal projects(:johans), @repository.breadcrumb_parent
  end

  should " return its name as title" do
    assert_equal @repository.title, @repository.name
  end

  should "return the project title as owner_title if it is a mainline" do
    @repository.kind = Repository::KIND_PROJECT_REPO
    assert_equal @repository.project.title, @repository.owner_title
  end

  should "return the owner title as owner_title if it is not a mainline" do
    @repository.kind = Repository::KIND_TEAM_REPO
    assert_equal @repository.owner.title, @repository.owner_title
  end

  should "returns a list of committers depending on owner type" do
    repo = repositories(:johans2)
    repo.committerships.each(&:delete)
    repo.reload
    assert !committers(repo).include?(users(:moe))

    repo.committerships.create_with_permissions!({
        :committer => users(:johan)
      }, Committership::CAN_COMMIT)

    assert_equal [users(:johan).login], committers(repo).map(&:login)

    repo.committerships.create_with_permissions!({
        :committer => groups(:team_thunderbird)
      }, Committership::CAN_COMMIT)
    exp_users = groups(:team_thunderbird).members.unshift(users(:johan))
    assert_equal exp_users.map(&:login), committers(repo).map(&:login)

    groups(:team_thunderbird).add_member(users(:moe), Role.admin)
    repo.reload
    assert committers(repo).include?(users(:moe))
  end

  should "know you can request merges from it"  do
    repo = repositories(:johans2)
    assert !repo.mainline?
    assert committer?(users(:mike), repo)
    assert can_request_merge?(users(:mike), repo)

    repo.kind = Repository::KIND_PROJECT_REPO
    assert repo.mainline?
    assert !can_request_merge?(users(:mike), repo), "mainlines should not request merges"
  end

  should "use a sharded hashed path if enable_repository_dir_sharding is toggled" do
    GitoriousConfig["enable_repository_dir_sharding"] = true
    repository = new_repos
    assert repository.new_record?, "repository.new_record? should be true"
    repository.save!
    assert_not_nil repository.hashed_path
    assert_equal 3, repository.hashed_path.split("/").length
    assert_match(/[a-z0-9\/]{42}/, repository.hashed_path)
    GitoriousConfig["enable_repository_dir_sharding"] = false
  end

  should "use repo name for path by default, not sharded hashed paths" do
    GitoriousConfig["enable_repository_dir_sharding"] = false
    repository = new_repos
    FileUtils.mkdir_p(repository.full_repository_path, :mode => 0755)
    repository.save!
    assert_equal "#{repository.project.slug}/#{repository.name}", repository.hashed_path
  end

  should "create the initial committership on create for owner" do
    group_repo = new_repos(:owner => groups(:team_thunderbird))
    assert_difference("Committership.count") do
      group_repo.save!
      assert_equal 1, group_repo.committerships.count
      assert_equal groups(:team_thunderbird), group_repo.committerships.first.committer
    end

    user_repo = new_repos(:owner => users(:johan), :name => "foo2")
    assert_difference("Committership.count") do
      user_repo.save!
      assert_equal 1, user_repo.committerships.count
      cs = user_repo.committerships.first
      assert_equal users(:johan), cs.committer
      [:reviewer?, :committer?, :admin?].each do |m|
        assert cs.send(m), "should have #{m} permissions"
      end
    end
  end

  should "know the full hashed path" do
    assert_equal @repository.hashed_path, @repository.full_hashed_path
  end

  should "allow changing ownership from a user to a group" do
    repo = repositories(:johans)
    repo.change_owner_to!(groups(:team_thunderbird))
    assert_equal groups(:team_thunderbird), repo.owner
    repo.change_owner_to!(users(:johan))
    assert_equal groups(:team_thunderbird), repo.owner
  end

  should "not change kind when it is a project repo and changing owner" do
    repo = repositories(:johans)
    repo.change_owner_to!(groups(:team_thunderbird))
    assert_equal groups(:team_thunderbird), repo.owner
    assert_equal Repository::KIND_PROJECT_REPO, repo.kind
  end

  should "change kind when changing owner" do
    repo = repositories(:johans)
    repo.update_attribute(:kind, Repository::KIND_USER_REPO)
    assert repo.user_repo?
    repo.change_owner_to!(groups(:team_thunderbird))
    assert_equal groups(:team_thunderbird), repo.owner
    assert repo.team_repo?
  end

  should "changing ownership adds the new owner to the committerships" do
    repo = repositories(:johans)
    old_committer = repo.owner
    repo.change_owner_to!(groups(:team_thunderbird))
    assert !committers(repo).include?(old_committer)
    assert committers(repo).include?(groups(:team_thunderbird).members.first)
    [:reviewer?, :committer?, :admin?].each do |m|
      assert repo.committerships.last.send(m), "cannot #{m}"
    end
  end

  should "downcases the name before validation" do
    repo = new_repos(:name => "FOOBAR")
    repo.save!
    assert_equal "foobar", repo.reload.name
  end

  should "have a project_or_owner" do
    repo = repositories(:johans)
    assert repo.project_repo?
    assert_equal repo.project, repo.project_or_owner

    repo.kind = Repository::KIND_TEAM_REPO
    repo.owner = groups(:team_thunderbird)
    assert_equal repo.owner, repo.project_or_owner

    repo.kind = Repository::KIND_TEAM_REPO
    repo.owner = groups(:team_thunderbird)
    assert_equal repo.owner, repo.project_or_owner
  end

  context "participant groups" do
    setup do
      @repo = repositories(:moes)
    end

    should "includes the groups' members in #committers" do
      assert committers(@repo).include?(groups(:team_thunderbird).members.first)
    end

    should "only include unique users in #committers" do
      groups(:team_thunderbird).add_member(users(:moe), Role.member)
      assert_equal 1, committers(@repo).select{|u| u == users(:moe)}.size
    end

    should "not include committerships without a commit permission bit" do
      assert_equal 1, @repo.committerships.count
      cs = @repo.committerships.first
      cs.build_permissions(:review)
      cs.save!
      assert_equal [], committers(@repo).map(&:login)
    end

    should "return a list of reviewers" do
      assert !reviewers(@repo).map(&:login).include?(users(:moe).login)
      @repo.committerships.create_with_permissions!({
          :committer => users(:moe)
        }, Committership::CAN_REVIEW)
      assert reviewers(@repo).map(&:login).include?(users(:moe).login)
    end

    context "permission helpers" do
      setup do
        @cs = @repo.committerships.first
        @cs.permissions = 0
        @cs.save!
      end
<commit_msg>Fix failing tests due to association caching<commit_after># encoding: utf-8
#--
#   Copyright (C) 2012 Gitorious AS
#   Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU Affero General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU Affero General Public License for more details.
#
#   You should have received a copy of the GNU Affero General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
#++

require "test_helper"
require "ostruct"

class RepositoryTest < ActiveSupport::TestCase
  def new_repos(opts={})
    Repository.new({
      :name => "foo",
      :project => projects(:johans),
      :user => users(:johan),
      :owner => users(:johan)
    }.merge(opts))
  end

  def setup
    @repository = new_repos
    FileUtils.mkdir_p(@repository.full_repository_path, :mode => 0755)
  end

  def teardown
    clear_message_queue
  end

  should "only accept names with alphanum characters in it" do
    @repository.name = "foo bar"
    assert !@repository.valid?, "valid? should be false"

    @repository.name = "foo!bar"
    assert !@repository.valid?, "valid? should be false"

    @repository.name = "foobar"
    assert @repository.valid?

    @repository.name = "foo42"
    assert @repository.valid?
  end

  should "has a unique name within a project" do
    @repository.save
    repos = new_repos(:name => "FOO")
    assert !repos.valid?, "valid? should be false"
    assert_not_nil repos.errors[:name]

    assert new_repos(:project => projects(:moes)).valid?
  end

  should "not have a reserved name" do
    repo = new_repos(:name => Gitorious::Reservations.repository_names.first.dup)
    repo.valid?
    assert_not_nil repo.errors[:name]
    RepositoriesController.action_methods.each do |action|
      repo.name = action.dup
      repo.valid?
      assert_not_nil repo.errors[:name], "fail on #{action}"
    end
  end

  context "git urls" do
    setup do
      @host_with_user = "#{GitoriousConfig['gitorious_user']}@#{GitoriousConfig['gitorious_host']}"
      @host = "#{GitoriousConfig['gitorious_host']}"
    end

    should "has a gitdir name" do
      assert_equal "#{@repository.project.slug}/foo.git", @repository.gitdir
    end

    should "has a push url" do
      assert_equal "#{@host_with_user}:#{@repository.project.slug}/foo.git", @repository.push_url
    end

    should "has a clone url" do
      assert_equal "git://#{@host}/#{@repository.project.slug}/foo.git", @repository.clone_url
    end

    should "has a http url" do
      assert_equal "#{GitoriousConfig['scheme']}://git.#{@host}/#{@repository.project.slug}/foo.git", @repository.http_clone_url
    end

    should "use the real http cloning URL" do
      old_value = Site::HTTP_CLONING_SUBDOMAIN
      silence_warnings do
        Site::HTTP_CLONING_SUBDOMAIN = "whatever"
      end
      assert_equal "#{GitoriousConfig['scheme']}://whatever.#{@host}/#{@repository.project.slug}/foo.git", @repository.http_clone_url
      silence_warnings {Site::HTTP_CLONING_SUBDOMAIN = old_value}
    end

    should "has a clone url with the project name, if it is a mainline" do
      @repository.owner = groups(:team_thunderbird)
      @repository.kind = Repository::KIND_PROJECT_REPO
      assert_equal "git://#{@host}/#{@repository.project.slug}/foo.git", @repository.clone_url
    end

    should "have a clone url with the team/user, if it is not a mainline" do
      @repository.kind = Repository::KIND_TEAM_REPO
      url = "git://#{@host}/#{@repository.owner.to_param_with_prefix}/#{@repository.project.slug}/foo.git"
      assert_equal url, @repository.clone_url

      @repository.kind = Repository::KIND_USER_REPO
      @repository.owner = users(:johan)
      url = "git://#{@host}/#{users(:johan).to_param_with_prefix}/#{@repository.project.slug}/foo.git"
      assert_equal url, @repository.clone_url
    end

    should "has a push url with the project name, if it is a mainline" do
      @repository.owner = groups(:team_thunderbird)
      @repository.kind = Repository::KIND_PROJECT_REPO
      assert_equal "#{@host_with_user}:#{@repository.project.slug}/foo.git", @repository.push_url
    end

    should "have a push url with the team/user, if it is not a mainline" do
      @repository.owner = groups(:team_thunderbird)
      @repository.kind = Repository::KIND_TEAM_REPO
      url = "#{@host_with_user}:#{groups(:team_thunderbird).to_param_with_prefix}/#{@repository.project.slug}/foo.git"
      assert_equal url, @repository.push_url

      @repository.kind = Repository::KIND_USER_REPO
      @repository.owner = users(:johan)
      url = "#{@host_with_user}:#{users(:johan).to_param_with_prefix}/#{@repository.project.slug}/foo.git"
      assert_equal url, @repository.push_url
    end

    should "has a http clone url with the project name, if it is a mainline" do
      @repository.owner = groups(:team_thunderbird)
      @repository.kind = Repository::KIND_PROJECT_REPO
      assert_equal "#{GitoriousConfig['scheme']}://git.#{@host}/#{@repository.project.slug}/foo.git", @repository.http_clone_url
    end

    should "have a http clone url with the team/user, if it is not a mainline" do
      @repository.owner = groups(:team_thunderbird)
      @repository.kind = Repository::KIND_TEAM_REPO
      url = "#{GitoriousConfig['scheme']}://git.#{@host}/#{groups(:team_thunderbird).to_param_with_prefix}/#{@repository.project.slug}/foo.git"
      assert_equal url, @repository.http_clone_url

      @repository.owner = users(:johan)
      @repository.kind = Repository::KIND_USER_REPO
      url = "#{GitoriousConfig['scheme']}://git.#{@host}/#{users(:johan).to_param_with_prefix}/#{@repository.project.slug}/foo.git"
      assert_equal url, @repository.http_clone_url
    end

    should "has a full repository_path" do
      expected_dir = File.expand_path(File.join(RepositoryRoot.default_base_path,
        "#{@repository.full_hashed_path}.git"))
      assert_equal expected_dir, @repository.full_repository_path
    end

    should "always display SSH URLs when so instructed" do
      old_value = GitoriousConfig["always_display_ssh_url"]
      GitoriousConfig["always_display_ssh_url"] = true
      assert @repository.display_ssh_url?(users(:moe))
      GitoriousConfig["always_display_ssh_url"] = old_value
    end
  end

  should "inits the git repository" do
    path = @repository.full_repository_path
    Repository.git_backend.expects(:create).with(path).returns(true)
    Repository.create_git_repository(@repository.real_gitdir)

    assert File.exist?(path), "File.exist?(path) should be true"

    Dir.chdir(path) do
      hooks = File.join(path, "hooks")
      assert File.exist?(hooks), "File.exist?(hooks) should be true"
      assert File.symlink?(hooks), "File.symlink?(hooks) should be true"
      assert File.symlink?(File.expand_path(File.readlink(hooks))), "File.symlink?(File.expand_path(File.readlink(hooks))) should be true"
    end
  end

  should "clones a git repository" do
    source = repositories(:johans)
    target = @repository
    target_path = @repository.full_repository_path

    git_backend = mock("Git backend")
    Repository.expects(:git_backend).returns(git_backend)
    git_backend.expects(:clone).with(target.full_repository_path,
      source.full_repository_path).returns(true)
    Repository.expects(:create_hooks).returns(true)

    assert Repository.clone_git_repository(target.real_gitdir, source.real_gitdir)
  end

  should "not create hooks if the :skip_hooks option is set to true" do
    source = repositories(:johans)
    target = @repository
    target_path = @repository.full_repository_path

    git_backend = mock("Git backend")
    Repository.expects(:git_backend).returns(git_backend)
    git_backend.expects(:clone).with(target.full_repository_path,
      source.full_repository_path).returns(true)
    Repository.expects(:create_hooks).never

    Repository.clone_git_repository(target.real_gitdir, source.real_gitdir, :skip_hooks => true)
  end

  should "create the hooks" do
    hooks = "/path/to/hooks"
    path = "/path/to/repository"
    base_path = "#{Rails.root}/data/hooks"

    File.expects(:join).in_sequence.with(RepositoryRoot.default_base_path, ".hooks").returns(hooks)

    Dir.expects(:chdir).in_sequence.with(path).yields(nil)

    File.expects(:symlink?).in_sequence.with(hooks).returns(false)
    File.expects(:exist?).in_sequence.with(hooks).returns(false)
    FileUtils.expects(:ln_s).in_sequence.with(base_path, hooks)

    local_hooks = "/path/to/local/hooks"
    File.expects(:join).in_sequence.with(path, "hooks").returns(local_hooks)
    File.expects(:exist?).in_sequence.with(local_hooks).returns(true)

    Repository.create_hooks(path)
  end

  should "deletes a repository" do
    Repository.git_backend.expects(:delete!).with(@repository.full_repository_path).returns(true)
    Repository.delete_git_repository(@repository.real_gitdir)
  end

  should "knows if has commits" do
    @repository.stubs(:new_record?).returns(false)
    @repository.stubs(:ready?).returns(true)
    git_mock = mock("Grit::Git")
    @repository.stubs(:git).returns(git_mock)
    head = mock("head")
    head.stubs(:name).returns("master")
    @repository.git.expects(:heads).returns([head])
    assert @repository.has_commits?, "@repository.has_commits? should be true"
  end

  should "knows if has commits, unless its a new record" do
    @repository.stubs(:new_record?).returns(false)
    assert !@repository.has_commits?, "@repository.has_commits? should be false"
  end

  should "knows if has commits, unless its not ready" do
    @repository.stubs(:ready?).returns(false)
    assert !@repository.has_commits?, "@repository.has_commits? should be false"
  end

  should " build a new repository by cloning another one" do
    repos = Repository.new_by_cloning(@repository)
    assert_equal @repository, repos.parent
    assert_equal @repository.project, repos.project
    assert repos.new_record?, "new_record? should be true"
  end

  should "inherit merge request inclusion from its parent" do
    @repository.update_attribute(:merge_requests_enabled, true)
    clone = Repository.new_by_cloning(@repository)
    assert clone.merge_requests_enabled?
    @repository.update_attribute(:merge_requests_enabled, false)
    clone = Repository.new_by_cloning(@repository)
    assert !clone.merge_requests_enabled?
  end

  should "suggests a decent name for a cloned repository bsed on username" do
    repos = Repository.new_by_cloning(@repository, username="johan")
    assert_equal "johans-foo", repos.name
    repos = Repository.new_by_cloning(@repository, username=nil)
    assert_equal nil, repos.name
  end

  should "has it is name as its to_param value" do
    @repository.save
    assert_equal @repository.name, @repository.to_param
  end

  should "finds a repository by name or raises" do
    assert_equal repositories(:johans), Repository.find_by_name!(repositories(:johans).name)
    assert_raises(ActiveRecord::RecordNotFound) do
      Repository.find_by_name!("asdasdasd")
    end
  end

  context "find_by_path" do
    should "finds a repository by its path" do
      repo = repositories(:johans)
      path = File.join(RepositoryRoot.default_base_path,
                        projects(:johans).slug, "#{repo.name}.git")
      assert_equal repo, Repository.find_by_path(path)
    end

    should_eventually "finds a repository by its path, regardless of repository kind" do
      repo = projects(:johans).wiki_repository
      path = File.join(RepositoryRoot.default_base_path.chomp("/"),
                        projects(:johans).slug, "#{repo.name}.git")
      assert_equal repo, Repository.find_by_path(path)
    end

    should "finds a group repository by its path" do
      repo = repositories(:johans)
      repo.owner = groups(:team_thunderbird)
      repo.kind = Repository::KIND_TEAM_REPO
      repo.save!
      path = File.join(RepositoryRoot.default_base_path, repo.gitdir)
      assert_equal repo, Repository.find_by_path(path)
    end

    should "finds a user repository by its path" do
      repo = repositories(:johans)
      repo.owner = users(:johan)
      repo.kind = Repository::KIND_USER_REPO
      repo.save!
      path = File.join(RepositoryRoot.default_base_path, repo.gitdir)
      assert_equal repo, Repository.find_by_path(path)
    end

    should "scope the find by project id" do
      repo = repositories(:johans)
      repo.owner = groups(:team_thunderbird)
      repo.kind = Repository::KIND_TEAM_REPO
      repo.save!
      same_name_repo = new_repos(:name => repo.name)
      same_name_repo
      same_name_repo.project = projects(:moes)
      same_name_repo.owner = groups(:team_thunderbird)
      same_name_repo.kind = Repository::KIND_TEAM_REPO
      same_name_repo.save!
      path = File.join(RepositoryRoot.default_base_path, same_name_repo.gitdir)
      assert_equal same_name_repo, Repository.find_by_path(path)
    end
  end

  context "#to_xml" do
    should "xmlilizes git paths as well" do
      assert @repository.to_xml.include?("<clone-url>")
      assert @repository.to_xml.include?("<push-url>")
    end

    should "include a description of the kind" do
      assert_match(/<kind>mainline<\/kind>/, @repository.to_xml)
      @repository.kind = Repository::KIND_TEAM_REPO
      assert_match(/<kind>team<\/kind>/, @repository.to_xml)
    end

    should "include the project name" do
      assert_match(/<project>#{@repository.project.slug}<\/project>/, @repository.to_xml)
    end

    should "include the owner" do
      assert_match(/<owner kind="User">johan<\/owner>/, @repository.to_xml)
    end
  end

  context "can_push?" do
    should "knows if a user can write to self" do
      @repository.owner = users(:johan)
      @repository.save!
      @repository.reload
      assert can_push?(users(:johan), @repository)
      assert !can_push?(users(:mike), @repository)

      @repository.change_owner_to!(groups(:team_thunderbird))
      @repository.save!
      assert !can_push?(users(:johan), @repository)

      @repository.owner.add_member(users(:moe), Role.member)
      @repository.committerships.reload
      assert can_push?(users(:moe), @repository)
    end

    context "a wiki repository" do
      setup do
        @repository.kind = Repository::KIND_WIKI
      end

      should "be writable by everyone" do
        @repository.wiki_permissions = Repository::WIKI_WRITABLE_EVERYONE
        [:johan, :mike, :moe].each do |login|
          assert can_push?(users(login), @repository), "not writable_by #{login}"
        end
      end

      should "only be writable by project members" do
        @repository.wiki_permissions = Repository::WIKI_WRITABLE_PROJECT_MEMBERS
        assert @repository.project.member?(users(:johan))
        assert can_push?(users(:johan), @repository)

        assert !@repository.project.member?(users(:moe))
        assert !can_push?(users(:moe), @repository)
      end
    end
  end

  should "publishe a message on create and update" do
    @repository.save!

    assert_published("/queue/GitoriousRepositoryCreation", {
                       "command" => "create_git_repository",
                       "target_id" => @repository.id,
                       "arguments" => [@repository.real_gitdir]
                     })
  end

  should "publishe a message on clone" do
    @repository.parent = repositories(:johans)
    @repository.save!

    assert_published("/queue/GitoriousRepositoryCreation", {
                       "command" => "clone_git_repository",
                       "target_id" => @repository.id,
                       "arguments" => [@repository.real_gitdir,
                                       @repository.parent.real_gitdir]
                     })
  end

  should "create a notification on destroy" do
    @repository.save!
    @repository.destroy

    assert_published("/queue/GitoriousRepositoryDeletion", {
                       "command" => "delete_git_repository",
                       "arguments" => [@repository.real_gitdir]
                     })
  end

  should "has one recent commit" do
    @repository.save!
    repos_mock = mock("Git mock")
    commit_mock = stub_everything("Git::Commit mock")
    repos_mock.expects(:commits).with("master", 1).returns([commit_mock])
    @repository.stubs(:git).returns(repos_mock)
    @repository.stubs(:has_commits?).returns(true)
    heads_stub = mock("head")
    heads_stub.stubs(:name).returns("master")
    @repository.stubs(:head_candidate).returns(heads_stub)
    assert_equal commit_mock, @repository.last_commit
  end

  should "has one recent commit within a given ref" do
    @repository.save!
    repos_mock = mock("Git mock")
    commit_mock = stub_everything("Git::Commit mock")
    repos_mock.expects(:commits).with("foo", 1).returns([commit_mock])
    @repository.stubs(:git).returns(repos_mock)
    @repository.stubs(:has_commits?).returns(true)
    @repository.expects(:head_candidate).never
    assert_equal commit_mock, @repository.last_commit("foo")
  end

  context "deletion" do
    setup do
      @repository.kind = Repository::KIND_PROJECT_REPO
      @repository.project.repositories << new_repos(:name => "another")
      @repository.save!
      @repository.committerships.create!({
        :committer => users(:moe),
        :permissions => (Committership::CAN_REVIEW | Committership::CAN_COMMIT)
      })
    end

    should "be deletable by admins" do
      assert admin?(users(:johan), @repository)
      assert !admin?(users(:moe), @repository)

      assert can_delete?(users(:johan), @repository)
      assert !can_delete?(users(:moe), @repository)
    end

    should "always be deletable if admin and non-project repo" do
      @repository.kind = Repository::KIND_TEAM_REPO
      assert can_delete?(users(:johan), @repository)
      assert !can_delete?(users(:moe), @repository)
    end

    should "also be deletable by users with admin privs" do
      @repository.kind = Repository::KIND_TEAM_REPO
      cs = @repository.committerships.create_with_permissions!({
          :committer => users(:mike)
        }, Committership::CAN_ADMIN)
      assert can_delete?(users(:johan), @repository)
      assert can_delete?(users(:mike), @repository)
    end
  end

  should "have a git method that accesses the repository" do
    # FIXME: meh for stubbing internals, need to refactor that part in Grit
    File.expects(:exist?).at_least(1).with("#{@repository.full_repository_path}/.git").returns(false)
    File.expects(:exist?).at_least(1).with(@repository.full_repository_path).returns(true)
    assert_instance_of Grit::Repo, @repository.git
    assert_equal @repository.full_repository_path, @repository.git.path
  end

  should "have a head_candidate" do
    head_stub = mock("head")
    head_stub.stubs(:name).returns("master")
    git = mock("git backend")
    @repository.stubs(:git).returns(git)
    git.expects(:head).returns(head_stub)
    @repository.expects(:has_commits?).returns(true)

    assert_equal head_stub, @repository.head_candidate
  end

  should "have a head_candidate, unless it does not have commits" do
    @repository.expects(:has_commits?).returns(false)
    assert_equal nil, @repository.head_candidate
  end

  should "has paginated_commits" do
    git = mock("git")
    commits = [mock("commit"), mock("commit")]
    @repository.expects(:git).times(2).returns(git)
    git.expects(:commit_count).returns(120)
    git.expects(:commits).with("foo", 30, 30).returns(commits)
    commits = @repository.paginated_commits("foo", 2, 30)
    assert_instance_of WillPaginate::Collection, commits
  end

  should "has a count_commits_from_last_week_by_user of 0 if no commits" do
    @repository.expects(:has_commits?).returns(false)
    assert_equal 0, @repository.count_commits_from_last_week_by_user(users(:johan))
  end

  should "returns a set of users from a list of commits" do
    commits = []
    users(:johan, :moe).map do |u|
      committer = OpenStruct.new(:email => u.email)
      commits << OpenStruct.new(:committer => committer, :author => committer)
    end
    users = Repository.users_by_commits(commits)
    assert_equal users(:johan, :moe).map(&:email).sort, users.keys.sort
    assert_equal users(:johan, :moe).map(&:login).sort, users.values.map(&:login).sort
  end

  should "know if it is a normal project repository" do
    assert @repository.project_repo?, "@repository.project_repo? should be true"
  end

  should "know if it is a wiki repo" do
    @repository.kind = Repository::KIND_WIKI
    assert @repository.wiki?, "@repository.wiki? should be true"
  end

  should "has a parent, which is the owner" do
    @repository.kind = Repository::KIND_TEAM_REPO
    @repository.owner = groups(:team_thunderbird)
    assert_equal groups(:team_thunderbird), @repository.breadcrumb_parent

    @repository.kind = Repository::KIND_USER_REPO
    @repository.owner = users(:johan)
    assert_equal users(:johan), @repository.breadcrumb_parent
  end

  should "has a parent, which is the project for mainlines" do
    @repository.kind = Repository::KIND_PROJECT_REPO
    @repository.owner = groups(:team_thunderbird)
    assert_equal projects(:johans), @repository.breadcrumb_parent

    @repository.owner = users(:johan)
    assert_equal projects(:johans), @repository.breadcrumb_parent
  end

  should " return its name as title" do
    assert_equal @repository.title, @repository.name
  end

  should "return the project title as owner_title if it is a mainline" do
    @repository.kind = Repository::KIND_PROJECT_REPO
    assert_equal @repository.project.title, @repository.owner_title
  end

  should "return the owner title as owner_title if it is not a mainline" do
    @repository.kind = Repository::KIND_TEAM_REPO
    assert_equal @repository.owner.title, @repository.owner_title
  end

  should "returns a list of committers depending on owner type" do
    repo = repositories(:johans2)
    repo.committerships.each(&:delete)
    repo.reload
    assert !committers(repo).include?(users(:moe))

    repo.committerships.create_with_permissions!({
        :committer => users(:johan)
      }, Committership::CAN_COMMIT)

    assert_equal [users(:johan).login], committers(repo.reload).map(&:login)

    repo.committerships.create_with_permissions!({
        :committer => groups(:team_thunderbird)
      }, Committership::CAN_COMMIT)
    exp_users = groups(:team_thunderbird).members.unshift(users(:johan))
    assert_equal exp_users.map(&:login), committers(repo.reload).map(&:login)

    groups(:team_thunderbird).add_member(users(:moe), Role.admin)
    repo.reload
    assert committers(repo).include?(users(:moe))
  end

  should "know you can request merges from it"  do
    repo = repositories(:johans2)
    assert !repo.mainline?
    assert committer?(users(:mike), repo)
    assert can_request_merge?(users(:mike), repo)

    repo.kind = Repository::KIND_PROJECT_REPO
    assert repo.mainline?
    assert !can_request_merge?(users(:mike), repo), "mainlines should not request merges"
  end

  should "use a sharded hashed path if enable_repository_dir_sharding is toggled" do
    GitoriousConfig["enable_repository_dir_sharding"] = true
    repository = new_repos
    assert repository.new_record?, "repository.new_record? should be true"
    repository.save!
    assert_not_nil repository.hashed_path
    assert_equal 3, repository.hashed_path.split("/").length
    assert_match(/[a-z0-9\/]{42}/, repository.hashed_path)
    GitoriousConfig["enable_repository_dir_sharding"] = false
  end

  should "use repo name for path by default, not sharded hashed paths" do
    GitoriousConfig["enable_repository_dir_sharding"] = false
    repository = new_repos
    FileUtils.mkdir_p(repository.full_repository_path, :mode => 0755)
    repository.save!
    assert_equal "#{repository.project.slug}/#{repository.name}", repository.hashed_path
  end

  should "create the initial committership on create for owner" do
    group_repo = new_repos(:owner => groups(:team_thunderbird))
    assert_difference("Committership.count") do
      group_repo.save!
      assert_equal 1, group_repo.committerships.count
      assert_equal groups(:team_thunderbird), group_repo.committerships.first.committer
    end

    user_repo = new_repos(:owner => users(:johan), :name => "foo2")
    assert_difference("Committership.count") do
      user_repo.save!
      assert_equal 1, user_repo.committerships.count
      cs = user_repo.committerships.first
      assert_equal users(:johan), cs.committer
      [:reviewer?, :committer?, :admin?].each do |m|
        assert cs.send(m), "should have #{m} permissions"
      end
    end
  end

  should "know the full hashed path" do
    assert_equal @repository.hashed_path, @repository.full_hashed_path
  end

  should "allow changing ownership from a user to a group" do
    repo = repositories(:johans)
    repo.change_owner_to!(groups(:team_thunderbird))
    assert_equal groups(:team_thunderbird), repo.owner
    repo.change_owner_to!(users(:johan))
    assert_equal groups(:team_thunderbird), repo.owner
  end

  should "not change kind when it is a project repo and changing owner" do
    repo = repositories(:johans)
    repo.change_owner_to!(groups(:team_thunderbird))
    assert_equal groups(:team_thunderbird), repo.owner
    assert_equal Repository::KIND_PROJECT_REPO, repo.kind
  end

  should "change kind when changing owner" do
    repo = repositories(:johans)
    repo.update_attribute(:kind, Repository::KIND_USER_REPO)
    assert repo.user_repo?
    repo.change_owner_to!(groups(:team_thunderbird))
    assert_equal groups(:team_thunderbird), repo.owner
    assert repo.team_repo?
  end

  should "changing ownership adds the new owner to the committerships" do
    repo = repositories(:johans)
    old_committer = repo.owner
    repo.change_owner_to!(groups(:team_thunderbird))
    assert !committers(repo).include?(old_committer)
    assert committers(repo).include?(groups(:team_thunderbird).members.first)
    [:reviewer?, :committer?, :admin?].each do |m|
      assert repo.committerships.last.send(m), "cannot #{m}"
    end
  end

  should "downcases the name before validation" do
    repo = new_repos(:name => "FOOBAR")
    repo.save!
    assert_equal "foobar", repo.reload.name
  end

  should "have a project_or_owner" do
    repo = repositories(:johans)
    assert repo.project_repo?
    assert_equal repo.project, repo.project_or_owner

    repo.kind = Repository::KIND_TEAM_REPO
    repo.owner = groups(:team_thunderbird)
    assert_equal repo.owner, repo.project_or_owner

    repo.kind = Repository::KIND_TEAM_REPO
    repo.owner = groups(:team_thunderbird)
    assert_equal repo.owner, repo.project_or_owner
  end

  context "participant groups" do
    setup do
      @repo = repositories(:moes)
    end

    should "includes the groups' members in #committers" do
      assert committers(@repo).include?(groups(:team_thunderbird).members.first)
    end

    should "only include unique users in #committers" do
      groups(:team_thunderbird).add_member(users(:moe), Role.member)
      assert_equal 1, committers(@repo).select{|u| u == users(:moe)}.size
    end

    should "not include committerships without a commit permission bit" do
      assert_equal 1, @repo.committerships.count
      cs = @repo.committerships.first
      cs.build_permissions(:review)
      cs.save!
      assert_equal [], committers(@repo).map(&:login)
    end

    should "return a list of reviewers" do
      assert !reviewers(@repo).map(&:login).include?(users(:moe).login)
      @repo.committerships.create_with_permissions!({
          :committer => users(:moe)
        }, Committership::CAN_REVIEW)
      assert reviewers(@repo.reload).map(&:login).include?(users(:moe).login)
    end

    context "permission helpers" do
      setup do
        @cs = @repo.committerships.first
        @cs.permissions = 0
        @cs.save!
      end

{'fraction_non_alphanumeric': 0.11465992647058823, 'fraction_numerical': 0.0017675339366515837, 'mean_word_length': 3.782502113271344, 'pattern_counts': {'":': 0, '<': 35, '<?xml version=': 0, '>': 91, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 16}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1979163', 'n_tokens_mistral': 17980, 'n_tokens_neox': 17528, 'n_words': 3882}