Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 37 additions & 51 deletions back/app/services/github.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,47 +46,57 @@ def create_repository(name)

def get_all_files(repo_name)
return [] unless repository_exists?(repo_name)
return [] if repository_empty?(repo_name)

repository_name = set_repository_name(repo_name)
head_sha = @client.ref(repository_name, 'heads/main').object.sha

# ディレクトリ再帰巡回の代わりに Trees API で全パスを1回のAPIコールで取得
tree = @client.tree(repository_name, head_sha, recursive: true)
file_blobs = tree.tree.select { |item| item.type == 'blob' }

# 各ファイルのコンテンツ取得を並列実行
futures = file_blobs.map do |blob|
Concurrent::Future.execute do
content = @client.blob(repository_name, blob.sha)
decoded = Base64.decode64(content.content.gsub(/\s+/, ''))
{ name: File.basename(blob.path), path: blob.path, content: decoded }
end
end

@remote_files = get_all_files_recursive(repo_name).flatten
@remote_files
@remote_files = futures.map(&:value!)
rescue Octokit::Error => e
Rails.logger.error e
[]
end

def commit_push(repo_name, files, commitMessage)
repository_name = set_repository_name(repo_name)
branch = 'main'

begin
# 現在のツリーを取得
base_tree = @client.ref(repository_name, "heads/#{branch}").object.sha
base_tree = @client.ref(repository_name, "heads/#{branch}").object.sha

blobs = files.map do |file|
# blob作成を並列実行
futures = files.map do |file|
Concurrent::Future.execute do
if file[:is_delete]
# ファイルの削除
delete_file(file[:path])
elsif file[:path] == file[:old_path] || file[:old_path].empty?
# ファイルの新規作成と更新
create_or_update_file(repository_name, file)
else
# ファイル名の変更
# ファイル名変更のメソッドがないため、削除と新規作成を行う
delete_blob = delete_file(file[:old_path])
create_blob = create_or_update_file(repository_name, file)
[delete_blob, create_blob]
[delete_file(file[:old_path]), create_or_update_file(repository_name, file)]
end
end.flatten

# 新しいツリーを作成
new_tree = @client.create_tree(repository_name, blobs, base_tree: base_tree)
end
end

# 新しいコミットを作成
new_commit = @client.create_commit(repository_name, commitMessage, new_tree.sha, base_tree)
blobs = futures.map(&:value!).flatten

# リモートリポジトリにプッシュ
@client.update_ref(repository_name, "heads/#{branch}", new_commit.sha)
rescue Octokit::Error => e
Rails.logger.error e
{ success: false, message: 'コミットに失敗しました'}
end
new_tree = @client.create_tree(repository_name, blobs, base_tree: base_tree)
new_commit = @client.create_commit(repository_name, commitMessage, new_tree.sha, base_tree)
@client.update_ref(repository_name, "heads/#{branch}", new_commit.sha)
rescue Octokit::Error => e
Rails.logger.error e
{ success: false, message: 'コミットに失敗しました'}
end

private
Expand All @@ -101,40 +111,18 @@ def repository_empty?(repo_name)
true
end

def get_all_files_recursive(repo_name, path = '')
return [] if repository_empty?(repo_name)
contents = @client.contents(set_repository_name(repo_name), path: path)

files = []

contents.each do |content|
if content.type == 'file'
file_contents = @client.contents(set_repository_name(repo_name), path: content.path)
files << { name: content.name, path: content.path, content: Base64.decode64(file_contents.content) }
elsif content.type == 'dir'
files << get_all_files_recursive(repo_name, content.path)
end
end

files
end

def exist_file_update(repo_name, files)
return [] if files.nil? || files.empty?

error = []
files.each do |file|
begin
# contentがnilだとエラーが発生するので空文字を代入
content = file[:content].presence || ''

# contentとリモートリポジトリのファイル内容が同じ場合は更新しない
next if @remote_files.any? { |f| f[:name] == file[:name] && f[:content] == content }

file_contents = @client.contents(set_repository_name(repo_name), path: file[:name])

sha = file_contents.sha
@client.update_contents(set_repository_name(repo_name), file[:name], "更新 #{@commitMessage}", sha,content)
@client.update_contents(set_repository_name(repo_name), file[:name], "更新 #{@commitMessage}", sha, content)
rescue Octokit::Error => e
Rails.logger.error e
error << "#{file[:name]} の更新に失敗しました"
Expand All @@ -150,7 +138,7 @@ def new_file_create(repo_name, files)
error = []
files.each do |file|
begin
content = file[:content] || '' # 空の内容でも許容する
content = file[:content] || ''
@client.create_contents(set_repository_name(repo_name), file[:name], "作成 #{@commitMessage}", content)
rescue Octokit::Error => e
Rails.logger.error e
Expand All @@ -168,8 +156,6 @@ def file_delete(repo_name, files)
files.each do |file|
begin
file_contents = @client.contents(set_repository_name(repo_name), path: file[:name])

# ファイルが存在しない場合はスキップ
next if file_contents.nil?

sha = file_contents.sha
Expand Down
57 changes: 57 additions & 0 deletions back/spec/services/github_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,63 @@
end
end

describe '#get_all_files' do
let(:ref_double) { double('ref', object: double('object', sha: 'head_sha')) }
let(:blob_item_1) { double('item', type: 'blob', path: 'README.md', sha: 'sha1') }
let(:blob_item_2) { double('item', type: 'blob', path: 'notes/memo.md', sha: 'sha2') }
let(:tree_item_dir) { double('item', type: 'tree', path: 'notes', sha: 'dir_sha') }
let(:tree_double) { double('tree', tree: [blob_item_1, blob_item_2, tree_item_dir]) }
let(:blob1) { double('blob', content: Base64.encode64('# Hello')) }
let(:blob2) { double('blob', content: Base64.encode64('- memo')) }

before do
allow(octokit_client).to receive(:repository).and_return(double('repo'))
allow(octokit_client).to receive(:contents).and_return([double('content')])
allow(octokit_client).to receive(:ref).and_return(ref_double)
allow(octokit_client).to receive(:tree).and_return(tree_double)
allow(octokit_client).to receive(:blob).with("#{user.name}/myrepo", 'sha1').and_return(blob1)
allow(octokit_client).to receive(:blob).with("#{user.name}/myrepo", 'sha2').and_return(blob2)
end

it 'Trees APIを再帰オプションで呼び出す' do
github.get_all_files('myrepo')
expect(octokit_client).to have_received(:tree).with("#{user.name}/myrepo", 'head_sha', recursive: true)
end

it 'blobのみを対象にファイル一覧を返す' do
result = github.get_all_files('myrepo')
expect(result.size).to eq(2)
expect(result).to include(
{ name: 'README.md', path: 'README.md', content: '# Hello' },
{ name: 'memo.md', path: 'notes/memo.md', content: '- memo' }
)
end

context 'リポジトリが存在しない場合' do
before { allow(octokit_client).to receive(:repository).and_raise(Octokit::NotFound) }

it '空配列を返す' do
expect(github.get_all_files('myrepo')).to eq([])
end
end

context 'リポジトリが空の場合' do
before { allow(octokit_client).to receive(:contents).and_raise(Octokit::NotFound) }

it '空配列を返す' do
expect(github.get_all_files('myrepo')).to eq([])
end
end

context 'GitHub APIがエラーを返した場合' do
before { allow(octokit_client).to receive(:ref).and_raise(Octokit::Error) }

it '空配列を返す' do
expect(github.get_all_files('myrepo')).to eq([])
end
end
end

describe '#commit_push' do
let(:ref_double) { double('ref', object: double('object', sha: 'base_sha')) }
let(:tree_double) { double('tree', sha: 'tree_sha') }
Expand Down
Loading