noesya/osuny

View on GitHub
app/services/git/providers/github.rb

Summary

Maintainability
A
0 mins
Test Coverage
D
69%
class Git::Providers::Github < Git::Providers::Abstract
  BASE_URL = "https://github.com".freeze
  COMMIT_BATCH_SIZE = 250

  include WithSecrets
  include WithTheme

  def url
    "#{BASE_URL}/#{repository}"
  end

  def create_file(path, content)
    batch << {
      path: path,
      mode: '100644', # https://docs.github.com/en/rest/reference/git#create-a-tree
      type: 'blob',
      content: content
    }
  end

  def update_file(path, previous_path, content)
    # Handle newly created GitFiles which update existing remote files while having blank previous_path.
    path_to_check = previous_path.present? ? previous_path : path
    file = tree_item_at_path(path_to_check)
    return if file.nil?
    batch << {
      path: path_to_check,
      mode: file[:mode],
      type: file[:type],
      sha: nil
    }
    batch << {
      path: path,
      mode: file[:mode],
      type: file[:type],
      content: content
    }
  end

  def destroy_file(path)
    file = tree_item_at_path(path)
    return if file.nil?
    batch << {
      path: path,
      mode: file[:mode],
      type: file[:type],
      sha: nil
    }
  end

  def init_from_template(name)
    client.create_repository_from_template(
      ENV['GITHUB_WEBSITE_TEMPLATE_REPOSITORY'],
      name,
      {
        owner: ENV['GITHUB_WEBSITE_OWNER'],
        private: false
      }
    )
  end

  def push(commit_message)
    return if !valid? || batch.empty?
    commit = create_commit_from_batch(batch, commit_message)
    client.update_branch repository, default_branch, commit[:sha]
    # The repo changed, invalidate the tree
    @tree = nil
    @tree_items_by_path = nil
    #
    true
  end

  def create_commit_from_batch(batch, commit_message)
    base_tree_sha = tree[:sha]
    base_commit_sha = branch_sha
    commit = nil
    commits_count = (batch.size / COMMIT_BATCH_SIZE.to_f).ceil
    batch.each_slice(COMMIT_BATCH_SIZE).with_index do |sub_batch, i|
      sub_commit_message = commit_message
      sub_commit_message += " (#{i+1}/#{commits_count})" if commits_count > 1
      commit = create_sub_commit(sub_batch, sub_commit_message, base_tree_sha, base_commit_sha)
      base_tree_sha = commit[:tree][:sha]
      base_commit_sha = commit[:sha]
    end
    commit
  end

  def create_sub_commit(sub_batch, sub_commit_message, base_tree_sha, base_commit_sha)
    puts "Creating commit with #{sub_batch.size} files."
    new_tree = client.create_tree repository, sub_batch, base_tree: base_tree_sha
    client.create_commit repository, sub_commit_message, new_tree[:sha], base_commit_sha
  end

  def computed_sha(string)
    # Git SHA-1 is calculated from the String "blob <length>\x00<contents>"
    # Source: https://alblue.bandlem.com/2011/08/git-tip-of-week-objects.html
    OpenSSL::Digest::SHA1.hexdigest "blob #{string.bytesize}\x00#{string}"
  end

  def git_sha(path)
    return if path.nil?
    # Try to find in stored tree to avoid multiple queries
    tree_item_at_path(path)&.dig(:sha)
  end

  def valid?
    return false unless super
    begin
      client.repository(repository)
      true
    rescue Octokit::Unauthorized
      git_repository.website.invalidate_access_token!
      false
    end
  end

  def files_in_the_repository
    @files_in_the_repository ||= tree[:tree].map { |file| file[:path] }
  end

  protected

  def client
    @client ||= Octokit::Client.new access_token: access_token
  end

  def default_branch
    @default_branch ||= branch.present? ? branch
                                        : client.repo(repository)[:default_branch]
  end

  def branch_sha
    @branch_sha ||= client.branch(repository, default_branch)[:commit][:sha]
  end

  def tree_item_at_path(path)
    tree_items_by_path[path] if tree_items_by_path.has_key? path
  end

  def tree_items_by_path
    unless @tree_items_by_path
      @tree_items_by_path = {}
      tree[:tree].each do |hash|
        path = hash[:path]
        @tree_items_by_path[path] = {
          mode: hash[:mode],
          type: hash[:type],
          sha: hash[:sha]
        }
      end
    end
    @tree_items_by_path
  end

  def tree
    @tree ||= client.tree repository, branch_sha, recursive: true
  end

end