<commit_before>require 'rugged'
require 'gitolite'
require 'digest/md5'
require 'rubygems'
require 'git'

require File.join(File.dirname(__FILE__),'repository') # load repository module

def commit_options(repo, author, message)
  {
    author:  { email: '<EMAIL>', name: author, time: Time.now },
    committer: { email: '<EMAIL>', name: author, time: Time.now },
    message: message,
    tree: repo.index.write_tree(repo),
    parents: repo.empty? ? [] : [repo.head.target].compact,
    update_ref: 'HEAD'
  }
end

module Repository

  # Implements AbstractRepository for Git repositories
  # It implements the following paradigm:
  #   1. Repositories are created by using ???
  #   2. Existing repositories are opened by using either ???
  class GitRepository < Repository::AbstractRepository

    # Constructor: Connects to an existing Git
    # repository, using Ruby bindings; Note: A repository has to be
    # created using GitRespository.create(), it it is not yet existent
    def initialize(connect_string)
      # Check if configuration is in order
      if Repository.conf[:IS_REPOSITORY_ADMIN].nil?
        raise ConfigurationError.new(
                  "Required config 'IS_REPOSITORY_ADMIN' not set")
      end
      if Repository.conf[:REPOSITORY_STORAGE].nil?
        raise ConfigurationError.new(
                  "Required config 'REPOSITORY_STORAGE' not set")
      end
      if Repository.conf[:REPOSITORY_PERMISSION_FILE].nil?
        raise ConfigurationError.new(
                  "Required config 'REPOSITORY_PERMISSION_FILE' not set")
      end
      begin
        super(connect_string) # dummy call to super
      rescue NotImplementedError; end
      @repos_path = connect_string
      @closed = false
      @repos_admin = Repository.conf[:IS_REPOSITORY_ADMIN]
      if (GitRepository.repository_exists?(@repos_path))

        # make sure working directory is up-to-date
        g = Git.open(@repos_path)
        g.pull

        @repos = Rugged::Repository.new(@repos_path)
      else
        raise "Repository does not exist at path \"" + @repos_path + "\""
      end
    end

    # Static method: Creates a new Git repository at
    # location 'connect_string'
    def self.create(connect_string)
      if GitRepository.repository_exists?(connect_string)
        raise RepositoryCollision.new(
                  "There is already a repository at #{connect_string}")
      end
      if File.exists?(connect_string)
        raise IOError.new("Could not create a repository at #{connect_string}:
                          some directory with same name exists already")
      end

      ga_repo = Gitolite::GitoliteAdmin.new(
        Repository.conf[:REPOSITORY_STORAGE] +
          '/gitolite-admin', GITOLITE_SETTINGS)

      # Bring the repo up to date
      ga_repo.reload!

      # Grab the gitolite admin repo config
      conf = ga_repo.config

      repo_name = File.basename(connect_string.split('/').last)

      # Grab the repo in question, if it does not exist, create it
      repo = ga_repo.config.get_repo(repo_name)
      if repo.nil?
        # Generate new repo since this repo hasn't been created yet
        repo = Gitolite::Config::Repo.new(repo_name)
      end

      # Add permissions for git user
      repo.add_permission('RW+', '', 'git')

      # Add the repo to the gitolite admin config
      conf.add_repo(repo)

      # Readd the 'git' public key to the gitolite admin repo after changes
      self.class.readd_admin_key

      # Stage and push the changes to the gitolite admin repo
      ga_repo.save_and_apply

      # Repo is created by gitolite, proceed to clone it in
      # the repository storage location
      cloned_repo = Git.clone(
        'git@localhost:' + repo_name, REPOSITORY_STORAGE + '/' + repo_name)

      repo = Rugged::Repository.discover(REPOSITORY_STORAGE + '/' + repo_name)

      # Lets make some sample files and the new master branch
      cloned_repo.reset
      cloned_repo.branch('master')

      # Create the README.md on the filesystem
      file_path_for_readme = File.join(repo.workdir, 'README.md')
      File.open(file_path_for_readme, 'w+') do |readme|
        readme.write('Initial commit.')
      end

      cloned_repo.add(all: true)

      cloned_repo.commit_all('Initial commit.')
      cloned_repo.push

      # Finished cloning repo

      repo = Rugged::Repository.discover(REPOSITORY_STORAGE + '/' + repo_name)

      # Do an initial commit with a README to create index.
      file_path_for_readme = File.join(repo.workdir, 'README.md')
      File.open(file_path_for_readme, 'w+') do |readme|
        readme.write('Initial commit.')
      end
      oid = Rugged::Blob.from_workdir(repo, 'README.md')
      index = repo.index
      index.add(path: 'README.md', oid: oid, mode: 0100644)
      index.write
      Rugged::Commit.create(
        repo,
        commit_options(
                repo,
                'Markus',
                'Initial readme commit.'
        )
      )

      cloned_repo.push
      return true
    end

    # Static method: Opens an existing Git repository
    # at location 'connect_string'
    def self.open(connect_string)
      repo = GitRepository.new(connect_string)
    end

    # static method that should yeild to a git repo and then close it
    def self.access(connect_string)
      repo = self.open(connect_string)
      yield repo
    end

    # static method that deletes the git repo
    # rm everything? or only .git?
    def self.delete(repo_path)
      #repo = Rugged::Repository.new(repo_path)
      #ref = Rugged::Reference.lookup(repo, "refs/heads/master")
      #ref.delete!
      FileUtils.rm_rf(repo_path)
    end

    # Given a OID of a file from a Rugged::Repository lookup, return the blob
    # object of the file itself.
    def get_blob(oid)
      @repos.lookup(oid)
    end

    # Exports git repo to a new folder (clone repository)
    # If a filepath is given, the repo_dest_dir needs to point to a file, and
    # all the repository on that path need to exist, or the export will fail.
    # Exports git repo to a new folder (clone repository)
    # If a filepath is given, the repo_dest_dir needs to point to a file, and
    # all the repository on that path need to exist, or the export will fail.
    # if export means exporting repo as zip/tgz git-ruby library should be used
    def export(repo_dest_dir, filepath = nil)

      # Case 1: clone all the repo to repo_dest_dir
      if(filepath.nil?)
        # Raise an error if the destination repository already exists
        if (File.exists?(repo_dest_dir))
          raise(ExportRepositoryAlreadyExists,
                'Exported repository already exists')
        end

        repo = Rugged::Repository.clone_at(@repos_path, repo_dest_dir)
      else
        # Case 2: clone a file to a folder
        # Raise an error if the destination file already exists
        if (File.exists?(repo_dest_dir))
          raise(ExportRepositoryAlreadyExists,
                'Exported file already exists')
        end
        FileUtils.cp(get_repos_workdir + filepath, repo_dest_dir)
        return true
      end

    end

    #  Converts a pathname to an absolute pathname
    def expand_path(file_name, dir_string = '/')
      expanded = File.expand_path(file_name, dir_string)
      if RUBY_PLATFORM =~ /(:?mswin|mingw)/ #only if the platform is Windows
        expanded = expanded[2..-1]#remove the drive letter
      end
      expanded
    end

    def self.closable?
      # return if the git library supports close,
      # probably going to need to be a dumby method
    end

    def close
      # closes the git repo
      @repos.close
      @closed = true
    end

    def closed?
      # checks if the repo is closed
      return @closed
    end

    def get_repos
      # Get rugged repository from GitRepository
      return @repos
    end

    def get_repos_workdir
      # Get work directory from GitRepository
      # workdir = path/to/my/repository/
      return @repos.workdir
    end

    def get_repos_path
      # Get Rugged repository from GitRepository
      # workdir = path/to/my/repository/.git
      return @repos.path
    end

    # Given a File object, perform a lookup for the Rugged::Tree
    # object which contains the directory information for the
    # folder where this file resides, then return the specific
    # file contents that we are interested in.
    def stringify(file)
      revision = get_revision(file.from_revision)
      blob = revision.find_object_at_path(file.path)

      # From the returned Tree blob, find the file in the collection
      blob.entries.each do |file_entry|
        if file_entry[:name] == file.name
          return get_blob(file_entry[:oid]).content
        end
      end
    end
    alias download_as_string stringify # create alias

    # Static method: Reports if a Git repository exists.
    # Done in a similarly hacky method as the git side.
    # TODO - find a better way to do this.
    def self.repository_exists?(repos_path)
      repos_meta_files_exist = false
      if File.exist?(File.join(repos_path, '.git/config'))
        if File.exist?(File.join(repos_path, '.git/description'))
          if File.exist?(File.join(repos_path, '.git/HEAD'))
            repos_meta_files_exist = true
          end
        end
      end
      return repos_meta_files_exist
    end

    def get_revision_number(hash)
      # This functions walks down git log and counts the steps from beginning
      # to get the revision number.
      walker = Rugged::Walker.new(@repos)
      walker.sorting(Rugged::SORT_DATE | Rugged::SORT_REVERSE)
      walker.push(hash)
      start = 0
      walker.each do |commit|
        start += 1
        break if commit.oid == hash
      end
      return start
    end

    # Returns a Repository::SubversionRevision instance
    # holding the latest Subversion repository revision
    # number
    def get_latest_revision
      return get_revision(latest_revision_number)
    end

    # Returns hash wrapped
    # as a Git instance
    def get_revision(revision_number)
      return Repository::GitRevision.new(revision_number, self)
    end

    def get_all_revisions
      youngest_revision = latest_revision_number
      log = []
      (1..youngest_revision).each do |num|
        log.push(Repository::GitRevision.new(num, self))
      end
      return log
    end

    def get_revision_by_timestamp(target_timestamp, _path = nil)
      # returns a Git instance representing the revision at the
      # current timestamp, should be a ruby time stamp instance
      walker = Rugged::Walker.new(self.get_repos)
      walker.push(self.get_repos.head.target)
      walker.each do |c|
        if c.time <= target_timestamp
          @revision_number = get_revision_number(c)
          return get_revision(@revision_number)
        end
      end
      # If no revision number was found, display the latest revision
      # with an error message
      raise 'No revision found before supplied timestamp.'
    end

    # Returns a Repository::TransAction object, to work with. Do operations,
    # like 'add', 'remove', etc. on the transaction instead of the repository
    def get_transaction(user_id, comment = '')
      if user_id.nil?
        raise 'Expected a user_id (Repository.get_transaction(user_id))'
      end
      return Repository::Transaction.new(user_id, comment)
    end

    def commit(transaction)
      # Carries out actions on a Git repository stored in
      # 'transaction'. In case of certain conflicts corresponding
      # Repositor::Conflict(s) are added to the transaction object
      jobs = transaction.jobs

      jobs.each do |job|
        case job[:action]
        when :add_path
          begin
            make_directory(job[:path])
          rescue Repository::Conflict => e
            transaction.add_conflict(e)
          end
        when :add
          begin
            add_file(job[:path], job[:file_data], transaction.user_id)
          rescue Repository::Conflict => e
            transaction.add_conflict(e)
          end
        when :remove
          begin
            remove_file(transaction,
                            job[:path], transaction.user_id,
                            job[:expected_revision_number])
          rescue Repository::Conflict => e
            transaction.add_conflict(e)
          end
        when :replace
          begin
            replace_file(transaction,
                               job[:path], job[:file_data], job[:mime_type],
                               job[:expected_revision_number])
          rescue Repository::Conflict => e
            transaction.add_conflict(e)
          end
        end
      end

      if transaction.conflicts?
        return false
      end
      return true
    end

    # Adds a user with given permissions to the repository
    def add_user(user_id, permissions)

      if @repos_admin # Are we admin?
        ga_repo = Gitolite::GitoliteAdmin.new(
          Repository.conf[:REPOSITORY_STORAGE] +
            '/gitolite-admin', GITOLITE_SETTINGS)

        # Sync the gitolite admin repo
        ga_repo.reload!

        repo_name = self.get_repos.workdir.split('/').last

        # Grab the repo from gitolite
        repo = ga_repo.config.get_repo(repo_name)

        # Create a new repo if required
        if repo.nil?
          repo = Gitolite::Config::Repo.new(repo_name)
          ga_repo.config.add_repo(repo)
        else
          repo.permissions[0].each do |perm|
            if(repo.permissions[0][perm[0]][""].include? user_id)
              raise UserAlreadyExistent.new(user_id + ' already existent')
            end
          end
        end

        git_permission = self.class.__translate_to_git_perms(permissions)
        repo.add_permission(git_permission, '', user_id)

        # Readd the 'git' public key to the gitolite admin repo after changes
        self.class.readd_admin_key

        # update Gitolite repo
        ga_repo.save_and_apply
      else
        raise NotAuthorityError.new('Unable to modify permissions:
                                     Not in authoritative mode!')
      end

    end

    def get_users(permissions)
      # Gets a list of users with AT LEAST the provided permissions.
      # Returns nil if there aren't any.

      # Permissions provided
      # http://gitolite.com/gitolite/write-types.html

      result_list = []

      # Access the gitolite admin repo
      ga_repo = Gitolite::GitoliteAdmin.new(
        Repository.conf[:REPOSITORY_PERMISSION_FILE])

      # Sync the repo
      ga_repo.update

      # Grab the repo in question from gitolite
      repo = ga_repo.config.get_repo(self.get_repos.workdir.split('/').last)

      if !repo.nil?
        repo.permissions[0].each do |perm|
          if self.class.__translate_perms_from_file(perm[0]) >= permissions
            repo.permissions[0][perm[0]][""].each do |user|
              result_list.push(user)
            end
          end
        end
      end

      if !result_list.empty?
        return result_list
      else
        return nil
      end
    end

    def get_permissions(user_id)

      if @repos_admin # Are we admin?
        # Adds a user with given permissions to the repository
        ga_repo = Gitolite::GitoliteAdmin.new(
          Repository.conf[:REPOSITORY_PERMISSION_FILE])

        # Sync the admin repo
        ga_repo.update
        repo = ga_repo.config.get_repo(get_repos.workdir.split('/').last)

        # Gets permissions of a particular user
        repo.permissions[0].each do |perm|
          if repo.permissions[0][perm[0]][''].include? user_id
            return self.class.__translate_perms_from_file(perm[0])
          end
        end

        raise UserNotFound.new(user_id + ' not found')

      else
        raise NotAuthorityError.new(
          'Unable to modify permissions: Not in authoritative mode!')
      end
    end

    def set_permissions(user_id, permissions)
      # Set permissions for a single given user
      if @repos_admin # Are we admin?

        remove_user(user_id)

        # Adds a user with given permissions to the repository
        ga_repo = Gitolite::GitoliteAdmin.new(
          Repository.conf[:REPOSITORY_PERMISSION_FILE])

        # Sync gitolite admin repo
        ga_repo.update

        # Grab the repo from gitolite
        repo_name = self.get_repos.workdir.split('/').last
        repo = ga_repo.config.get_repo(repo_name)

        if repo.nil?
          # Create new repo if it doesn't exist
          repo = Gitolite::Config::Repo.new(repo_name)
        end

        git_permission = self.class.__translate_to_git_perms(permissions)
        repo.add_permission(git_permission, "", user_id)
        ga_repo.config.add_repo(repo)

        # Readd the 'git' public key to the gitolite admin repo after changes
        self.class.readd_admin_key

        # update Gitolite repo
        ga_repo.save_and_apply

      else
        raise NotAuthorityError.new(
          'Unable to modify permissions: Not in authoritative mode!')
      end
    end

    def remove_user(user_id)
      # Delete user from access list
      # There is no user remove support from gitolite ruby library
      # Work-around:
      # - copy permissions from repo
      # - remove repo from config and save and apply
      # - add again permissions not removed

      if @repos_admin # Are we admin?
        # Adds a user with given permissions to the repository

        ga_repo = Gitolite::GitoliteAdmin.new(
          Repository.conf[:REPOSITORY_PERMISSION_FILE])

        # Sync gitolite admin repo
        ga_repo.update

        repo_name = self.get_repos.workdir.split('/').last

        # Grab the repo from gitolite
        repo = ga_repo.config.get_repo(repo_name)
        rw_list = []
        r_list  = []
        found = false
        if !repo.nil?
          repo.permissions[0]["RW+"][""].each do |user|
            if(user != user_id)
              rw_list.push(user)
            else
              found = true
            end
          end

          repo.permissions[0]["R"][""].each do |user|
            if(user != user_id)
              r_list.push(user)
            else
              found = true
            end
          end

          # Readd the 'git' public key to the gitolite admin repo after changes
          self.class.readd_admin_key

          # update Gitolite repo
          ga_repo.save_and_apply

          if found==true
            ga_repo.config.rm_repo(repo)
            ga_repo.save_and_apply
            rw_list.each do |user|
              add_user(user,Repository::Permission::READ_WRITE)
            end

            r_list.each do |user|
              add_user(user,Repository::Permission::READ)
            end
          else
            raise UserNotFound.new(user_id + " not found")
          end
        else
          raise UserNotFound.new(user_id + " not found")
        end
      else
        raise NotAuthorityError.new(
          'Unable to modify permissions: Not in authoritative mode!')
      end
    end

    def self.add_user(user_id, permissions, repo_name)

      # Adds a user with given permissions to the repository
      if !File.exist?(Repository.conf[:REPOSITORY_PERMISSION_FILE])
        # create file if not existent
        File.open(Repository.conf[:REPOSITORY_PERMISSION_FILE], 'w').close
      end

      ga_repo = Gitolite::GitoliteAdmin.new(
        Repository.conf[:REPOSITORY_PERMISSION_FILE])

      # Sync repo
      ga_repo.reload!

      # Grab the repo from gitolite
      repo = ga_repo.config.get_repo(repo_name)

      # Create a new repo if required from gitolite
      if repo.nil?
        repo = Gitolite::Config::Repo.new(repo_name)
        ga_repo.config.add_repo(repo)
      else
        repo.permissions[0].each do |perm|
          if(repo.permissions[0][perm[0]][""].include? user_id)
            raise UserAlreadyExistent.new(user_id + ' already existent')
          end
        end
      end

      git_permission = GitRepository.__translate_to_git_perms(permissions)
      repo.add_permission(git_permission, '', user_id)

      # Readd the 'git' public key to the gitolite admin repo after changes
      self.class.readd_admin_key

      # update Gitolite repo
      ga_repo.save_and_apply
    end

    # Sets permissions over several repositories. Use set_permissions to set
    # permissions on a single repository.
    def self.set_bulk_permissions(repo_names, user_id_permissions_map)
      # Check if configuration is in order
      if Repository.conf[:IS_REPOSITORY_ADMIN].nil?
        raise ConfigurationError.new(
          "Required config 'IS_REPOSITORY_ADMIN' not set")
      end
      # If we're not in authoritative mode, bail out
      if !Repository.conf[:IS_REPOSITORY_ADMIN] # Are we admin?
        raise NotAuthorityError.new(
          'Unable to set bulk permissions: Not in authoritative mode!')
      end

      ga_repo = Gitolite::GitoliteAdmin.new(
        Repository.conf[:REPOSITORY_STORAGE] +
          '/gitolite-admin', GITOLITE_SETTINGS)

      # Sync admin repo
      ga_repo.update

      # The admin repo is loaded into memory
      conf = ga_repo.config

      repo_names.each do |repo_name|
        repo_name = File.basename(repo_name)
        repo = ga_repo.config.get_repo(repo_name)
        if repo.nil?
          # Generate new repo conf since this repo hasn't been created yet
          repo = Gitolite::Config::Repo.new(repo_name)
        end
        # Add the permissions for each user
        user_id_permissions_map.each do |user_id, permissions|
          perm_string = __translate_to_git_perms(permissions)
          repo.add_permission(perm_string, '', user_id)
          repo.add_permission(perm_string, '', 'git') # testing
        end
        conf.add_repo(repo)
      end

      # Readd the 'git' public key to the gitolite admin repo after changes
      self.class.readd_admin_key

      # update Gitolite repo
      ga_repo.save_and_apply
    end

    def self.delete_bulk_permissions(repo_names, user_ids)
      # Deletes permissions over several repositories. Use remove_user to remove
      # permissions of a single repository.
      # There is no user remove support from gitolite ruby library
      # Work-around:
      # - copy permissions from repo
      # - remove repo from config and save and apply
      # - add again permissions not removed

      if @repos_admin # Are we admin?
        # Adds a user with given permissions to the repository
        ga_repo = Gitolite::GitoliteAdmin.new(
          Repository.conf[:REPOSITORY_PERMISSION_FILE])

        # Sync gitolite admin repo
        ga_repo.update

        repo_names.each do |repo_name|
          repo_name = File.basename(repo_name)
          repo = ga_repo.config.get_repo(repo_name)
          rw_list = []
          r_list = []
          found = false
          if !repo.nil?
            repo.permissions[0]['RW+'][''].each do |user|
              if !user_ids.include? user
                rw_list.push(user)
              else
                found = true
              end
            end

            repo.permissions[0]['R'][''].each do |user|
              if !user_ids.include? user
                r_list.push(user)
              else
                found = true
              end
            end
            if found == true
              ga_repo.reload!
              ga_repo.config.rm_repo(repo)

              readd_admin_key

              # update Gitolite repo
              ga_repo.save_and_apply

              rw_list.each do |user|
                add_user(user, Repository::Permission::READ_WRITE, repo_name)
              end

              r_list.each do |user|
                add_user(user, Repository::Permission::READ, repo_name)
              end
            else
              raise UserNotFound.new(user_id + ' not found')
            end
          else
            raise UserNotFound.new(user_id + ' not found')
          end
        end
      else
        raise NotAuthorityError.new('Unable to modify permissions:
                                     Not in authoritative mode!')
      end
    end

    # Helper method to translate internal permissions to git
    # permissions
    # If we want the directory creation to have its own commit,
    # we have to add a dummy file in that directory to do it.
    def self.__translate_to_git_perms(permissions)
      case (permissions)
      when Repository::Permission::READ
        return "R"
      when Repository::Permission::READ_WRITE
        return "RW+"
      else raise "Unknown permissions"
      end # end case
    end

    # Helper method to translate git permissions to internal
    # permissions
    def self.__translate_perms_from_file(perm_string)
      case (perm_string)
      when "R"
        return Repository::Permission::READ
      when "RW"
        return Repository::Permission::READ_WRITE
      when "RW+"
        return Repository::Permission::READ_WRITE
      else raise "Unknown permissions"
      end # end case
    end

    # Helper method to readd the Gitolite admin key after repo perm changes
    def self.readd_admin_key
      admin_key = Gitolite::SSHKey.from_file(
        GITOLITE_SETTINGS[:public_key])
      ga_repo.add_key(admin_key)
    end

    ####################################################################
    ##  Private method definitions
    ####################################################################

    private
    def latest_revision_number(_path = nil, _revision_number = nil)
      return get_revision_number(@repos.head.target)
    end

    def path_exists_for_latest_revision?(path)
      get_latest_revision.path_exists?(path)
    end

    # adds a file to a transaction and eventually to repository
    def add_file(path, file_data = nil, author)
      if path_exists_for_latest_revision?(path)
        raise Repository::FileExistsConflict.new(path)
      end
      write_file(path, file_data, author)
    end

    # removes a file from a transaction and eventually from repository
    def remove_file(txn, path, author, _expected_revision_number = 0)
      repo = @repos
      @repos.index.remove(path);
      File.unlink File.join(repo.workdir, path)
      @repos.index.write_tree repo
      @repos.index.write
      Rugged::Commit.create(@repos, commit_options(@repos, author,
                                                   'Removing file'))
<commit_msg>Fixed refactoring errors<commit_after>require 'rugged'
require 'gitolite'
require 'digest/md5'
require 'rubygems'
require 'git'

require File.join(File.dirname(__FILE__),'repository') # load repository module

def commit_options(repo, author, message)
  {
    author:  { email: '<EMAIL>', name: author, time: Time.now },
    committer: { email: '<EMAIL>', name: author, time: Time.now },
    message: message,
    tree: repo.index.write_tree(repo),
    parents: repo.empty? ? [] : [repo.head.target].compact,
    update_ref: 'HEAD'
  }
end

module Repository

  # Implements AbstractRepository for Git repositories
  # It implements the following paradigm:
  #   1. Repositories are created by using ???
  #   2. Existing repositories are opened by using either ???
  class GitRepository < Repository::AbstractRepository

    # Constructor: Connects to an existing Git
    # repository, using Ruby bindings; Note: A repository has to be
    # created using GitRespository.create(), it it is not yet existent
    def initialize(connect_string)
      # Check if configuration is in order
      if Repository.conf[:IS_REPOSITORY_ADMIN].nil?
        raise ConfigurationError.new(
                  "Required config 'IS_REPOSITORY_ADMIN' not set")
      end
      if Repository.conf[:REPOSITORY_STORAGE].nil?
        raise ConfigurationError.new(
                  "Required config 'REPOSITORY_STORAGE' not set")
      end
      if Repository.conf[:REPOSITORY_PERMISSION_FILE].nil?
        raise ConfigurationError.new(
                  "Required config 'REPOSITORY_PERMISSION_FILE' not set")
      end
      begin
        super(connect_string) # dummy call to super
      rescue NotImplementedError; end
      @repos_path = connect_string
      @closed = false
      @repos_admin = Repository.conf[:IS_REPOSITORY_ADMIN]
      if (GitRepository.repository_exists?(@repos_path))

        # make sure working directory is up-to-date
        g = Git.open(@repos_path)
        g.pull

        @repos = Rugged::Repository.new(@repos_path)
      else
        raise "Repository does not exist at path \"" + @repos_path + "\""
      end
    end

    # Static method: Creates a new Git repository at
    # location 'connect_string'
    def self.create(connect_string)
      if GitRepository.repository_exists?(connect_string)
        raise RepositoryCollision.new(
                  "There is already a repository at #{connect_string}")
      end
      if File.exists?(connect_string)
        raise IOError.new("Could not create a repository at #{connect_string}:
                          some directory with same name exists already")
      end

      ga_repo = Gitolite::GitoliteAdmin.new(
        Repository.conf[:REPOSITORY_STORAGE] +
          '/gitolite-admin', GITOLITE_SETTINGS)

      # Bring the repo up to date
      ga_repo.reload!

      # Grab the gitolite admin repo config
      conf = ga_repo.config

      repo_name = File.basename(connect_string.split('/').last)

      # Grab the repo in question, if it does not exist, create it
      repo = ga_repo.config.get_repo(repo_name)
      if repo.nil?
        # Generate new repo since this repo hasn't been created yet
        repo = Gitolite::Config::Repo.new(repo_name)
      end

      # Add permissions for git user
      repo.add_permission('RW+', '', 'git')

      # Add the repo to the gitolite admin config
      conf.add_repo(repo)

      # Readd the 'git' public key to the gitolite admin repo after changes
      admin_key = Gitolite::SSHKey.from_file(
          GITOLITE_SETTINGS[:public_key])
      ga_repo.add_key(admin_key)

      # Stage and push the changes to the gitolite admin repo
      ga_repo.save_and_apply

      # Repo is created by gitolite, proceed to clone it in
      # the repository storage location
      cloned_repo = Git.clone(
        'git@localhost:' + repo_name, REPOSITORY_STORAGE + '/' + repo_name)

      repo = Rugged::Repository.discover(REPOSITORY_STORAGE + '/' + repo_name)

      # Lets make some sample files and the new master branch
      cloned_repo.reset
      cloned_repo.branch('master')

      # Create the README.md on the filesystem
      file_path_for_readme = File.join(repo.workdir, 'README.md')
      File.open(file_path_for_readme, 'w+') do |readme|
        readme.write('Initial commit.')
      end

      cloned_repo.add(all: true)

      cloned_repo.commit_all('Initial commit.')
      cloned_repo.push

      # Finished cloning repo

      repo = Rugged::Repository.discover(REPOSITORY_STORAGE + '/' + repo_name)

      # Do an initial commit with a README to create index.
      file_path_for_readme = File.join(repo.workdir, 'README.md')
      File.open(file_path_for_readme, 'w+') do |readme|
        readme.write('Initial commit.')
      end
      oid = Rugged::Blob.from_workdir(repo, 'README.md')
      index = repo.index
      index.add(path: 'README.md', oid: oid, mode: 0100644)
      index.write
      Rugged::Commit.create(
        repo,
        commit_options(
                repo,
                'Markus',
                'Initial readme commit.'
        )
      )

      cloned_repo.push
      return true
    end

    # Static method: Opens an existing Git repository
    # at location 'connect_string'
    def self.open(connect_string)
      repo = GitRepository.new(connect_string)
    end

    # static method that should yeild to a git repo and then close it
    def self.access(connect_string)
      repo = self.open(connect_string)
      yield repo
    end

    # static method that deletes the git repo
    # rm everything? or only .git?
    def self.delete(repo_path)
      #repo = Rugged::Repository.new(repo_path)
      #ref = Rugged::Reference.lookup(repo, "refs/heads/master")
      #ref.delete!
      FileUtils.rm_rf(repo_path)
    end

    # Given a OID of a file from a Rugged::Repository lookup, return the blob
    # object of the file itself.
    def get_blob(oid)
      @repos.lookup(oid)
    end

    # Exports git repo to a new folder (clone repository)
    # If a filepath is given, the repo_dest_dir needs to point to a file, and
    # all the repository on that path need to exist, or the export will fail.
    # Exports git repo to a new folder (clone repository)
    # If a filepath is given, the repo_dest_dir needs to point to a file, and
    # all the repository on that path need to exist, or the export will fail.
    # if export means exporting repo as zip/tgz git-ruby library should be used
    def export(repo_dest_dir, filepath = nil)

      # Case 1: clone all the repo to repo_dest_dir
      if(filepath.nil?)
        # Raise an error if the destination repository already exists
        if (File.exists?(repo_dest_dir))
          raise(ExportRepositoryAlreadyExists,
                'Exported repository already exists')
        end

        repo = Rugged::Repository.clone_at(@repos_path, repo_dest_dir)
      else
        # Case 2: clone a file to a folder
        # Raise an error if the destination file already exists
        if (File.exists?(repo_dest_dir))
          raise(ExportRepositoryAlreadyExists,
                'Exported file already exists')
        end
        FileUtils.cp(get_repos_workdir + filepath, repo_dest_dir)
        return true
      end

    end

    #  Converts a pathname to an absolute pathname
    def expand_path(file_name, dir_string = '/')
      expanded = File.expand_path(file_name, dir_string)
      if RUBY_PLATFORM =~ /(:?mswin|mingw)/ #only if the platform is Windows
        expanded = expanded[2..-1]#remove the drive letter
      end
      expanded
    end

    def self.closable?
      # return if the git library supports close,
      # probably going to need to be a dumby method
    end

    def close
      # closes the git repo
      @repos.close
      @closed = true
    end

    def closed?
      # checks if the repo is closed
      return @closed
    end

    def get_repos
      # Get rugged repository from GitRepository
      return @repos
    end

    def get_repos_workdir
      # Get work directory from GitRepository
      # workdir = path/to/my/repository/
      return @repos.workdir
    end

    def get_repos_path
      # Get Rugged repository from GitRepository
      # workdir = path/to/my/repository/.git
      return @repos.path
    end

    # Given a File object, perform a lookup for the Rugged::Tree
    # object which contains the directory information for the
    # folder where this file resides, then return the specific
    # file contents that we are interested in.
    def stringify(file)
      revision = get_revision(file.from_revision)
      blob = revision.find_object_at_path(file.path)

      # From the returned Tree blob, find the file in the collection
      blob.entries.each do |file_entry|
        if file_entry[:name] == file.name
          return get_blob(file_entry[:oid]).content
        end
      end
    end
    alias download_as_string stringify # create alias

    # Static method: Reports if a Git repository exists.
    # Done in a similarly hacky method as the git side.
    # TODO - find a better way to do this.
    def self.repository_exists?(repos_path)
      repos_meta_files_exist = false
      if File.exist?(File.join(repos_path, '.git/config'))
        if File.exist?(File.join(repos_path, '.git/description'))
          if File.exist?(File.join(repos_path, '.git/HEAD'))
            repos_meta_files_exist = true
          end
        end
      end
      return repos_meta_files_exist
    end

    def get_revision_number(hash)
      # This functions walks down git log and counts the steps from beginning
      # to get the revision number.
      walker = Rugged::Walker.new(@repos)
      walker.sorting(Rugged::SORT_DATE | Rugged::SORT_REVERSE)
      walker.push(hash)
      start = 0
      walker.each do |commit|
        start += 1
        break if commit.oid == hash
      end
      return start
    end

    # Returns a Repository::SubversionRevision instance
    # holding the latest Subversion repository revision
    # number
    def get_latest_revision
      return get_revision(latest_revision_number)
    end

    # Returns hash wrapped
    # as a Git instance
    def get_revision(revision_number)
      return Repository::GitRevision.new(revision_number, self)
    end

    def get_all_revisions
      youngest_revision = latest_revision_number
      log = []
      (1..youngest_revision).each do |num|
        log.push(Repository::GitRevision.new(num, self))
      end
      return log
    end

    def get_revision_by_timestamp(target_timestamp, _path = nil)
      # returns a Git instance representing the revision at the
      # current timestamp, should be a ruby time stamp instance
      walker = Rugged::Walker.new(self.get_repos)
      walker.push(self.get_repos.head.target)
      walker.each do |c|
        if c.time <= target_timestamp
          @revision_number = get_revision_number(c)
          return get_revision(@revision_number)
        end
      end
      # If no revision number was found, display the latest revision
      # with an error message
      raise 'No revision found before supplied timestamp.'
    end

    # Returns a Repository::TransAction object, to work with. Do operations,
    # like 'add', 'remove', etc. on the transaction instead of the repository
    def get_transaction(user_id, comment = '')
      if user_id.nil?
        raise 'Expected a user_id (Repository.get_transaction(user_id))'
      end
      return Repository::Transaction.new(user_id, comment)
    end

    def commit(transaction)
      # Carries out actions on a Git repository stored in
      # 'transaction'. In case of certain conflicts corresponding
      # Repositor::Conflict(s) are added to the transaction object
      jobs = transaction.jobs

      jobs.each do |job|
        case job[:action]
        when :add_path
          begin
            make_directory(job[:path])
          rescue Repository::Conflict => e
            transaction.add_conflict(e)
          end
        when :add
          begin
            add_file(job[:path], job[:file_data], transaction.user_id)
          rescue Repository::Conflict => e
            transaction.add_conflict(e)
          end
        when :remove
          begin
            remove_file(transaction,
                            job[:path], transaction.user_id,
                            job[:expected_revision_number])
          rescue Repository::Conflict => e
            transaction.add_conflict(e)
          end
        when :replace
          begin
            replace_file(transaction,
                               job[:path], job[:file_data], job[:mime_type],
                               job[:expected_revision_number])
          rescue Repository::Conflict => e
            transaction.add_conflict(e)
          end
        end
      end

      if transaction.conflicts?
        return false
      end
      return true
    end

    # Adds a user with given permissions to the repository
    def add_user(user_id, permissions)

      if @repos_admin # Are we admin?
        ga_repo = Gitolite::GitoliteAdmin.new(
          Repository.conf[:REPOSITORY_STORAGE] +
            '/gitolite-admin', GITOLITE_SETTINGS)

        # Sync the gitolite admin repo
        ga_repo.reload!

        repo_name = self.get_repos.workdir.split('/').last

        # Grab the repo from gitolite
        repo = ga_repo.config.get_repo(repo_name)

        # Create a new repo if required
        if repo.nil?
          repo = Gitolite::Config::Repo.new(repo_name)
          ga_repo.config.add_repo(repo)
        else
          repo.permissions[0].each do |perm|
            if(repo.permissions[0][perm[0]][""].include? user_id)
              raise UserAlreadyExistent.new(user_id + ' already existent')
            end
          end
        end

        git_permission = self.class.__translate_to_git_perms(permissions)
        repo.add_permission(git_permission, '', user_id)

        # Readd the 'git' public key to the gitolite admin repo after changes
        admin_key = Gitolite::SSHKey.from_file(
            GITOLITE_SETTINGS[:public_key])
        ga_repo.add_key(admin_key)

        # update Gitolite repo
        ga_repo.save_and_apply
      else
        raise NotAuthorityError.new('Unable to modify permissions:
                                     Not in authoritative mode!')
      end

    end

    def get_users(permissions)
      # Gets a list of users with AT LEAST the provided permissions.
      # Returns nil if there aren't any.

      # Permissions provided
      # http://gitolite.com/gitolite/write-types.html

      result_list = []

      # Access the gitolite admin repo
      ga_repo = Gitolite::GitoliteAdmin.new(
        Repository.conf[:REPOSITORY_PERMISSION_FILE])

      # Sync the repo
      ga_repo.update

      # Grab the repo in question from gitolite
      repo = ga_repo.config.get_repo(self.get_repos.workdir.split('/').last)

      if !repo.nil?
        repo.permissions[0].each do |perm|
          if self.class.__translate_perms_from_file(perm[0]) >= permissions
            repo.permissions[0][perm[0]][""].each do |user|
              result_list.push(user)
            end
          end
        end
      end

      if !result_list.empty?
        return result_list
      else
        return nil
      end
    end

    def get_permissions(user_id)

      if @repos_admin # Are we admin?
        # Adds a user with given permissions to the repository
        ga_repo = Gitolite::GitoliteAdmin.new(
          Repository.conf[:REPOSITORY_PERMISSION_FILE])

        # Sync the admin repo
        ga_repo.update
        repo = ga_repo.config.get_repo(get_repos.workdir.split('/').last)

        # Gets permissions of a particular user
        repo.permissions[0].each do |perm|
          if repo.permissions[0][perm[0]][''].include? user_id
            return self.class.__translate_perms_from_file(perm[0])
          end
        end

        raise UserNotFound.new(user_id + ' not found')

      else
        raise NotAuthorityError.new(
          'Unable to modify permissions: Not in authoritative mode!')
      end
    end

    def set_permissions(user_id, permissions)
      # Set permissions for a single given user
      if @repos_admin # Are we admin?

        remove_user(user_id)

        # Adds a user with given permissions to the repository
        ga_repo = Gitolite::GitoliteAdmin.new(
          Repository.conf[:REPOSITORY_PERMISSION_FILE])

        # Sync gitolite admin repo
        ga_repo.update

        # Grab the repo from gitolite
        repo_name = self.get_repos.workdir.split('/').last
        repo = ga_repo.config.get_repo(repo_name)

        if repo.nil?
          # Create new repo if it doesn't exist
          repo = Gitolite::Config::Repo.new(repo_name)
        end

        git_permission = self.class.__translate_to_git_perms(permissions)
        repo.add_permission(git_permission, "", user_id)
        ga_repo.config.add_repo(repo)

        # Readd the 'git' public key to the gitolite admin repo after changes
        admin_key = Gitolite::SSHKey.from_file(
            GITOLITE_SETTINGS[:public_key])
        ga_repo.add_key(admin_key)

        # update Gitolite repo
        ga_repo.save_and_apply

      else
        raise NotAuthorityError.new(
          'Unable to modify permissions: Not in authoritative mode!')
      end
    end

    def remove_user(user_id)
      # Delete user from access list
      # There is no user remove support from gitolite ruby library
      # Work-around:
      # - copy permissions from repo
      # - remove repo from config and save and apply
      # - add again permissions not removed

      if @repos_admin # Are we admin?
        # Adds a user with given permissions to the repository

        ga_repo = Gitolite::GitoliteAdmin.new(
          Repository.conf[:REPOSITORY_PERMISSION_FILE])

        # Sync gitolite admin repo
        ga_repo.update

        repo_name = self.get_repos.workdir.split('/').last

        # Grab the repo from gitolite
        repo = ga_repo.config.get_repo(repo_name)
        rw_list = []
        r_list  = []
        found = false
        if !repo.nil?
          repo.permissions[0]["RW+"][""].each do |user|
            if(user != user_id)
              rw_list.push(user)
            else
              found = true
            end
          end

          repo.permissions[0]["R"][""].each do |user|
            if(user != user_id)
              r_list.push(user)
            else
              found = true
            end
          end

          # Readd the 'git' public key to the gitolite admin repo after changes
          admin_key = Gitolite::SSHKey.from_file(
              GITOLITE_SETTINGS[:public_key])
          ga_repo.add_key(admin_key)

          # update Gitolite repo
          ga_repo.save_and_apply

          if found==true
            ga_repo.config.rm_repo(repo)
            ga_repo.save_and_apply
            rw_list.each do |user|
              add_user(user,Repository::Permission::READ_WRITE)
            end

            r_list.each do |user|
              add_user(user,Repository::Permission::READ)
            end
          else
            raise UserNotFound.new(user_id + " not found")
          end
        else
          raise UserNotFound.new(user_id + " not found")
        end
      else
        raise NotAuthorityError.new(
          'Unable to modify permissions: Not in authoritative mode!')
      end
    end

    def self.add_user(user_id, permissions, repo_name)

      # Adds a user with given permissions to the repository
      if !File.exist?(Repository.conf[:REPOSITORY_PERMISSION_FILE])
        # create file if not existent
        File.open(Repository.conf[:REPOSITORY_PERMISSION_FILE], 'w').close
      end

      ga_repo = Gitolite::GitoliteAdmin.new(
        Repository.conf[:REPOSITORY_PERMISSION_FILE])

      # Sync repo
      ga_repo.reload!

      # Grab the repo from gitolite
      repo = ga_repo.config.get_repo(repo_name)

      # Create a new repo if required from gitolite
      if repo.nil?
        repo = Gitolite::Config::Repo.new(repo_name)
        ga_repo.config.add_repo(repo)
      else
        repo.permissions[0].each do |perm|
          if(repo.permissions[0][perm[0]][""].include? user_id)
            raise UserAlreadyExistent.new(user_id + ' already existent')
          end
        end
      end

      git_permission = GitRepository.__translate_to_git_perms(permissions)
      repo.add_permission(git_permission, '', user_id)

      # Readd the 'git' public key to the gitolite admin repo after changes
      admin_key = Gitolite::SSHKey.from_file(
          GITOLITE_SETTINGS[:public_key])
      ga_repo.add_key(admin_key)

      # update Gitolite repo
      ga_repo.save_and_apply
    end

    # Sets permissions over several repositories. Use set_permissions to set
    # permissions on a single repository.
    def self.set_bulk_permissions(repo_names, user_id_permissions_map)
      # Check if configuration is in order
      if Repository.conf[:IS_REPOSITORY_ADMIN].nil?
        raise ConfigurationError.new(
          "Required config 'IS_REPOSITORY_ADMIN' not set")
      end
      # If we're not in authoritative mode, bail out
      if !Repository.conf[:IS_REPOSITORY_ADMIN] # Are we admin?
        raise NotAuthorityError.new(
          'Unable to set bulk permissions: Not in authoritative mode!')
      end

      ga_repo = Gitolite::GitoliteAdmin.new(
        Repository.conf[:REPOSITORY_STORAGE] +
          '/gitolite-admin', GITOLITE_SETTINGS)

      # Sync admin repo
      ga_repo.update

      # The admin repo is loaded into memory
      conf = ga_repo.config

      repo_names.each do |repo_name|
        repo_name = File.basename(repo_name)
        repo = ga_repo.config.get_repo(repo_name)
        if repo.nil?
          # Generate new repo conf since this repo hasn't been created yet
          repo = Gitolite::Config::Repo.new(repo_name)
        end
        # Add the permissions for each user
        user_id_permissions_map.each do |user_id, permissions|
          perm_string = __translate_to_git_perms(permissions)
          repo.add_permission(perm_string, '', user_id)
          repo.add_permission(perm_string, '', 'git') # testing
        end
        conf.add_repo(repo)
      end

      # Readd the 'git' public key to the gitolite admin repo after changes
      admin_key = Gitolite::SSHKey.from_file(
          GITOLITE_SETTINGS[:public_key])
      ga_repo.add_key(admin_key)

      # update Gitolite repo
      ga_repo.save_and_apply
    end

    def self.delete_bulk_permissions(repo_names, user_ids)
      # Deletes permissions over several repositories. Use remove_user to remove
      # permissions of a single repository.
      # There is no user remove support from gitolite ruby library
      # Work-around:
      # - copy permissions from repo
      # - remove repo from config and save and apply
      # - add again permissions not removed

      if @repos_admin # Are we admin?
        # Adds a user with given permissions to the repository
        ga_repo = Gitolite::GitoliteAdmin.new(
          Repository.conf[:REPOSITORY_PERMISSION_FILE])

        # Sync gitolite admin repo
        ga_repo.update

        repo_names.each do |repo_name|
          repo_name = File.basename(repo_name)
          repo = ga_repo.config.get_repo(repo_name)
          rw_list = []
          r_list = []
          found = false
          if !repo.nil?
            repo.permissions[0]['RW+'][''].each do |user|
              if !user_ids.include? user
                rw_list.push(user)
              else
                found = true
              end
            end

            repo.permissions[0]['R'][''].each do |user|
              if !user_ids.include? user
                r_list.push(user)
              else
                found = true
              end
            end
            if found == true
              ga_repo.reload!
              ga_repo.config.rm_repo(repo)

              admin_key = Gitolite::SSHKey.from_file(
                  GITOLITE_SETTINGS[:public_key])
              ga_repo.add_key(admin_key)

              # update Gitolite repo
              ga_repo.save_and_apply

              rw_list.each do |user|
                add_user(user, Repository::Permission::READ_WRITE, repo_name)
              end

              r_list.each do |user|
                add_user(user, Repository::Permission::READ, repo_name)
              end
            else
              raise UserNotFound.new(user_id + ' not found')
            end
          else
            raise UserNotFound.new(user_id + ' not found')
          end
        end
      else
        raise NotAuthorityError.new('Unable to modify permissions:
                                     Not in authoritative mode!')
      end
    end

    # Helper method to translate internal permissions to git
    # permissions
    # If we want the directory creation to have its own commit,
    # we have to add a dummy file in that directory to do it.
    def self.__translate_to_git_perms(permissions)
      case (permissions)
      when Repository::Permission::READ
        return "R"
      when Repository::Permission::READ_WRITE
        return "RW+"
      else raise "Unknown permissions"
      end # end case
    end

    # Helper method to translate git permissions to internal
    # permissions
    def self.__translate_perms_from_file(perm_string)
      case (perm_string)
      when "R"
        return Repository::Permission::READ
      when "RW"
        return Repository::Permission::READ_WRITE
      when "RW+"
        return Repository::Permission::READ_WRITE
      else raise "Unknown permissions"
      end # end case
    end
    
    ####################################################################
    ##  Private method definitions
    ####################################################################

    private
    def latest_revision_number(_path = nil, _revision_number = nil)
      return get_revision_number(@repos.head.target)
    end

    def path_exists_for_latest_revision?(path)
      get_latest_revision.path_exists?(path)
    end

    # adds a file to a transaction and eventually to repository
    def add_file(path, file_data = nil, author)
      if path_exists_for_latest_revision?(path)
        raise Repository::FileExistsConflict.new(path)
      end
      write_file(path, file_data, author)
    end

    # removes a file from a transaction and eventually from repository
    def remove_file(txn, path, author, _expected_revision_number = 0)
      repo = @repos
      @repos.index.remove(path);
      File.unlink File.join(repo.workdir, path)
      @repos.index.write_tree repo
      @repos.index.write
      Rugged::Commit.create(@repos, commit_options(@repos, author,
                                                   'Removing file'))

{'fraction_non_alphanumeric': 0.08165713750117162, 'fraction_numerical': 0.0013497047520854813, 'mean_word_length': 2.236621769202767, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5739109', 'n_tokens_mistral': 15557, 'n_tokens_neox': 15049, 'n_words': 4274}