#!/usr/bin/env python3 # git_auto_commit.py - 自动提交指定目录到 Gitea(支持子目录映射) # 用法: python git_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 # 配置 REPO_URL = "https://gittea.dev/popiskill/skills.git" BRANCH = "master" 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 / "git-config.env" if not config_file.exists(): print(f"错误: 配置文件不存在: {config_file}") print("请创建配置文件并设置 GIT_USERNAME 和 GIT_TOKEN") 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_PASSWORD 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)") sys.exit(1) return config['GIT_USERNAME'], token def build_auth_url(repo_url, username, token): """构建带凭证的 URL""" parsed = urlparse(repo_url) return f"{parsed.scheme}://{username}:{token}@{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='git_auto_commit_') print(f"工作目录: {work_dir}") print(f"源目录: {source_dir}") try: # 构建带凭证的 URL auth_url = build_auth_url(REPO_URL, username, token) # 尝试克隆现有仓库 try: run_git_command(['clone', auth_url, '.'], cwd=work_dir) print("已克隆现有仓库") except subprocess.CalledProcessError: print("初始化新仓库...") run_git_command(['init'], cwd=work_dir) run_git_command(['remote', 'add', 'origin', auth_url], cwd=work_dir) # 根据映射复制文件 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', 'auto@commit.local'], 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) 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()