From 19a8bf43ba0bd5d0a970be5e545ae3a70b662d20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=E5=98=89=E9=98=B3-coder?= Date: Thu, 20 Nov 2025 21:52:51 +0800 Subject: [PATCH] fix --- .claude/settings.local.json | 4 +- .gitea/workflows/multi-repo-sync.yml | 245 +-------------------------- sync_repos.py | 193 +++++++++++++++++++++ 3 files changed, 198 insertions(+), 244 deletions(-) create mode 100644 sync_repos.py diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 205e28c..371328e 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -5,7 +5,9 @@ "Bash(git pull:*)", "Bash(git config pull.rebase false:*)", "Bash(git add .claude/settings.local.json)", - "Bash(git commit:*)" + "Bash(git commit:*)", + "Bash(git push:*)", + "Bash(/tmp/old_workflow.yml)" ], "deny": [], "ask": [] diff --git a/.gitea/workflows/multi-repo-sync.yml b/.gitea/workflows/multi-repo-sync.yml index 4d81717..0b1712d 100644 --- a/.gitea/workflows/multi-repo-sync.yml +++ b/.gitea/workflows/multi-repo-sync.yml @@ -6,7 +6,7 @@ on: - master paths: - 'repos.yaml' - - '.github/workflows/multi-repo-sync.yml' + - '.gitea/workflows/*.yml' workflow_dispatch: schedule: # 每天凌晨1点执行 @@ -50,245 +50,4 @@ jobs: UPSTREAM_TOKEN: ${{ secrets.UPSTREAM_TOKEN }} TARGET_USERNAME: ${{ secrets.TARGET_USERNAME }} TARGET_TOKEN: ${{ secrets.TARGET_TOKEN }} - run: | - #!/usr/bin/env python3 - import yaml - import os - import sys - import subprocess - from pathlib import Path - import tempfile - import shutil - import time - - def sync_repository(repo_config): - """同步单个仓库""" - name = repo_config.get('name', 'unnamed-repo') - source_url = repo_config.get('source_url', '') - target_url = repo_config.get('target_url', '') - branch = repo_config.get('branch', 'master') - - if not source_url or not target_url: - print(f"❌ [{name}] 错误: 缺少 source_url 或 target_url") - return False - - # 替换环境变量 - source_url = os.path.expandvars(source_url) - target_url = os.path.expandvars(target_url) - - # 检查必要的环境变量是否设置 - if '${' in source_url: - print(f"⚠️ [{name}] 警告: source_url 中的环境变量未完全解析: {source_url}") - if '${' in target_url: - print(f"⚠️ [{name}] 警告: target_url 中的环境变量未完全解析: {target_url}") - - print(f"\n{'='*60}") - print(f"开始同步仓库: {name}") - print(f"源地址: {source_url.split('@')[-1] if '@' in source_url else source_url}") - print(f"目标地址: {target_url.split('@')[-1] if '@' in target_url else target_url}") - print(f"分支: {branch}") - print(f"{'='*60}") - - # 创建临时工作目录 - work_dir = tempfile.mkdtemp(prefix=f"sync_{name}_") - - try: - # 克隆目标仓库 - print(f"\n[1/6] 克隆目标仓库...") - result = subprocess.run( - ['git', 'clone', '--bare', target_url, work_dir], - capture_output=True, text=True, timeout=300 - ) - - if result.returncode != 0: - print(f"❌ 克隆目标仓库失败:") - print(result.stderr) - return False - - os.chdir(work_dir) - - # 添加上游远程 - print(f"[2/6] 添加上游远程...") - result = subprocess.run( - ['git', 'remote', 'add', 'upstream', source_url], - capture_output=True, text=True - ) - - if result.returncode != 0: - print(f"❌ 添加上游远程失败:") - print(result.stderr) - return False - - # 获取上游更改 - print(f"[3/6] 获取上游更改...") - result = subprocess.run( - ['git', 'fetch', 'upstream'], - capture_output=True, text=True, timeout=600 - ) - - if result.returncode != 0: - print(f"❌ 获取上游更改失败:") - print(result.stderr) - return False - - # 检查目标分支是否存在 - print(f"[4/6] 检查分支...") - result = subprocess.run( - ['git', 'branch', '-a'], - capture_output=True, text=True - ) - - remote_branch = f'upstream/{branch}' - if remote_branch not in result.stdout: - print(f"❌ 上游分支 {branch} 不存在!") - print(f"可用的分支:") - print(result.stdout) - return False - - # 同步操作:重置到上游(确保完全同步) - print(f"[5/6] 同步到上游分支 (使用 reset --hard)...") - - # 首先更新本地分支 - subprocess.run(['git', 'fetch', 'origin'], capture_output=True) - - # 重置到上游状态 - result = subprocess.run( - ['git', 'reset', '--hard', f'upstream/{branch}'], - capture_output=True, text=True - ) - - if result.returncode != 0: - print(f"❌ 同步失败:") - print(result.stderr) - return False - - # 推送到目标 - print(f"[6/6] 推送到目标仓库...") - result = subprocess.run( - ['git', 'push', '--force', '--tags', 'origin', f'refs/heads/{branch}'], - capture_output=True, text=True, timeout=600 - ) - - if result.returncode != 0: - print(f"❌ 推送到目标仓库失败:") - print(result.stderr) - return False - - print(f"✅ [{name}] 同步成功!") - return True - - except subprocess.TimeoutExpired: - print(f"❌ [{name}] 操作超时!") - return False - except Exception as e: - print(f"❌ [{name}] 发生错误: {e}") - return False - finally: - # 返回原始目录并清理 - os.chdir('/') - if Path(work_dir).exists(): - shutil.rmtree(work_dir, ignore_errors=True) - - def load_config(): - """加载配置文件""" - config_path = os.environ.get('CONFIG_FILE', 'repos.yaml') - - if not os.path.exists(config_path): - print(f"错误: 配置文件 {config_path} 不存在!") - sys.exit(1) - - try: - with open(config_path, 'r', encoding='utf-8') as f: - config = yaml.safe_load(f) - - if not isinstance(config, dict) or 'repositories' not in config: - print(f"错误: 配置文件格式不正确,需要包含 'repositories' 键") - sys.exit(1) - - repos = config['repositories'] - - if not isinstance(repos, list) or len(repos) == 0: - print(f"警告: 配置文件中未找到仓库配置") - return [] - - # 过滤掉注释掉的或空配置 - valid_repos = [ - repo for repo in repos - if isinstance(repo, dict) and - repo.get('name') and - not repo.get('name', '').strip().startswith('#') and - (repo.get('source_url') or '').strip() - ] - - print(f"✓ 找到 {len(valid_repos)} 个仓库配置") - return valid_repos - - except yaml.YAMLError as e: - print(f"错误: 解析YAML配置文件失败: {e}") - sys.exit(1) - except Exception as e: - print(f"错误: 读取配置文件失败: {e}") - sys.exit(1) - - def main(): - print("="*80) - print("多仓库同步工具") - print("="*80) - print(f"\n开始时间: {time.strftime('%Y-%m-%d %H:%M:%S')}") - - # 检查必要的环境变量 - required_vars = ['UPSTREAM_USERNAME', 'UPSTREAM_TOKEN', 'TARGET_USERNAME', 'TARGET_TOKEN'] - missing_vars = [var for var in required_vars if not os.environ.get(var)] - - if missing_vars: - print(f"\n❌ 错误: 缺少必要的环境变量: {', '.join(missing_vars)}") - print("请在 Gitea/Actions Secrets 中设置这些变量") - sys.exit(1) - - print("✓ 所有必要环境变量已设置") - - # 加载配置 - repos = load_config() - - if not repos: - print("\n⚠️ 没有需要同步的仓库,退出") - sys.exit(0) - - # 同步所有仓库 - results = [] - - for i, repo in enumerate(repos, 1): - print(f"\n[{i}/{len(repos)}] 正在同步...") - success = sync_repository(repo) - results.append((repo['name'], success)) - - # 在仓库之间等待一下,避免过于频繁的操作 - if i < len(repos): - time.sleep(5) - - # 总结报告 - print("\n" + "="*80) - print("同步完成报告") - print("="*80) - - for name, success in results: - status = "✅ 成功" if success else "❌ 失败" - print(f"{status} {name}") - - successful = sum(1 for _, s in results if s) - failed = len(results) - successful - - print(f"\n总计: {len(results)} 个仓库") - print(f"成功: {successful} 个") - print(f"失败: {failed} 个") - - if failed > 0: - print("\n❌ 有仓库同步失败,请查看详细日志") - sys.exit(1) - else: - print("\n✅ 所有仓库同步成功!") - - print(f"\n结束时间: {time.strftime('%Y-%m-%d %H:%M:%S')}") - - if __name__ == '__main__': - main() + run: python3 sync_repos.py diff --git a/sync_repos.py b/sync_repos.py new file mode 100644 index 0000000..ad792c7 --- /dev/null +++ b/sync_repos.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +import yaml +import os +import sys +import subprocess +from pathlib import Path +import tempfile +import shutil +import time +def sync_repository(repo_config): + """同步单个仓库""" + name = repo_config.get('name', 'unnamed-repo') + source_url = repo_config.get('source_url', '') + target_url = repo_config.get('target_url', '') + branch = repo_config.get('branch', 'master') + if not source_url or not target_url: + print(f"❌ [{name}] 错误: 缺少 source_url 或 target_url") + return False + # 替换环境变量 + source_url = os.path.expandvars(source_url) + target_url = os.path.expandvars(target_url) + # 检查必要的环境变量是否设置 + if '${' in source_url: + print(f"⚠️ [{name}] 警告: source_url 中的环境变量未完全解析: {source_url}") + if '${' in target_url: + print(f"⚠️ [{name}] 警告: target_url 中的环境变量未完全解析: {target_url}") + print(f"\n{'='*60}") + print(f"开始同步仓库: {name}") + print(f"源地址: {source_url.split('@')[-1] if '@' in source_url else source_url}") + print(f"目标地址: {target_url.split('@')[-1] if '@' in target_url else target_url}") + print(f"分支: {branch}") + print(f"{'='*60}") + # 创建临时工作目录 + work_dir = tempfile.mkdtemp(prefix=f"sync_{name}_") + try: + # 克隆目标仓库 + print(f"\n[1/6] 克隆目标仓库...") + result = subprocess.run( + ['git', 'clone', '--bare', target_url, work_dir], + capture_output=True, text=True, timeout=300 + ) + if result.returncode != 0: + print(f"❌ 克隆目标仓库失败:") + print(result.stderr) + return False + os.chdir(work_dir) + # 添加上游远程 + print(f"[2/6] 添加上游远程...") + result = subprocess.run( + ['git', 'remote', 'add', 'upstream', source_url], + capture_output=True, text=True + ) + if result.returncode != 0: + print(f"❌ 添加上游远程失败:") + print(result.stderr) + return False + # 获取上游更改 + print(f"[3/6] 获取上游更改...") + result = subprocess.run( + ['git', 'fetch', 'upstream'], + capture_output=True, text=True, timeout=600 + ) + if result.returncode != 0: + print(f"❌ 获取上游更改失败:") + print(result.stderr) + return False + # 检查目标分支是否存在 + print(f"[4/6] 检查分支...") + result = subprocess.run( + ['git', 'branch', '-a'], + capture_output=True, text=True + ) + remote_branch = f'upstream/{branch}' + if remote_branch not in result.stdout: + print(f"❌ 上游分支 {branch} 不存在!") + print(f"可用的分支:") + print(result.stdout) + return False + # 同步操作:重置到上游(确保完全同步) + print(f"[5/6] 同步到上游分支 (使用 reset --hard)...") + # 首先更新本地分支 + subprocess.run(['git', 'fetch', 'origin'], capture_output=True) + # 重置到上游状态 + result = subprocess.run( + ['git', 'reset', '--hard', f'upstream/{branch}'], + capture_output=True, text=True + ) + if result.returncode != 0: + print(f"❌ 同步失败:") + print(result.stderr) + return False + # 推送到目标 + print(f"[6/6] 推送到目标仓库...") + result = subprocess.run( + ['git', 'push', '--force', '--tags', 'origin', f'refs/heads/{branch}'], + capture_output=True, text=True, timeout=600 + ) + if result.returncode != 0: + print(f"❌ 推送到目标仓库失败:") + print(result.stderr) + return False + print(f"✅ [{name}] 同步成功!") + return True + except subprocess.TimeoutExpired: + print(f"❌ [{name}] 操作超时!") + return False + except Exception as e: + print(f"❌ [{name}] 发生错误: {e}") + return False + finally: + # 返回原始目录并清理 + os.chdir('/') + if Path(work_dir).exists(): + shutil.rmtree(work_dir, ignore_errors=True) +def load_config(): + """加载配置文件""" + config_path = os.environ.get('CONFIG_FILE', 'repos.yaml') + if not os.path.exists(config_path): + print(f"错误: 配置文件 {config_path} 不存在!") + sys.exit(1) + try: + with open(config_path, 'r', encoding='utf-8') as f: + config = yaml.safe_load(f) + if not isinstance(config, dict) or 'repositories' not in config: + print(f"错误: 配置文件格式不正确,需要包含 'repositories' 键") + sys.exit(1) + repos = config['repositories'] + if not isinstance(repos, list) or len(repos) == 0: + print(f"警告: 配置文件中未找到仓库配置") + return [] + # 过滤掉注释掉的或空配置 + valid_repos = [ + repo for repo in repos + if isinstance(repo, dict) and + repo.get('name') and + not repo.get('name', '').strip().startswith('#') and + (repo.get('source_url') or '').strip() + ] + print(f"✓ 找到 {len(valid_repos)} 个仓库配置") + return valid_repos + except yaml.YAMLError as e: + print(f"错误: 解析YAML配置文件失败: {e}") + sys.exit(1) + except Exception as e: + print(f"错误: 读取配置文件失败: {e}") + sys.exit(1) +def main(): + print("="*80) + print("多仓库同步工具") + print("="*80) + print(f"\n开始时间: {time.strftime('%Y-%m-%d %H:%M:%S')}") + # 检查必要的环境变量 + required_vars = ['UPSTREAM_USERNAME', 'UPSTREAM_TOKEN', 'TARGET_USERNAME', 'TARGET_TOKEN'] + missing_vars = [var for var in required_vars if not os.environ.get(var)] + if missing_vars: + print(f"\n❌ 错误: 缺少必要的环境变量: {', '.join(missing_vars)}") + print("请在 Gitea/Actions Secrets 中设置这些变量") + sys.exit(1) + print("✓ 所有必要环境变量已设置") + # 加载配置 + repos = load_config() + if not repos: + print("\n⚠️ 没有需要同步的仓库,退出") + sys.exit(0) + # 同步所有仓库 + results = [] + for i, repo in enumerate(repos, 1): + print(f"\n[{i}/{len(repos)}] 正在同步...") + success = sync_repository(repo) + results.append((repo['name'], success)) + # 在仓库之间等待一下,避免过于频繁的操作 + if i < len(repos): + time.sleep(5) + # 总结报告 + print("\n" + "="*80) + print("同步完成报告") + print("="*80) + for name, success in results: + status = "✅ 成功" if success else "❌ 失败" + print(f"{status} {name}") + successful = sum(1 for _, s in results if s) + failed = len(results) - successful + print(f"\n总计: {len(results)} 个仓库") + print(f"成功: {successful} 个") + print(f"失败: {failed} 个") + if failed > 0: + print("\n❌ 有仓库同步失败,请查看详细日志") + sys.exit(1) + else: + print("\n✅ 所有仓库同步成功!") + print(f"\n结束时间: {time.strftime('%Y-%m-%d %H:%M:%S')}") +if __name__ == '__main__': + main()