| import os |
| import shutil |
| import tempfile |
| import subprocess |
| from pathlib import Path |
| import gradio as gr |
|
|
| |
| temp_root = tempfile.mkdtemp() |
| REPO_TARGET_NAME = "target_repo" |
| REPO_SOURCE_NAME = "source_repo" |
|
|
| |
| def clone_repo(url, name): |
| dest = os.path.join(temp_root, name) |
| if os.path.exists(dest): |
| shutil.rmtree(dest) |
| try: |
| subprocess.run(["git", "clone", url, dest], check=True, capture_output=True, text=True) |
| return dest, None |
| except subprocess.CalledProcessError as e: |
| return None, f"クローン失敗: {e.stderr.strip()}" |
|
|
| |
| def get_relative_diff_files(repo_a_path, repo_b_path): |
| try: |
| result = subprocess.run( |
| ["git", "diff", "--no-index", "--name-only", repo_a_path, repo_b_path], |
| check=False, capture_output=True, text=True |
| ) |
| files = result.stdout.strip().split('\n') |
| clean_files = [] |
| for f in files: |
| f = f.strip() |
| if not f: |
| continue |
| rel = f.replace(repo_a_path + os.sep, "").replace(repo_b_path + os.sep, "") |
| if not rel.startswith(".git") and ".git/" not in rel: |
| clean_files.append(rel) |
| return clean_files |
| except Exception as e: |
| return [f"エラー: {str(e)}"] |
|
|
| |
| def run_comparison(target_url, source_url): |
| repo_a, err1 = clone_repo(target_url, REPO_TARGET_NAME) |
| repo_b, err2 = clone_repo(source_url, REPO_SOURCE_NAME) |
|
|
| if err1: |
| return [], [], f"対象リポジトリのクローンに失敗しました: {err1}" |
| if err2: |
| return [], [], f"比較元リポジトリのクローンに失敗しました: {err2}" |
|
|
| diff_files = get_relative_diff_files(repo_a, repo_b) |
| return gr.update(choices=diff_files, value=diff_files), diff_files, "" |
|
|
| |
| def copy_files(selected_files): |
| repo_a = os.path.join(temp_root, REPO_TARGET_NAME) |
| repo_b = os.path.join(temp_root, REPO_SOURCE_NAME) |
|
|
| updated = [] |
| for rel_path in selected_files: |
| src = os.path.abspath(os.path.join(repo_b, rel_path)) |
| dst = os.path.abspath(os.path.join(repo_a, rel_path)) |
|
|
| if not os.path.exists(src): |
| continue |
| if src == dst: |
| continue |
|
|
| os.makedirs(os.path.dirname(dst), exist_ok=True) |
| shutil.copy2(src, dst) |
| updated.append(rel_path) |
|
|
| return f"{len(updated)} 個のファイルをコピーしました:\n" + "\n".join(updated) |
|
|
| |
| def git_commit_and_push(token, commit_message, user_name, user_email): |
| repo_path = os.path.join(temp_root, REPO_TARGET_NAME) |
| if not os.path.exists(repo_path): |
| return "エラー:対象リポジトリが存在しません" |
|
|
| try: |
| |
| subprocess.run(["git", "config", "user.name", user_name], cwd=repo_path, check=True) |
| subprocess.run(["git", "config", "user.email", user_email], cwd=repo_path, check=True) |
|
|
| subprocess.run(["git", "add", "."], cwd=repo_path, check=True) |
|
|
| |
| status = subprocess.run(["git", "status", "--porcelain"], cwd=repo_path, capture_output=True, text=True) |
| if not status.stdout.strip(): |
| return "⚠️ 変更がないため、コミットとPushはスキップされました" |
|
|
| subprocess.run(["git", "commit", "-m", commit_message], cwd=repo_path, check=True) |
|
|
| remote_get = subprocess.run(["git", "remote", "get-url", "origin"], |
| cwd=repo_path, check=True, capture_output=True, text=True) |
| remote_url = remote_get.stdout.strip() |
|
|
| if remote_url.startswith("https://"): |
| remote_with_token = remote_url.replace("https://", f"https://{token}@") |
| else: |
| return "HTTPS URL の GitHub リモートのみ対応しています" |
|
|
| subprocess.run(["git", "remote", "set-url", "origin", remote_with_token], cwd=repo_path, check=True) |
| subprocess.run(["git", "push", "origin", "main"], cwd=repo_path, check=True) |
|
|
| return "✅ Push 成功しました" |
| except subprocess.CalledProcessError as e: |
| return f"Gitエラー: {e.stderr or e.stdout or str(e)}" |
| except Exception as e: |
| return f"エラー: {str(e)}" |
|
|
| |
| with gr.Blocks(title="Git差分アップデーター") as demo: |
| gr.Markdown("## 🔄 Gitリポジトリ差分アップデート + GitHub Push") |
|
|
| with gr.Row(): |
| target_url = gr.Textbox(label="対象リポジトリURL(上書き先)") |
| source_url = gr.Textbox(label="比較元リポジトリURL(コピー元)") |
|
|
| diff_btn = gr.Button("差分取得&クローン") |
| diff_status = gr.Textbox(label="ステータス", interactive=False) |
| error_msg = gr.Textbox(label="エラー", visible=False, interactive=False) |
| diff_checkboxes = gr.CheckboxGroup(label="差分ファイル一覧(コピーしたいものを選択)", choices=[]) |
|
|
| copy_btn = gr.Button("選択ファイルを上書きコピー") |
| copy_result = gr.Textbox(label="コピー結果", lines=10, interactive=False) |
|
|
| gr.Markdown("### 🔐 GitHub Push 設定") |
| token_input = gr.Textbox(label="GitHub Personal Access Token(公開しないでください)", type="password") |
| user_name = gr.Textbox(label="Gitユーザー名", value="your-name") |
| user_email = gr.Textbox(label="Gitメールアドレス", value="your@email.com") |
| commit_msg = gr.Textbox(label="コミットメッセージ", value="Update from comparison tool") |
| push_btn = gr.Button("Push(mainブランチへ)") |
| push_result = gr.Textbox(label="Push結果", lines=3, interactive=False) |
|
|
| diff_btn.click(fn=run_comparison, inputs=[target_url, source_url], |
| outputs=[diff_checkboxes, diff_status, error_msg]) |
| copy_btn.click(fn=copy_files, inputs=[diff_checkboxes], outputs=copy_result) |
| push_btn.click(fn=git_commit_and_push, |
| inputs=[token_input, commit_msg, user_name, user_email], |
| outputs=push_result) |
|
|
| demo.launch() |
|
|