Files
skills/scripts/github_auto_commit.py
T

247 lines
8.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# github_auto_commit.py - 自动提交指定目录到 GitHub(支持子目录映射)
# 用法: python github_auto_commit.py [目录路径]
import os
import sys
import shutil
import subprocess
import tempfile
from datetime import datetime
from pathlib import Path
from urllib.parse import urlparse, quote
# 配置
REPO_URL = "https://github.com/popiskill/popiskill.git"
BRANCH = "main"
DEFAULT_SOURCE_DIR = "C:/ai/skills" # 仓库本地提交目录
# 本地子目录 -> 仓库目录映射
DIR_MAPPING = {
"内容创作": "content_creation",
"图像制作": "image_generation",
"视频制作": "video_production",
"音频创作": "audio_creation",
"AI剪辑": "AI_video_trim",
"社媒运营": "social_media",
"电商工具": "E-commerce_tools",
"漫剧制作": "comic_drama",
}
# 排除的文件/目录
EXCLUDE_PATTERNS = ['.git', '.openclaw']
def load_config():
"""从配置文件读取凭证"""
script_dir = Path(__file__).parent
config_file = script_dir / "github-config.env"
if not config_file.exists():
print(f"错误: 配置文件不存在: {config_file}")
print("请创建配置文件并设置 GIT_USERNAME 和 GIT_PASSWORD")
sys.exit(1)
config = {}
with open(config_file, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
config[key.strip()] = value.strip().strip('"\'')
# 支持 GIT_TOKEN 或 GIT_PASSWORDGitHub 推荐使用 Token
token = config.get('GIT_TOKEN') or config.get('GIT_PASSWORD')
if not config.get('GIT_USERNAME') or not token:
print("错误: 请在配置文件中设置 GIT_USERNAME 和 GIT_TOKEN (或 GIT_PASSWORD)")
print("注意: GitHub 已不支持密码直接登录,请使用 Personal Access Token")
sys.exit(1)
return config['GIT_USERNAME'], token
def build_auth_url(repo_url, username, password):
"""构建带凭证的 URL"""
parsed = urlparse(repo_url)
# URL 编码用户名和密码(处理 @ 等特殊字符)
encoded_username = quote(username, safe='')
encoded_password = quote(password, safe='')
return f"{parsed.scheme}://{encoded_username}:{encoded_password}@{parsed.netloc}{parsed.path}"
def copy_directory(src, dst, exclude=None):
"""复制目录,排除指定文件"""
if exclude is None:
exclude = []
src_path = Path(src)
dst_path = Path(dst)
for item in src_path.rglob('*'):
# 检查是否在排除列表中
rel_path = item.relative_to(src_path)
if any(part in exclude for part in rel_path.parts):
continue
if item.is_file():
target = dst_path / rel_path
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(item, target)
def copy_with_mapping(source_dir, work_dir):
"""根据映射复制文件,并删除仓库中本地不存在的文件"""
source_path = Path(source_dir)
work_path = Path(work_dir)
copied_dirs = []
# 遍历源目录下的一级子目录
for local_name in DIR_MAPPING.keys():
local_dir = source_path / local_name
if not local_dir.exists() or not local_dir.is_dir():
continue
# 获取映射的仓库目录名
repo_name = DIR_MAPPING[local_name]
repo_dir = work_path / repo_name
print(f" 映射: {local_name} -> {repo_name}")
# 先记录仓库目录现有的文件
existing_files = set()
if repo_dir.exists():
for item in repo_dir.rglob('*'):
if item.is_file():
existing_files.add(item.relative_to(repo_dir))
# 复制该目录下的所有内容
copy_directory(local_dir, repo_dir, exclude=EXCLUDE_PATTERNS)
copied_dirs.append(repo_name)
# 删除仓库中本地没有的文件
current_files = set()
for item in local_dir.rglob('*'):
if item.is_file():
rel_path = item.relative_to(local_dir)
# 跳过排除的文件
if any(part in EXCLUDE_PATTERNS for part in rel_path.parts):
continue
current_files.add(rel_path)
files_to_delete = existing_files - current_files
for rel_path in files_to_delete:
file_to_delete = repo_dir / rel_path
if file_to_delete.exists():
print(f" 删除: {rel_path}")
file_to_delete.unlink()
# 清理空目录
try:
file_to_delete.parent.rmdir()
except OSError:
pass # 目录不为空,保留
# 复制根目录下的文件(不映射,直接放在根目录)
for item in source_path.iterdir():
if item.is_file():
target = work_path / item.name
shutil.copy2(item, target)
print(f" 复制根文件: {item.name}")
return copied_dirs
def run_git_command(args, cwd=None, check=True):
"""运行 git 命令"""
result = subprocess.run(
['git'] + args,
cwd=cwd,
capture_output=True,
text=True,
encoding='utf-8'
)
if check and result.returncode != 0:
print(f"Git 错误: {result.stderr}")
raise subprocess.CalledProcessError(result.returncode, ['git'] + args)
return result
def main():
# 获取源目录
if len(sys.argv) < 2:
source_dir = DEFAULT_SOURCE_DIR
print(f"未指定目录,使用默认: {source_dir}")
else:
source_dir = sys.argv[1]
source_path = Path(source_dir)
if not source_path.exists():
print(f"错误: 目录不存在: {source_dir}")
sys.exit(1)
# 加载配置
username, token = load_config()
# 创建临时工作目录
work_dir = tempfile.mkdtemp(prefix='github_auto_commit_')
print(f"工作目录: {work_dir}")
print(f"源目录: {source_dir}")
try:
# 构建带凭证的 URL
auth_url = build_auth_url(REPO_URL, username, token)
# 尝试克隆现有仓库
clone_success = False
is_empty_repo = False
try:
run_git_command(['clone', auth_url, '.'], cwd=work_dir)
print("已克隆现有仓库")
clone_success = True
# 检查是否是空仓库(没有提交)
result = run_git_command(['rev-parse', '--verify', 'HEAD'], cwd=work_dir, check=False)
if result.returncode != 0:
is_empty_repo = True
print("仓库为空,将创建初始提交")
except subprocess.CalledProcessError:
print("初始化新仓库...")
run_git_command(['init'], cwd=work_dir)
run_git_command(['remote', 'add', 'origin', auth_url], cwd=work_dir)
is_empty_repo = True
# 根据映射复制文件
print("复制文件(带目录映射)...")
copied_dirs = copy_with_mapping(source_dir, work_dir)
if not copied_dirs:
print("警告: 没有找到映射的目录")
else:
print(f"已复制 {len(copied_dirs)} 个映射目录")
# 配置 git
run_git_command(['config', 'user.email', '[email protected]'], cwd=work_dir)
run_git_command(['config', 'user.name', 'Auto Commit'], cwd=work_dir)
# 添加所有更改
run_git_command(['add', '-A'], cwd=work_dir)
# 检查是否有更改要提交
status_result = run_git_command(['diff', '--cached', '--quiet'], cwd=work_dir, check=False)
if status_result.returncode == 0:
print("没有更改需要提交")
return
# 提交
commit_msg = f"Auto commit: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
run_git_command(['commit', '-m', commit_msg], cwd=work_dir)
# 推送(如果是空仓库,首次推送需要 -u 参数)
if is_empty_repo:
print(f"首次推送到 {BRANCH} 分支...")
run_git_command(['push', '-u', 'origin', BRANCH], cwd=work_dir)
else:
run_git_command(['push', 'origin', BRANCH], cwd=work_dir)
print(f"[OK] 成功提交到 {REPO_URL}")
finally:
# 清理临时目录
shutil.rmtree(work_dir, ignore_errors=True)
if __name__ == '__main__':
main()