Compare commits
8 Commits
multi-repo
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| cb22214784 | |||
| 989dd10aef | |||
| c8b749aade | |||
| 215df4f140 | |||
| cbf2aa37fd | |||
| 166223ef3f | |||
| 66511432ed | |||
| 6161f2875d |
57
.gitea/workflows/aliyun-repo-merge.yml
Normal file
57
.gitea/workflows/aliyun-repo-merge.yml
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
name: 阿里云仓库合并流水线(合并模式)
|
||||||
|
|
||||||
|
# 注意:此workflow使用合并模式(git merge --no-ff),会保留目标仓库的更改历史
|
||||||
|
# 如果遇到合并冲突,会跳过该仓库并继续处理其他仓库
|
||||||
|
# 如果需要强制同步(镜像模式),请使用 aliyun-repo-sync.yml workflow
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths:
|
||||||
|
- 'aliyun_repos.yaml'
|
||||||
|
- '.gitea/workflows/aliyun-repo-merge.yml'
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
# 每天凌晨2点执行
|
||||||
|
- cron: '0 2 * * 0'
|
||||||
|
|
||||||
|
env:
|
||||||
|
CONFIG_FILE: 'aliyun_repos.yaml'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
merge-repositories:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 签出配置仓库
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: 配置 Git
|
||||||
|
run: |
|
||||||
|
git config --global user.name "gitea-runner"
|
||||||
|
git config --global user.email "actions@gitea.fjy8018.top"
|
||||||
|
git config --global init.defaultBranch master
|
||||||
|
|
||||||
|
- name: 安装依赖
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y python3-yaml jq
|
||||||
|
|
||||||
|
- name: 验证配置文件
|
||||||
|
run: |
|
||||||
|
if [ ! -f "${{ env.CONFIG_FILE }}" ]; then
|
||||||
|
echo "错误: 配置文件 ${{ env.CONFIG_FILE }} 不存在!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✓ 配置文件已找到"
|
||||||
|
|
||||||
|
- name: 从配置文件中提取仓库并合并
|
||||||
|
env:
|
||||||
|
UPSTREAM_USERNAME: ${{ secrets.CODEUP_USERNAME }}
|
||||||
|
UPSTREAM_TOKEN: ${{ secrets.CODEUP_PASSWORD }}
|
||||||
|
TARGET_USERNAME: ${{ secrets.TARGET_USERNAME }}
|
||||||
|
TARGET_TOKEN: ${{ secrets.TARGET_TOKEN }}
|
||||||
|
run: python3 merge_repos.py
|
||||||
53
.gitea/workflows/aliyun-repo-sync.yml
Normal file
53
.gitea/workflows/aliyun-repo-sync.yml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
name: 阿里云仓库同步流水线(强制同步/镜像模式)
|
||||||
|
|
||||||
|
# 注意:此workflow使用强制同步模式(git reset --hard),会覆盖目标仓库的所有更改
|
||||||
|
# 如果需要使用合并模式(merge --no-ff),请使用 aliyun-repo-merge.yml workflow
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths:
|
||||||
|
- 'aliyun_repos.yaml'
|
||||||
|
- '.gitea/workflows/aliyun-repo-sync.yml'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
CONFIG_FILE: 'aliyun_repos.yaml'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
sync-repositories:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 签出配置仓库
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: 配置 Git
|
||||||
|
run: |
|
||||||
|
git config --global user.name "gitea-runner"
|
||||||
|
git config --global user.email "actions@gitea.fjy8018.top"
|
||||||
|
git config --global init.defaultBranch master
|
||||||
|
|
||||||
|
- name: 安装依赖
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y python3-yaml jq
|
||||||
|
|
||||||
|
- name: 验证配置文件
|
||||||
|
run: |
|
||||||
|
if [ ! -f "${{ env.CONFIG_FILE }}" ]; then
|
||||||
|
echo "错误: 配置文件 ${{ env.CONFIG_FILE }} 不存在!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✓ 配置文件已找到"
|
||||||
|
|
||||||
|
- name: 从配置文件中提取仓库并同步
|
||||||
|
env:
|
||||||
|
UPSTREAM_USERNAME: ${{ secrets.CODEUP_USERNAME }}
|
||||||
|
UPSTREAM_TOKEN: ${{ secrets.CODEUP_PASSWORD }}
|
||||||
|
TARGET_USERNAME: ${{ secrets.TARGET_USERNAME }}
|
||||||
|
TARGET_TOKEN: ${{ secrets.TARGET_TOKEN }}
|
||||||
|
run: python3 sync_repos.py
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
name: 多仓库同步流水线
|
name: BladeX仓库同步流水线
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
paths:
|
paths:
|
||||||
- 'repos.yaml'
|
- 'bladex_repos.yaml'
|
||||||
- '.gitea/workflows/*.yml'
|
- '.gitea/workflows/bladex-repo-sync.yml'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
# 每天凌晨1点执行
|
# 每天凌晨1点执行
|
||||||
- cron: '0 1 * * *'
|
- cron: '0 1 * * 0'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CONFIG_FILE: 'repos.yaml'
|
CONFIG_FILE: 'bladex_repos.yaml'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sync-repositories:
|
sync-repositories:
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
name: "同步 A → B"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
paths:
|
|
||||||
- 'configs/A_to_B.yaml'
|
|
||||||
- 'scripts/sync_tool.py'
|
|
||||||
- '.gitea/workflows/sync_A_to_B.yml'
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
# 每天凌晨2点执行
|
|
||||||
- cron: '0 2 * * *'
|
|
||||||
|
|
||||||
env:
|
|
||||||
CONFIG_FILE: 'configs/A_to_B.yaml'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
sync-repositories:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: 签出配置仓库
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 1
|
|
||||||
|
|
||||||
- name: 配置 Git
|
|
||||||
run: |
|
|
||||||
git config --global user.name "gitea-runner"
|
|
||||||
git config --global user.email "actions@gitea.fjy8018.top"
|
|
||||||
git config --global init.defaultBranch master
|
|
||||||
|
|
||||||
- name: 安装依赖
|
|
||||||
run: |
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y python3-yaml
|
|
||||||
|
|
||||||
- name: 验证配置文件
|
|
||||||
run: |
|
|
||||||
if [ ! -f "${{ env.CONFIG_FILE }}" ]; then
|
|
||||||
echo "错误: 配置文件 ${{ env.CONFIG_FILE }} 不存在!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "✓ 配置文件已找到: ${{ env.CONFIG_FILE }}"
|
|
||||||
|
|
||||||
- name: 执行同步 A → B
|
|
||||||
env:
|
|
||||||
SYNC_A_B_USERNAME: ${{ secrets.SYNC_A_B_USERNAME }}
|
|
||||||
SYNC_A_B_TOKEN: ${{ secrets.SYNC_A_B_TOKEN }}
|
|
||||||
run: |
|
|
||||||
echo "================================================================================"
|
|
||||||
echo "开始同步: A → B"
|
|
||||||
echo "使用配置: ${{ env.CONFIG_FILE }}"
|
|
||||||
echo "环境前缀: SYNC_A_B"
|
|
||||||
echo "================================================================================"
|
|
||||||
python3 scripts/sync_tool.py ${{ env.CONFIG_FILE }} --env-prefix SYNC_A_B
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
name: "同步 B → C"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
paths:
|
|
||||||
- 'configs/B_to_C.yaml'
|
|
||||||
- 'scripts/sync_tool.py'
|
|
||||||
- '.gitea/workflows/sync_B_to_C.yml'
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
# 每天凌晨3点执行(在 A→B 之后)
|
|
||||||
- cron: '0 3 * * *'
|
|
||||||
|
|
||||||
env:
|
|
||||||
CONFIG_FILE: 'configs/B_to_C.yaml'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
sync-repositories:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: 签出配置仓库
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 1
|
|
||||||
|
|
||||||
- name: 配置 Git
|
|
||||||
run: |
|
|
||||||
git config --global user.name "gitea-runner"
|
|
||||||
git config --global user.email "actions@gitea.fjy8018.top"
|
|
||||||
git config --global init.defaultBranch master
|
|
||||||
|
|
||||||
- name: 安装依赖
|
|
||||||
run: |
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y python3-yaml
|
|
||||||
|
|
||||||
- name: 验证配置文件
|
|
||||||
run: |
|
|
||||||
if [ ! -f "${{ env.CONFIG_FILE }}" ]; then
|
|
||||||
echo "错误: 配置文件 ${{ env.CONFIG_FILE }} 不存在!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "✓ 配置文件已找到: ${{ env.CONFIG_FILE }}"
|
|
||||||
|
|
||||||
- name: 执行同步 B → C
|
|
||||||
env:
|
|
||||||
SYNC_B_C_USERNAME: ${{ secrets.SYNC_B_C_USERNAME }}
|
|
||||||
SYNC_B_C_TOKEN: ${{ secrets.SYNC_B_C_TOKEN }}
|
|
||||||
run: |
|
|
||||||
echo "================================================================================"
|
|
||||||
echo "开始同步: B → C"
|
|
||||||
echo "使用配置: ${{ env.CONFIG_FILE }}"
|
|
||||||
echo "环境前缀: SYNC_B_C"
|
|
||||||
echo "================================================================================"
|
|
||||||
python3 scripts/sync_tool.py ${{ env.CONFIG_FILE }} --env-prefix SYNC_B_C
|
|
||||||
295
README.md
295
README.md
@@ -1,151 +1,113 @@
|
|||||||
# 多仓库同步系统
|
# 多仓库同步系统
|
||||||
|
|
||||||
这是一个支持多方向、多仓库批量同步的自动化流水线系统,适用于在不同 Git 服务器之间同步代码的场景。
|
这是一个支持批量同步多个 Git 仓库的自动化流水线系统,适用于从上游仓库同步代码到目标 Gitea 仓库。
|
||||||
|
|
||||||
## 🚀 功能特性
|
## 🚀 功能特性
|
||||||
|
|
||||||
- **多方向同步**:支持 A→B, B→C, A→C 等多种同步方向
|
- **批量同步**:通过配置文件一次性管理多个仓库同步
|
||||||
- **独立配置**:每个同步方向使用单独的配置文件和流水线
|
|
||||||
- **环境隔离**:每个方向使用不同的环境变量前缀,安全性更高
|
|
||||||
- **批量同步**:每个方向可以通过配置文件管理多个仓库
|
|
||||||
- **灵活调度**:每个方向可以配置不同的定时任务
|
|
||||||
- **手动触发**:支持手动触发单个方向的同步
|
|
||||||
- **详细日志**:提供每个仓库同步过程的详细日志
|
|
||||||
- **冲突自动处理**:使用 `reset --hard` 策略,确保目标仓库完全同步为上游状态
|
- **冲突自动处理**:使用 `reset --hard` 策略,确保目标仓库完全同步为上游状态
|
||||||
|
- **定时执行**:支持定时任务(每天凌晨1点自动执行)
|
||||||
|
- **手动触发**:支持手动触发和配置文件更新时自动触发
|
||||||
|
- **详细日志**:提供每个仓库同步过程的详细日志和总结报告
|
||||||
|
- **安全可靠**:内置超时机制和错误处理
|
||||||
|
|
||||||
## 📁 项目结构
|
## 📁 项目结构
|
||||||
|
|
||||||
```
|
```
|
||||||
code-sync-project/
|
.
|
||||||
|
├── repos.yaml # 仓库同步配置文件
|
||||||
├── .gitea/
|
├── .gitea/
|
||||||
│ └── workflows/
|
│ └── workflows/
|
||||||
│ ├── sync_A_to_B.yml # A→B 同步流水线
|
│ └── multi-repo-sync.yml # 同步流水线定义
|
||||||
│ └── sync_B_to_C.yml # B→C 同步流水线
|
|
||||||
├── configs/
|
|
||||||
│ ├── A_to_B.yaml # A→B 同步配置
|
|
||||||
│ └── B_to_C.yaml # B→C 同步配置
|
|
||||||
├── scripts/
|
|
||||||
│ └── sync_tool.py # 核心同步工具(支持动态环境变量前缀)
|
|
||||||
├── repos.yaml # 旧版单配置文件(向后兼容)
|
|
||||||
├── sync_repos.py # 旧版同步脚本(向后兼容)
|
|
||||||
└── README.md # 本文档
|
└── README.md # 本文档
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**注意:** Gitea Actions 支持两种工作流路径:
|
||||||
|
- `.gitea/workflows/` (官方推荐)
|
||||||
|
- `.github/workflows/` (兼容性支持)
|
||||||
|
|
||||||
## 🔧 配置说明
|
## 🔧 配置说明
|
||||||
|
|
||||||
### 1. 配置 Secrets
|
### 1. 配置文件 (repos.yaml)
|
||||||
|
|
||||||
在 Gitea 仓库的 **Settings > Secrets** 中配置以下变量:
|
在项目根目录的 `repos.yaml` 文件中定义需要同步的仓库:
|
||||||
|
|
||||||
#### 对于 A→B 同步:
|
|
||||||
```bash
|
|
||||||
SYNC_A_B_USERNAME=your_username # 源A的用户名
|
|
||||||
SYNC_A_B_TOKEN=your_token # 源A的访问令牌
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 对于 B→C 同步:
|
|
||||||
```bash
|
|
||||||
SYNC_B_C_USERNAME=your_username # 源B的用户名
|
|
||||||
SYNC_B_C_TOKEN=your_token # 源B的访问令牌
|
|
||||||
```
|
|
||||||
|
|
||||||
**注意**:每个同步方向使用不同的环境变量前缀,确保安全性。
|
|
||||||
|
|
||||||
### 2. 配置同步任务
|
|
||||||
|
|
||||||
编辑 `configs/A_to_B.yaml`:
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
name: "A_to_B_Sync" # 同步任务名称
|
# 多仓库同步配置文件
|
||||||
|
# 定义需要同步的仓库对,源仓库 -> 目标仓库
|
||||||
repositories:
|
repositories:
|
||||||
- name: "bladex-tool"
|
# 示例:同步 BladeX-Tool 仓库
|
||||||
source_url: "https://${USERNAME}:${TOKEN}@center.javablade.com/blade/BladeX-Tool.git"
|
- name: "bladex-tool" # 仓库名称(用于日志输出)
|
||||||
target_url: "https://${USERNAME}:${TOKEN}@gitea.fjy8018.top/BladeX/BladeX-Tool.git"
|
source_url: "https://${UPSTREAM_USERNAME}:${UPSTREAM_TOKEN}@center.javablade.com/blade/BladeX-Tool.git"
|
||||||
branch: "master"
|
target_url: "https://${TARGET_USERNAME}:${TARGET_TOKEN}@gitea.fjy8018.top/home/BladeX-Tool.git"
|
||||||
|
branch: "master" # 要同步的分支
|
||||||
|
|
||||||
# 添加更多仓库...
|
# 示例:同步另一个仓库(取消注释并修改以下配置)
|
||||||
```
|
- name: "another-repo"
|
||||||
|
source_url: "https://${UPSTREAM_USERNAME}:${UPSTREAM_TOKEN}@github.com/example/repo.git"
|
||||||
|
target_url: "https://${TARGET_USERNAME}:${TARGET_TOKEN}@gitea.fjy8018.top/home/repo.git"
|
||||||
|
branch: "main"
|
||||||
|
|
||||||
编辑 `configs/B_to_C.yaml`:
|
# 添加更多仓库同步配置...
|
||||||
|
|
||||||
```yaml
|
|
||||||
name: "B_to_C_Sync" # 同步任务名称
|
|
||||||
|
|
||||||
repositories:
|
|
||||||
- name: "bladex-tool"
|
|
||||||
source_url: "https://${USERNAME}:${TOKEN}@gitea.fjy8018.top/BladeX/BladeX-Tool.git"
|
|
||||||
target_url: "https://${USERNAME}:${TOKEN}@backup.example.com/backup/BladeX-Tool.git"
|
|
||||||
branch: "master"
|
|
||||||
|
|
||||||
# 添加更多仓库...
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**配置字段说明:**
|
**配置字段说明:**
|
||||||
|
|
||||||
| 字段 | 必填 | 说明 |
|
| 字段 | 必填 | 说明 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| `name` | 是 | 同步任务名称,用于日志输出 |
|
| `name` | 是 | 仓库标识名称,用于日志输出 |
|
||||||
| `repositories[].name` | 是 | 仓库标识名称 |
|
| `source_url` | 是 | 上游仓库地址,支持环境变量 |
|
||||||
| `repositories[].source_url` | 是 | 上游仓库地址,使用 `${USERNAME}:${TOKEN}` 格式 |
|
| `target_url` | 是 | 目标 Gitea 仓库地址,支持环境变量 |
|
||||||
| `repositories[].target_url` | 是 | 目标仓库地址,使用 `${USERNAME}:${TOKEN}` 格式 |
|
| `branch` | 否 | 要同步的分支,默认为 `master` |
|
||||||
| `repositories[].branch` | 否 | 要同步的分支,默认为 `master` |
|
|
||||||
|
|
||||||
### 3. 定时任务
|
### 2. 环境变量
|
||||||
|
|
||||||
A→B:每天凌晨 2 点执行(`.gitea/workflows/sync_A_to_B.yml`)
|
在 Gitea 仓库的 **Settings > Secrets** 中配置以下变量:
|
||||||
```yaml
|
|
||||||
schedule:
|
|
||||||
- cron: '0 2 * * *'
|
|
||||||
```
|
|
||||||
|
|
||||||
B→C:每天凌晨 3 点执行(`.gitea/workflows/sync_B_to_C.yml`)
|
| 变量名 | 必填 | 说明 |
|
||||||
```yaml
|
|--------|------|------|
|
||||||
schedule:
|
| `UPSTREAM_USERNAME` | 是 | 上游仓库用户名 |
|
||||||
- cron: '0 3 * * *'
|
| `UPSTREAM_TOKEN` | 是 | 上游仓库访问令牌/密码 |
|
||||||
```
|
| `TARGET_USERNAME` | 是 | 目标 Gitea 用户名 |
|
||||||
|
| `TARGET_TOKEN` | 是 | 目标 Gitea 访问令牌 |
|
||||||
|
|
||||||
可以在对应的 workflow 文件中修改 cron 表达式。
|
**注意:** 变量名不能以 `GITEA_` 或 `GITHUB_` 开头(这些是系统保留前缀),建议使用 `TARGET_` 或 `UPSTREAM_` 等前缀区分不同仓库的凭证。
|
||||||
|
|
||||||
|
**安全建议:** 使用 Personal Access Token (PAT) 而不是密码,以提高安全性。
|
||||||
|
|
||||||
## 🚀 使用方法
|
## 🚀 使用方法
|
||||||
|
|
||||||
### 方法 1:自动触发
|
### 方法 1:手动触发
|
||||||
|
|
||||||
当满足以下条件之一时自动触发:
|
进入 Gitea 仓库页面的 **Actions** 标签页,选择 "多仓库同步流水线",点击 **Run workflow** 手动执行。
|
||||||
|
|
||||||
- 定时任务到达设定时间
|
### 方法 2:定时任务
|
||||||
- 修改了对应的配置文件
|
|
||||||
- 修改了对应的流水线文件
|
|
||||||
- 修改了核心脚本 `sync_tool.py`
|
|
||||||
|
|
||||||
### 方法 2:手动触发
|
流水线默认配置为每天凌晨 1 点(UTC)自动执行。可在 `.gitea/workflows/multi-repo-sync.yml` 文件中修改 cron 表达式:
|
||||||
|
|
||||||
进入 Gitea 仓库页面的 **Actions** 标签页:
|
```yaml
|
||||||
|
schedule:
|
||||||
|
- cron: '0 1 * * *' # 每天凌晨1点执行
|
||||||
|
```
|
||||||
|
|
||||||
1. 选择 "同步 A → B" 或 "同步 B → C"
|
### 方法 3:配置文件更新时自动触发
|
||||||
2. 点击 **Run workflow**
|
|
||||||
3. 查看执行日志
|
当修改 `repos.yaml` 或工作流文件时,会自动触发同步。
|
||||||
|
|
||||||
## 📊 执行结果
|
## 📊 执行结果
|
||||||
|
|
||||||
执行后,Actions 页面会显示:
|
执行成功后,可以在 Gitea Actions 页面查看:
|
||||||
|
|
||||||
|
- 每个仓库的同步状态(成功/失败)
|
||||||
|
- 详细的同步日志
|
||||||
|
- 同步完成总结报告
|
||||||
|
|
||||||
|
示例输出:
|
||||||
```
|
```
|
||||||
================================================================================
|
|
||||||
多仓库同步工具
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
开始时间: 2025-11-22 10:00:00
|
|
||||||
配置文件: configs/A_to_B.yaml
|
|
||||||
环境前缀: SYNC_A_B
|
|
||||||
✓ 配置任务: A_to_B_Sync
|
|
||||||
✓ 找到 2 个仓库配置
|
|
||||||
|
|
||||||
[1/2] 正在同步...
|
|
||||||
============================================================
|
============================================================
|
||||||
开始同步仓库: bladex-tool
|
开始同步仓库: bladex-tool
|
||||||
源地址: ***:***@center.javablade.com/blade/BladeX-Tool.git
|
源地址: center.javablade.com/blade/BladeX-Tool.git
|
||||||
目标地址: ***:***@gitea.fjy8018.top/BladeX/BladeX-Tool.git
|
目标地址: gitea.fjy8018.top/home/BladeX-Tool.git
|
||||||
分支: master
|
分支: master
|
||||||
============================================================
|
============================================================
|
||||||
|
|
||||||
@@ -153,103 +115,16 @@ schedule:
|
|||||||
[2/6] 添加上游远程...
|
[2/6] 添加上游远程...
|
||||||
[3/6] 获取上游更改...
|
[3/6] 获取上游更改...
|
||||||
[4/6] 检查分支...
|
[4/6] 检查分支...
|
||||||
[5/6] 同步到上游分支 (使用 reset --hard)...
|
[5/6] 同步到上游分支...
|
||||||
[6/6] 推送到目标仓库...
|
[6/6] 推送到目标仓库...
|
||||||
✅ [bladex-tool] 同步成功!
|
✅ [bladex-tool] 同步成功!
|
||||||
|
|
||||||
================================================================================
|
|
||||||
同步完成报告
|
|
||||||
================================================================================
|
|
||||||
✅ 成功 bladex-tool
|
|
||||||
✅ 成功 another-repo
|
|
||||||
|
|
||||||
总计: 2 个仓库
|
|
||||||
成功: 2 个
|
|
||||||
失败: 0 个
|
|
||||||
|
|
||||||
✅ 所有仓库同步成功!
|
|
||||||
|
|
||||||
结束时间: 2025-11-22 10:05:00
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 添加新的同步方向
|
|
||||||
|
|
||||||
要添加新的同步方向(例如 A→C):
|
|
||||||
|
|
||||||
### 步骤 1:创建配置文件
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp configs/A_to_B.yaml configs/A_to_C.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
编辑 `configs/A_to_C.yaml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
name: "A_to_C_Sync"
|
|
||||||
|
|
||||||
repositories:
|
|
||||||
- name: "bladex-tool"
|
|
||||||
source_url: "https://${USERNAME}:${TOKEN}@center.javablade.com/blade/BladeX-Tool.git"
|
|
||||||
target_url: "https://${USERNAME}:${TOKEN}@backup.example.com/backup/BladeX-Tool.git"
|
|
||||||
branch: "master"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 步骤 2:创建流水线
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp .gitea/workflows/sync_A_to_B.yml .gitea/workflows/sync_A_to_C.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
编辑 `.gitea/workflows/sync_A_to_C.yml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
name: "同步 A → C"
|
|
||||||
|
|
||||||
env:
|
|
||||||
CONFIG_FILE: 'configs/A_to_C.yaml'
|
|
||||||
|
|
||||||
# ... 其他配置 ...
|
|
||||||
|
|
||||||
- name: 执行同步 A → C
|
|
||||||
env:
|
|
||||||
SYNC_A_C_USERNAME: ${{ secrets.SYNC_A_C_USERNAME }}
|
|
||||||
SYNC_A_C_TOKEN: ${{ secrets.SYNC_A_C_TOKEN }}
|
|
||||||
run: |
|
|
||||||
python3 scripts/sync_tool.py ${{ env.CONFIG_FILE }} --env-prefix SYNC_A_C
|
|
||||||
```
|
|
||||||
|
|
||||||
### 步骤 3:配置 Secrets
|
|
||||||
|
|
||||||
在 Gitea 中配置:
|
|
||||||
- `SYNC_A_C_USERNAME`
|
|
||||||
- `SYNC_A_C_TOKEN`
|
|
||||||
|
|
||||||
### 步骤 4:提交并推送
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add configs/A_to_C.yaml .gitea/workflows/sync_A_to_C.yml
|
|
||||||
git commit -m "添加 A→C 同步方向"
|
|
||||||
git push
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🛠️ 本地测试
|
|
||||||
|
|
||||||
在本地测试同步:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 设置环境变量
|
|
||||||
export SYNC_A_B_USERNAME="your_username"
|
|
||||||
export SYNC_A_B_TOKEN="your_token"
|
|
||||||
|
|
||||||
# 运行同步
|
|
||||||
python3 scripts/sync_tool.py configs/A_to_B.yaml --env-prefix SYNC_A_B
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## ⚠️ 重要说明
|
## ⚠️ 重要说明
|
||||||
|
|
||||||
1. **强制同步策略**:使用 `git reset --hard` 策略,确保目标仓库**完全与上游一致**。任何在目标仓库上的修改都可能被覆盖。
|
1. **强制同步策略**:本流水线使用 `git reset --hard` 策略,确保目标仓库**完全与上游一致**。任何在目标仓库上的修改都可能被覆盖。
|
||||||
|
|
||||||
2. **权限要求**:提供的 Token 必须有对目标仓库的写入权限。
|
2. **权限要求**:确保提供的 Token 具有对目标仓库的写入权限。
|
||||||
|
|
||||||
3. **冲突处理**:由于采用强制重置策略,不会保留目标仓库与上游的冲突修改。
|
3. **冲突处理**:由于采用强制重置策略,不会保留目标仓库与上游的冲突修改。
|
||||||
|
|
||||||
@@ -269,50 +144,32 @@ python3 scripts/sync_tool.py configs/A_to_B.yaml --env-prefix SYNC_A_B
|
|||||||
### 问题:仓库不存在
|
### 问题:仓库不存在
|
||||||
|
|
||||||
**解决方案**:
|
**解决方案**:
|
||||||
- 在目标服务器上预先创建空仓库
|
- 在目标 Gitea 上预先创建空仓库
|
||||||
- 检查配置文件中的仓库地址是否正确
|
- 检查配置文件中的仓库地址是否正确
|
||||||
|
|
||||||
### 问题:同步超时
|
### 问题:同步超时
|
||||||
|
|
||||||
**解决方案**:
|
**解决方案**:
|
||||||
- 在 workflow 文件中增加超时时间
|
- 在 `.gitea/workflows/multi-repo-sync.yml` 中增加超时时间
|
||||||
- 检查网络连接是否稳定
|
- 对于大仓库,考虑使用浅克隆(已优化为 bare clone)
|
||||||
|
|
||||||
### 问题:环境变量未设置
|
### 问题:配置文件解析错误
|
||||||
|
|
||||||
**解决方案**:
|
**解决方案**:
|
||||||
- 检查是否在 Gitea Secrets 中配置了对应的变量
|
- 使用 YAML 格式验证工具检查 repos.yaml
|
||||||
- 检查环境变量前缀是否与配置文件中的一致
|
- 确保所有必要字段都已填写
|
||||||
- 查看日志中的环境变量检查输出
|
|
||||||
|
|
||||||
## 📖 最佳实践
|
## 📝 最佳实践
|
||||||
|
|
||||||
1. **分环境配置**:不同环境(开发/测试/生产)使用不同的配置
|
1. **逐步添加仓库**:初次使用时,先配置 1-2 个仓库测试
|
||||||
2. **逐步添加仓库**:初次使用时,先配置 1-2 个仓库测试
|
2. **使用变量**:将敏感信息和可变信息放在 Secrets 中
|
||||||
3. **监控执行**:定期检查 Actions 执行日志
|
3. **监控执行**:定期检查流水线执行日志
|
||||||
4. **测试环境**:先在测试仓库验证同步逻辑
|
4. **测试环境**:先在测试仓库验证同步逻辑
|
||||||
5. **备份策略**:重要仓库确保有备份机制
|
5. **备份策略**:重要仓库确保有备份机制
|
||||||
6. **安全隔离**:不同方向使用不同的 Token,降低风险
|
|
||||||
|
|
||||||
## 🔄 向后兼容
|
|
||||||
|
|
||||||
保留旧版文件以支持单配置文件场景:
|
|
||||||
|
|
||||||
- `repos.yaml` - 单配置文件
|
|
||||||
- `sync_repos.py` - 旧版同步脚本
|
|
||||||
- `.gitea/workflows/multi-repo-sync.yml` - 旧版流水线
|
|
||||||
|
|
||||||
如需使用旧版,请配置以下 Secrets:
|
|
||||||
- `UPSTREAM_USERNAME`
|
|
||||||
- `UPSTREAM_TOKEN`
|
|
||||||
- `TARGET_USERNAME`
|
|
||||||
- `TARGET_TOKEN`
|
|
||||||
|
|
||||||
**建议**:新项目使用新版多配置文件架构。
|
|
||||||
|
|
||||||
## 🤝 贡献
|
## 🤝 贡献
|
||||||
|
|
||||||
欢迎提交 Issue 和 Pull Request 来改进这个工具!
|
欢迎提交 Issue 和 Pull Request 来改进此项目。
|
||||||
|
|
||||||
## 📄 许可证
|
## 📄 许可证
|
||||||
|
|
||||||
|
|||||||
15
aliyun_repos.yaml
Normal file
15
aliyun_repos.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# 多仓库同步配置文件
|
||||||
|
# 定义需要同步的仓库对,源仓库 -> 目标仓库
|
||||||
|
repositories:
|
||||||
|
- name: "pve-k3s-gitops"
|
||||||
|
source_url: "https://${UPSTREAM_USERNAME}:${UPSTREAM_TOKEN}@codeup.aliyun.com/619ddf2bcfe94d7ade4c41ab/fjy8018/pve-k3s-gitops.git"
|
||||||
|
target_url: "https://${TARGET_USERNAME}:${TARGET_TOKEN}@gitea.fjy8018.top/fjy8018/pve-k3s-gitops.git"
|
||||||
|
branch: "master"
|
||||||
|
- name: "shell-util"
|
||||||
|
source_url: "https://${UPSTREAM_USERNAME}:${UPSTREAM_TOKEN}@codeup.aliyun.com/619ddf2bcfe94d7ade4c41ab/util/shell-util.git"
|
||||||
|
target_url: "https://${TARGET_USERNAME}:${TARGET_TOKEN}@gitea.fjy8018.top/util/shell-util.git"
|
||||||
|
branch: "master"
|
||||||
|
- name: "environment"
|
||||||
|
source_url: "https://${UPSTREAM_USERNAME}:${UPSTREAM_TOKEN}@codeup.aliyun.com/619ddf2bcfe94d7ade4c41ab/fjy8018/environment.git"
|
||||||
|
target_url: "https://${TARGET_USERNAME}:${TARGET_TOKEN}@gitea.fjy8018.top/fjy8018/environment.git"
|
||||||
|
branch: "master"
|
||||||
35
bladex_repos.yaml
Normal file
35
bladex_repos.yaml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# 多仓库同步配置文件
|
||||||
|
# 定义需要同步的仓库对,源仓库 -> 目标仓库
|
||||||
|
repositories:
|
||||||
|
- name: "bladex-tool"
|
||||||
|
source_url: "https://${UPSTREAM_USERNAME}:${UPSTREAM_TOKEN}@center.javablade.com/blade/BladeX-Tool.git"
|
||||||
|
target_url: "https://${TARGET_USERNAME}:${TARGET_TOKEN}@gitea.fjy8018.top/BladeX/BladeX-Tool.git"
|
||||||
|
branch: "master"
|
||||||
|
- name: "BladeX-Safety"
|
||||||
|
source_url: "https://${UPSTREAM_USERNAME}:${UPSTREAM_TOKEN}@center.javablade.com/blade/BladeX-Safety.git"
|
||||||
|
target_url: "https://${TARGET_USERNAME}:${TARGET_TOKEN}@gitea.fjy8018.top/BladeX/BladeX-Safety.git"
|
||||||
|
branch: "master"
|
||||||
|
- name: "bladex"
|
||||||
|
source_url: "https://${UPSTREAM_USERNAME}:${UPSTREAM_TOKEN}@center.javablade.com/blade/BladeX.git"
|
||||||
|
target_url: "https://${TARGET_USERNAME}:${TARGET_TOKEN}@gitea.fjy8018.top/BladeX/BladeX.git"
|
||||||
|
branch: "master"
|
||||||
|
- name: "BladeX-Doc"
|
||||||
|
source_url: "https://${UPSTREAM_USERNAME}:${UPSTREAM_TOKEN}@center.javablade.com/blade/BladeX-Doc.git"
|
||||||
|
target_url: "https://${TARGET_USERNAME}:${TARGET_TOKEN}@gitea.fjy8018.top/BladeX/BladeX-Doc.git"
|
||||||
|
branch: "master"
|
||||||
|
- name: "BladeX-Boot"
|
||||||
|
source_url: "https://${UPSTREAM_USERNAME}:${UPSTREAM_TOKEN}@center.javablade.com/blade/BladeX-Boot.git"
|
||||||
|
target_url: "https://${TARGET_USERNAME}:${TARGET_TOKEN}@gitea.fjy8018.top/BladeX/BladeX-Boot.git"
|
||||||
|
branch: "master"
|
||||||
|
- name: "Saber"
|
||||||
|
source_url: "https://${UPSTREAM_USERNAME}:${UPSTREAM_TOKEN}@center.javablade.com/blade/Saber.git"
|
||||||
|
target_url: "https://${TARGET_USERNAME}:${TARGET_TOKEN}@gitea.fjy8018.top/BladeX/Saber.git"
|
||||||
|
branch: "master"
|
||||||
|
- name: "BladeX-Biz"
|
||||||
|
source_url: "https://${UPSTREAM_USERNAME}:${UPSTREAM_TOKEN}@center.javablade.com/blade/BladeX-Biz.git"
|
||||||
|
target_url: "https://${TARGET_USERNAME}:${TARGET_TOKEN}@gitea.fjy8018.top/BladeX/BladeX-Biz.git"
|
||||||
|
branch: "master"
|
||||||
|
- name: "Saber3"
|
||||||
|
source_url: "https://${UPSTREAM_USERNAME}:${UPSTREAM_TOKEN}@center.javablade.com/blade/Saber3.git"
|
||||||
|
target_url: "https://${TARGET_USERNAME}:${TARGET_TOKEN}@gitea.fjy8018.top/BladeX/Saber3.git"
|
||||||
|
branch: "master"
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# A -> B 同步配置示例
|
|
||||||
# 这个配置使用环境变量前缀: SYNC_A_B
|
|
||||||
# 需要在 Gitea Secrets 中配置:
|
|
||||||
# - SYNC_A_B_USERNAME
|
|
||||||
# - SYNC_A_B_TOKEN
|
|
||||||
|
|
||||||
name: "A_to_B_Sync" # 同步任务名称,用于日志输出
|
|
||||||
|
|
||||||
repositories:
|
|
||||||
# 示例:从源A同步到目标B
|
|
||||||
- name: "bladex-tool"
|
|
||||||
source_url: "https://${USERNAME}:${TOKEN}@center.javablade.com/blade/BladeX-Tool.git"
|
|
||||||
target_url: "https://${USERNAME}:${TOKEN}@gitea.fjy8018.top/BladeX/BladeX-Tool.git"
|
|
||||||
branch: "master"
|
|
||||||
|
|
||||||
# 示例:同步其他仓库(取消注释并修改)
|
|
||||||
# - name: "another-repo"
|
|
||||||
# source_url: "https://${USERNAME}:${TOKEN}@source.com/org/repo.git"
|
|
||||||
# target_url: "https://${USERNAME}:${TOKEN}@target.com/org/repo.git"
|
|
||||||
# branch: "main"
|
|
||||||
|
|
||||||
# 添加更多仓库同步配置...
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# B -> C 同步配置示例
|
|
||||||
# 这个配置使用环境变量前缀: SYNC_B_C
|
|
||||||
# 需要在 Gitea Secrets 中配置:
|
|
||||||
# - SYNC_B_C_USERNAME
|
|
||||||
# - SYNC_B_C_TOKEN
|
|
||||||
|
|
||||||
name: "B_to_C_Sync" # 同步任务名称,用于日志输出
|
|
||||||
|
|
||||||
repositories:
|
|
||||||
# 示例:从源B同步到目标C
|
|
||||||
- name: "bladex-tool"
|
|
||||||
source_url: "https://${USERNAME}:${TOKEN}@gitea.fjy8018.top/BladeX/BladeX-Tool.git"
|
|
||||||
target_url: "https://${USERNAME}:${TOKEN}@target.com/backup/BladeX-Tool.git"
|
|
||||||
branch: "master"
|
|
||||||
|
|
||||||
# 示例:同步其他仓库(取消注释并修改)
|
|
||||||
# - name: "another-repo"
|
|
||||||
# source_url: "https://${USERNAME}:${TOKEN}@source.com/org/repo.git"
|
|
||||||
# target_url: "https://${USERNAME}:${TOKEN}@target.com/org/repo.git"
|
|
||||||
# branch: "main"
|
|
||||||
|
|
||||||
# 添加更多仓库同步配置...
|
|
||||||
317
merge_repos.py
Normal file
317
merge_repos.py
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import yaml
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
import tempfile
|
||||||
|
import shutil
|
||||||
|
import time
|
||||||
|
|
||||||
|
def merge_repository(repo_config):
|
||||||
|
"""合并单个仓库(使用merge --no-ff)"""
|
||||||
|
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
|
||||||
|
|
||||||
|
# 替换环境变量
|
||||||
|
print(f"[DEBUG] ===== 变量替换开始 =====")
|
||||||
|
print(f"[DEBUG] 替换前的 source_url: {source_url}")
|
||||||
|
print(f"[DEBUG] 替换前的 target_url: {target_url}")
|
||||||
|
print(f"[DEBUG] 当前环境变量:")
|
||||||
|
|
||||||
|
# 检查环境变量是否已设置
|
||||||
|
env_vars = {}
|
||||||
|
for var in ['UPSTREAM_USERNAME', 'UPSTREAM_TOKEN', 'TARGET_USERNAME', 'TARGET_TOKEN']:
|
||||||
|
value = os.environ.get(var)
|
||||||
|
env_vars[var] = value
|
||||||
|
if value:
|
||||||
|
print(f"[DEBUG] {var}: 已设置 (长度: {len(value)})")
|
||||||
|
# 隐藏敏感信息(只显示前后几个字符)
|
||||||
|
if 'TOKEN' in var and len(value) > 8:
|
||||||
|
shown = value[:4] + '...' + value[-4:]
|
||||||
|
else:
|
||||||
|
shown = value
|
||||||
|
print(f"[DEBUG] {var} 值: {shown}")
|
||||||
|
else:
|
||||||
|
print(f"[DEBUG] {var}: ⚠️ 未设置或为空!会导致替换失败")
|
||||||
|
|
||||||
|
# 方法1:使用 os.path.expandvars()
|
||||||
|
source_url_expanded = os.path.expandvars(source_url)
|
||||||
|
target_url_expanded = os.path.expandvars(target_url)
|
||||||
|
|
||||||
|
# 方法2:手动替换(备用方案)
|
||||||
|
if '${' in source_url_expanded:
|
||||||
|
print(f"[DEBUG] os.path.expandvars() 未完全替换,尝试手动替换...")
|
||||||
|
for var, value in env_vars.items():
|
||||||
|
if value:
|
||||||
|
source_url_expanded = source_url_expanded.replace(f"${{{var}}}", value)
|
||||||
|
source_url_expanded = source_url_expanded.replace(f"${var}", value)
|
||||||
|
|
||||||
|
if '${' in target_url_expanded:
|
||||||
|
print(f"[DEBUG] os.path.expandvars() 未完全替换,尝试手动替换...")
|
||||||
|
for var, value in env_vars.items():
|
||||||
|
if value:
|
||||||
|
target_url_expanded = target_url_expanded.replace(f"${{{var}}}", value)
|
||||||
|
target_url_expanded = target_url_expanded.replace(f"${var}", value)
|
||||||
|
|
||||||
|
source_url = source_url_expanded
|
||||||
|
target_url = target_url_expanded
|
||||||
|
|
||||||
|
print(f"[DEBUG] 最终 source_url: {source_url}")
|
||||||
|
print(f"[DEBUG] 最终 target_url: {target_url}")
|
||||||
|
print(f"[DEBUG] ===== 变量替换结束 =====")
|
||||||
|
|
||||||
|
# 检查必要的环境变量是否设置
|
||||||
|
if '${' in source_url:
|
||||||
|
print(f"❌ [{name}] 错误: source_url 中的环境变量未完全解析: {source_url}")
|
||||||
|
print(f"❌ 这可能是因为:")
|
||||||
|
print(f" 1. 环境变量未传递给 Python 脚本")
|
||||||
|
print(f" 2. 变量名拼写错误")
|
||||||
|
print(f" 3. secrets 未在 Gitea 中正确配置")
|
||||||
|
return False
|
||||||
|
if '${' in target_url:
|
||||||
|
print(f"❌ [{name}] 错误: target_url 中的环境变量未完全解析: {target_url}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
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"merge_{name}_")
|
||||||
|
try:
|
||||||
|
# 克隆目标仓库
|
||||||
|
print(f"\n[1/7] 克隆目标仓库...")
|
||||||
|
result = subprocess.run(
|
||||||
|
['git', 'clone', target_url, work_dir],
|
||||||
|
capture_output=True, text=True, timeout=3600
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f"❌ 克隆目标仓库失败:")
|
||||||
|
print(result.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
os.chdir(work_dir)
|
||||||
|
|
||||||
|
# 添加上游远程
|
||||||
|
print(f"[2/7] 添加上游远程...")
|
||||||
|
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/7] 获取上游更改...")
|
||||||
|
max_retries = 3
|
||||||
|
for attempt in range(1, max_retries + 1):
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
['git', 'fetch', 'upstream'],
|
||||||
|
capture_output=True, text=True, timeout=3600
|
||||||
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
|
break
|
||||||
|
print(f"❌ 获取上游更改失败 (尝试 {attempt}/{max_retries}):")
|
||||||
|
print(result.stderr)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
print(f"❌ 获取上游更改超时 (尝试 {attempt}/{max_retries}),200秒限制")
|
||||||
|
|
||||||
|
if attempt < max_retries:
|
||||||
|
print(f"⏳ 等待5秒后重试...")
|
||||||
|
time.sleep(5)
|
||||||
|
else:
|
||||||
|
# 所有尝试都失败
|
||||||
|
print(f"\n网络诊断信息:")
|
||||||
|
print(f"- 错误类型: DNS解析或网络连接失败")
|
||||||
|
print(f"- 可能原因:")
|
||||||
|
print(f" 1. Gitea Actions Runner的DNS配置问题")
|
||||||
|
print(f" 2. Runner容器无法访问外部网络")
|
||||||
|
print(f" 3. 上游仓库服务器防火墙限制")
|
||||||
|
print(f"- 解决方案:")
|
||||||
|
print(f" 1. 在workflow中添加hosts配置")
|
||||||
|
print(f" 2. 检查Runner的网络设置")
|
||||||
|
print(f" 3. 使用IP地址替代域名")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 检查目标分支是否存在
|
||||||
|
print(f"[4/7] 检查分支...")
|
||||||
|
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/7] 更新本地分支...")
|
||||||
|
subprocess.run(['git', 'fetch', 'origin'], capture_output=True)
|
||||||
|
|
||||||
|
# 检查是否有未提交的更改
|
||||||
|
result = subprocess.run(['git', 'status', '--porcelain'], capture_output=True, text=True)
|
||||||
|
if result.stdout.strip():
|
||||||
|
print(f"❌ 本地仓库有未提交的更改,无法进行合并操作")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 合并操作:使用 merge --no-ff
|
||||||
|
print(f"[6/7] 合并上游分支 (使用 merge --no-ff)...")
|
||||||
|
result = subprocess.run(
|
||||||
|
['git', 'merge', '--no-ff', f'upstream/{branch}', '-m', f'Merge upstream/{branch} into {branch}'],
|
||||||
|
capture_output=True, text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f"❌ 合并失败,可能存在冲突:")
|
||||||
|
print(result.stderr)
|
||||||
|
|
||||||
|
# 检查是否是合并冲突
|
||||||
|
if "CONFLICT" in result.stderr or "Merge conflict" in result.stderr:
|
||||||
|
print(f"⚠️ 检测到合并冲突,跳过此仓库")
|
||||||
|
# 取消合并状态
|
||||||
|
subprocess.run(['git', 'merge', '--abort'], capture_output=True)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print(f"❌ 合并过程中发生其他错误")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 推送到目标
|
||||||
|
print(f"[7/7] 推送到目标仓库...")
|
||||||
|
result = subprocess.run(
|
||||||
|
['git', 'push', 'origin', f'refs/heads/{branch}'],
|
||||||
|
capture_output=True, text=True, timeout=3600
|
||||||
|
)
|
||||||
|
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("多仓库合并工具(使用merge --no-ff)")
|
||||||
|
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 = merge_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⚠️ 有仓库合并失败或被跳过,请查看详细日志")
|
||||||
|
# 注意:合并失败不应该导致整个流程失败,因为可能是预期的冲突
|
||||||
|
print("ℹ️ 合并失败通常是预期的冲突,不影响其他仓库的处理")
|
||||||
|
else:
|
||||||
|
print("\n✅ 所有仓库合并成功!")
|
||||||
|
|
||||||
|
print(f"\n结束时间: {time.strftime('%Y-%m-%d %H:%M:%S')}")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
16
repos.yaml
16
repos.yaml
@@ -1,16 +0,0 @@
|
|||||||
# 多仓库同步配置文件
|
|
||||||
# 定义需要同步的仓库对,源仓库 -> 目标仓库
|
|
||||||
repositories:
|
|
||||||
# 示例:同步 BladeX-Tool 仓库
|
|
||||||
- name: "bladex-tool" # 仓库名称(用于日志输出)
|
|
||||||
source_url: "https://${UPSTREAM_USERNAME}:${UPSTREAM_TOKEN}@center.javablade.com/blade/BladeX-Tool.git"
|
|
||||||
target_url: "https://${TARGET_USERNAME}:${TARGET_TOKEN}@gitea.fjy8018.top/BladeX/BladeX-Tool.git"
|
|
||||||
branch: "master" # 要同步的分支
|
|
||||||
|
|
||||||
# 示例:同步另一个仓库(取消注释并修改以下配置)
|
|
||||||
# - name: "another-repo"
|
|
||||||
# source_url: "https://${UPSTREAM_USERNAME}:${UPSTREAM_TOKEN}@github.com/example/repo.git"
|
|
||||||
# target_url: "https://${TARGET_USERNAME}:${TARGET_TOKEN}@gitea.fjy8018.top/home/repo.git"
|
|
||||||
# branch: "main"
|
|
||||||
|
|
||||||
# 添加更多仓库同步配置...
|
|
||||||
Binary file not shown.
@@ -1,293 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
多仓库同步工具 - 支持多方向同步
|
|
||||||
支持动态环境变量前缀,适用于 A->B, B->C 等多场景
|
|
||||||
"""
|
|
||||||
import yaml
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
|
||||||
import tempfile
|
|
||||||
import shutil
|
|
||||||
import time
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
def expand_env_vars(text, env_prefix):
|
|
||||||
"""使用指定的前缀扩展环境变量"""
|
|
||||||
if not text:
|
|
||||||
return text
|
|
||||||
|
|
||||||
# 构建完整的变量名
|
|
||||||
var_mappings = {
|
|
||||||
'USERNAME': f'{env_prefix}_USERNAME',
|
|
||||||
'TOKEN': f'{env_prefix}_TOKEN',
|
|
||||||
}
|
|
||||||
|
|
||||||
# 替换 ${VAR} 和 $VAR 格式
|
|
||||||
for placeholder, env_var in var_mappings.items():
|
|
||||||
value = os.environ.get(env_var, '')
|
|
||||||
if not value:
|
|
||||||
raise ValueError(f"环境变量 {env_var} 未设置")
|
|
||||||
|
|
||||||
# 替换 ${VAR}
|
|
||||||
text = text.replace(f'${{{placeholder}}}', value)
|
|
||||||
# 替换 $VAR
|
|
||||||
text = text.replace(f'${placeholder}', value)
|
|
||||||
|
|
||||||
# 如果还有未替换的变量,报错
|
|
||||||
if '${' in text:
|
|
||||||
raise ValueError(f"URL 中包含未解析的变量: {text}")
|
|
||||||
|
|
||||||
return text
|
|
||||||
|
|
||||||
def sync_repository(repo_config, env_prefix):
|
|
||||||
"""同步单个仓库"""
|
|
||||||
name = repo_config.get('name', 'unnamed-repo')
|
|
||||||
source_url_template = repo_config.get('source_url', '')
|
|
||||||
target_url_template = repo_config.get('target_url', '')
|
|
||||||
branch = repo_config.get('branch', 'master')
|
|
||||||
|
|
||||||
if not source_url_template or not target_url_template:
|
|
||||||
print(f"❌ [{name}] 错误: 缺少 source_url 或 target_url")
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 替换环境变量
|
|
||||||
source_url = expand_env_vars(source_url_template, env_prefix)
|
|
||||||
target_url = expand_env_vars(target_url_template, env_prefix)
|
|
||||||
except ValueError as e:
|
|
||||||
print(f"❌ [{name}] 错误: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 打印信息(隐藏敏感信息)
|
|
||||||
def hide_credentials(url):
|
|
||||||
if '@' in url:
|
|
||||||
parts = url.split('@')
|
|
||||||
return f"***:***@{parts[1]}"
|
|
||||||
return url
|
|
||||||
|
|
||||||
print(f"\n{'='*60}")
|
|
||||||
print(f"开始同步仓库: {name}")
|
|
||||||
print(f"源地址: {hide_credentials(source_url)}")
|
|
||||||
print(f"目标地址: {hide_credentials(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', 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] 获取上游更改...")
|
|
||||||
max_retries = 3
|
|
||||||
for attempt in range(1, max_retries + 1):
|
|
||||||
try:
|
|
||||||
result = subprocess.run(
|
|
||||||
['git', 'fetch', 'upstream'],
|
|
||||||
capture_output=True, text=True, timeout=200
|
|
||||||
)
|
|
||||||
if result.returncode == 0:
|
|
||||||
break
|
|
||||||
print(f"❌ 获取上游更改失败 (尝试 {attempt}/{max_retries}):")
|
|
||||||
print(result.stderr)
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
print(f"❌ 获取上游更改超时 (尝试 {attempt}/{max_retries}),200秒限制")
|
|
||||||
|
|
||||||
if attempt < max_retries:
|
|
||||||
print(f"⏳ 等待5秒后重试...")
|
|
||||||
time.sleep(5)
|
|
||||||
else:
|
|
||||||
print(f"\n网络诊断信息:")
|
|
||||||
print(f"- 错误类型: DNS解析或网络连接失败")
|
|
||||||
print(f"- 可能原因:")
|
|
||||||
print(f" 1. DNS配置问题")
|
|
||||||
print(f" 2. 无法访问外部网络")
|
|
||||||
print(f" 3. 上游仓库服务器限制")
|
|
||||||
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_file):
|
|
||||||
"""加载配置文件"""
|
|
||||||
if not os.path.exists(config_file):
|
|
||||||
print(f"错误: 配置文件 {config_file} 不存在!")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(config_file, '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 [], None
|
|
||||||
|
|
||||||
# 获取同步任务名称(可选)
|
|
||||||
sync_name = config.get('name', 'unnamed-sync')
|
|
||||||
|
|
||||||
# 过滤掉注释掉的或空配置
|
|
||||||
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"✓ 配置任务: {sync_name}")
|
|
||||||
print(f"✓ 找到 {len(valid_repos)} 个仓库配置")
|
|
||||||
return valid_repos, sync_name
|
|
||||||
|
|
||||||
except yaml.YAMLError as e:
|
|
||||||
print(f"错误: 解析YAML配置文件失败: {e}")
|
|
||||||
sys.exit(1)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"错误: 读取配置文件失败: {e}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(description='多仓库同步工具')
|
|
||||||
parser.add_argument('config_file', help='配置文件路径')
|
|
||||||
parser.add_argument('--env-prefix', required=True, help='环境变量前缀 (例如: SYNC_A_B)')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
print("="*80)
|
|
||||||
print("多仓库同步工具")
|
|
||||||
print("="*80)
|
|
||||||
print(f"\n开始时间: {time.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
||||||
print(f"配置文件: {args.config_file}")
|
|
||||||
print(f"环境前缀: {args.env_prefix}")
|
|
||||||
|
|
||||||
# 加载配置
|
|
||||||
repos, sync_name = load_config(args.config_file)
|
|
||||||
if not repos:
|
|
||||||
print("\n⚠️ 没有需要同步的仓库,退出")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
# 检查必要的环境变量
|
|
||||||
required_vars = [
|
|
||||||
f'{args.env_prefix}_USERNAME',
|
|
||||||
f'{args.env_prefix}_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(f"请设置以下环境变量:")
|
|
||||||
print(f" - {args.env_prefix}_USERNAME")
|
|
||||||
print(f" - {args.env_prefix}_TOKEN")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
print(f"✓ 所有必要环境变量已设置")
|
|
||||||
|
|
||||||
# 同步所有仓库
|
|
||||||
results = []
|
|
||||||
for i, repo in enumerate(repos, 1):
|
|
||||||
print(f"\n[{i}/{len(repos)}] 正在同步...")
|
|
||||||
success = sync_repository(repo, args.env_prefix)
|
|
||||||
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()
|
|
||||||
@@ -88,7 +88,7 @@ def sync_repository(repo_config):
|
|||||||
print(f"\n[1/6] 克隆目标仓库...")
|
print(f"\n[1/6] 克隆目标仓库...")
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
['git', 'clone', target_url, work_dir],
|
['git', 'clone', target_url, work_dir],
|
||||||
capture_output=True, text=True, timeout=300
|
capture_output=True, text=True, timeout=3600
|
||||||
)
|
)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
print(f"❌ 克隆目标仓库失败:")
|
print(f"❌ 克隆目标仓库失败:")
|
||||||
@@ -112,7 +112,7 @@ def sync_repository(repo_config):
|
|||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
['git', 'fetch', 'upstream'],
|
['git', 'fetch', 'upstream'],
|
||||||
capture_output=True, text=True, timeout=200
|
capture_output=True, text=True, timeout=3600
|
||||||
)
|
)
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
break
|
break
|
||||||
@@ -166,7 +166,7 @@ def sync_repository(repo_config):
|
|||||||
print(f"[6/6] 推送到目标仓库...")
|
print(f"[6/6] 推送到目标仓库...")
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
['git', 'push', '--force', '--tags', 'origin', f'refs/heads/{branch}'],
|
['git', 'push', '--force', '--tags', 'origin', f'refs/heads/{branch}'],
|
||||||
capture_output=True, text=True, timeout=600
|
capture_output=True, text=True, timeout=3600
|
||||||
)
|
)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
print(f"❌ 推送到目标仓库失败:")
|
print(f"❌ 推送到目标仓库失败:")
|
||||||
|
|||||||
Reference in New Issue
Block a user