From f9ed5bc10cfcb2c797234f0e085096dff9d191fa Mon Sep 17 00:00:00 2001 From: tukuaiai Date: Tue, 16 Dec 2025 21:16:52 +0800 Subject: [PATCH] build: Untrack backups directory Remove the backups directory from git tracking. The directory is now ignored via .gitignore. --- backups/README.md | 53 --------- backups/一键备份.sh | 83 -------------- backups/快速备份.py | 265 -------------------------------------------- 3 files changed, 401 deletions(-) delete mode 100644 backups/README.md delete mode 100644 backups/一键备份.sh delete mode 100644 backups/快速备份.py diff --git a/backups/README.md b/backups/README.md deleted file mode 100644 index fc03cca..0000000 --- a/backups/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# 快速备份工具 - -基于 `.gitignore` 规则的项目备份工具,自动排除不需要的文件。 - -## 功能特性 - -- 自动读取 `.gitignore` 规则 -- 支持取反规则(`!` 语法) -- 目录级剪枝优化 -- 生成 `.tar.gz` 压缩包 -- 零依赖(仅使用 Python 内置模块) - -## 文件结构 - -``` -backups/ -├── 快速备份.py # 核心备份引擎 -├── 一键备份.sh # Shell 启动脚本 -└── README.md # 本文档 -``` - -## 使用方法 - -```bash -# 方式一:Shell 脚本(推荐) -bash backups/一键备份.sh - -# 方式二:直接运行 Python -python3 backups/快速备份.py - -# 指定输出文件 -python3 backups/快速备份.py -o my_backup.tar.gz - -# 指定项目目录 -python3 backups/快速备份.py -p /path/to/project -``` - -## 输出位置 - -默认输出到 `backups/gz/备份_YYYYMMDD_HHMMSS.tar.gz` - -## 参数说明 - -| 参数 | 说明 | 默认值 | -|------|------|--------| -| `-p, --project` | 项目根目录 | 当前目录 | -| `-o, --output` | 输出文件路径 | `backups/gz/备份_时间戳.tar.gz` | -| `-g, --gitignore` | gitignore 文件路径 | `.gitignore` | - -## 依赖 - -- Python 3.x(无需额外包) -- Bash(用于 Shell 脚本) diff --git a/backups/一键备份.sh b/backups/一键备份.sh deleted file mode 100644 index d20f14d..0000000 --- a/backups/一键备份.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/bin/bash - -# 一键备份项目脚本 -# 自动读取 .gitignore 规则并排除匹配的文件 -# bash backups/一键备份.sh - -set -e - -# 颜色输出 -GREEN='\033[0;32m' -BLUE='\033[0;34m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -# 脚本所在目录 -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# 项目根目录(脚本所在目录的父目录) -PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" - -# 项目backups目录 -BACKUPS_DIR="${PROJECT_ROOT}/backups" - -# 备份脚本路径(始终在项目的backups目录中) -BACKUP_SCRIPT="${BACKUPS_DIR}/快速备份.py" - -# 检查备份脚本是否存在 -if [ ! -f "${BACKUP_SCRIPT}" ]; then - echo -e "${YELLOW}⚠️ 错误: 备份脚本不存在${NC}" - echo "" - echo "备份工具应位于项目的 backups/ 目录中:" - echo " ${BACKUPS_DIR}/" - echo "" - echo "请确保:" - echo " 1. 复制快速备份.py到 ${BACKUPS_DIR}/" - echo " 2. 复制一键备份.sh到 ${BACKUPS_DIR}/" - echo "" - echo "或者使用方式:" - echo " • 在项目根目录执行: bash backups/一键备份.sh" - echo " • 或直接执行: python3 backups/快速备份.py" - exit 1 -fi - -echo -e "${BLUE}========================================${NC}" -echo -e "${BLUE} 项目快速备份工具${NC}" -echo -e "${BLUE}========================================${NC}" -echo "" -echo -e "${GREEN}✓${NC} 找到备份脚本: backups/快速备份.py" - -# 检查 Python3 是否可用 -if ! command -v python3 &> /dev/null; then - echo -e "${YELLOW}⚠️ 错误: 未找到 python3 命令${NC}" - exit 1 -fi - -echo -e "${GREEN}✓${NC} 项目目录: ${PROJECT_ROOT}" -echo -e "${GREEN}✓${NC} 备份脚本: ${BACKUP_SCRIPT}" -echo -e "${GREEN}✓${NC} Python 版本: $(python3 --version)" -echo "" - -# 执行备份 -echo -e "${YELLOW}▶ 正在执行备份...${NC}" -echo "" - -# 切换到项目根目录 -cd "${PROJECT_ROOT}" - -# 运行备份脚本 -python3 "${BACKUP_SCRIPT}" - -# 检查执行结果 -if [ $? -eq 0 ]; then - echo "" - echo -e "${GREEN}========================================${NC}" - echo -e "${GREEN} ✓ 备份完成!${NC}" - echo -e "${GREEN}========================================${NC}" -else - echo "" - echo -e "${YELLOW}========================================${NC}" - echo -e "${YELLOW} ✗ 备份失败${NC}" - echo -e "${YELLOW}========================================${NC}" - exit 1 -fi diff --git a/backups/快速备份.py b/backups/快速备份.py deleted file mode 100644 index 3e03fd4..0000000 --- a/backups/快速备份.py +++ /dev/null @@ -1,265 +0,0 @@ -#!/usr/bin/env python3 -""" -快速备份项目工具 -读取 .gitignore 规则并打包项目文件(排除匹配的文件) - -bash backups/一键备份.sh - -文件位置: - backups/快速备份.py - -工具清单(backups/目录): - • 快速备份.py - 核心备份引擎(7.3 KB) - • 一键备份.sh - 一键执行脚本(2.4 KB) - -使用方法: - $ bash backups/一键备份.sh - 或 - $ python3 backups/快速备份.py - -备份输出: - backups/gz/备份_YYYYMMDD_HHMMSS.tar.gz - -适用项目: - 任何包含 .gitignore 文件的项目(自动读取规则并排除匹配文件) - -依赖: - 无需额外安装包,仅使用Python内置模块 -""" - -import os -import tarfile -import fnmatch -from pathlib import Path -from datetime import datetime -import argparse -import sys - - -class GitignoreFilter: - """解析 .gitignore 文件并过滤文件""" - - def __init__(self, gitignore_path: Path, project_root: Path): - self.project_root = project_root - # 规则按照出现顺序存储,支持取反(!)语义,后匹配覆盖前匹配 - # 每项: {"pattern": str, "dir_only": bool, "negate": bool, "has_slash": bool} - self.rules = [] - self.load_gitignore(gitignore_path) - - def load_gitignore(self, gitignore_path: Path): - """加载并解析 .gitignore 文件""" - if not gitignore_path.exists(): - print(f"⚠️ 警告: {gitignore_path} 不存在,将不应用任何过滤规则") - return - - try: - with open(gitignore_path, 'r', encoding='utf-8') as f: - for line in f: - line = line.strip() - - # 跳过空行和注释 - if not line or line.startswith('#'): - continue - - negate = line.startswith('!') - if negate: - line = line[1:].lstrip() - if not line: - continue - - dir_only = line.endswith('/') - has_slash = '/' in line.rstrip('/') - - self.rules.append({ - "pattern": line, - "dir_only": dir_only, - "negate": negate, - "has_slash": has_slash, - }) - - print(f"✓ 已加载 {len(self.rules)} 条规则(含取反)") - - except Exception as e: - print(f"❌ 读取 .gitignore 失败: {e}") - sys.exit(1) - - def _match_rule(self, rule: dict, relative_path_str: str, is_dir: bool) -> bool: - """按规则匹配路径,返回是否命中""" - pattern = rule["pattern"] - dir_only = rule["dir_only"] - has_slash = rule["has_slash"] - - # 目录规则:匹配目录自身或其子路径 - if dir_only: - normalized = pattern.rstrip('/') - if relative_path_str == normalized or relative_path_str.startswith(normalized + '/'): - return True - return False - - # 带路径分隔的规则:按相对路径匹配 - if has_slash: - return fnmatch.fnmatch(relative_path_str, pattern) - - # 无斜杠:匹配任意层级的基本名 - if fnmatch.fnmatch(Path(relative_path_str).name, pattern): - return True - # 额外处理目录命中:无通配符时,若任一父级目录名等于 pattern 也视为命中 - if pattern.isalpha() and pattern in relative_path_str.split('/'): - return True - return False - - def should_exclude(self, path: Path, is_dir: bool = False) -> bool: - """ - 判断路径是否应该被排除(支持 ! 取反,后匹配覆盖前匹配) - 返回 True 表示应该排除(不备份) - """ - try: - # 统一使用 POSIX 路径风格进行匹配 - relative_path_str = path.relative_to(self.project_root).as_posix() - except ValueError: - return False # 不在项目根目录内,不处理 - - # Git 风格:从上到下最后一次匹配决定去留 - matched = None - for rule in self.rules: - if self._match_rule(rule, relative_path_str, is_dir): - matched = not rule["negate"] # negate 表示显式允许 - - return bool(matched) - - -def create_backup(project_root: Path, output_file: Path, filter_obj: GitignoreFilter): - """创建备份压缩包""" - - # 统计信息 - total_files = 0 - excluded_files = 0 - included_files = 0 - - print(f"\n{'='*60}") - print(f"开始备份项目: {project_root}") - print(f"输出文件: {output_file}") - print(f"{'='*60}\n") - - try: - with tarfile.open(output_file, 'w:gz') as tar: - # 使用 os.walk 可在目录层级提前剪枝,避免进入已忽略目录 - for root, dirs, files in os.walk(project_root, topdown=True): - root_path = Path(root) - - # 目录剪枝:命中忽略规则或 .git 时不再深入 - pruned_dirs = [] - for d in dirs: - dir_path = root_path / d - if d == '.git' or filter_obj.should_exclude(dir_path, is_dir=True): - print(f" 排除目录: {dir_path.relative_to(project_root)}") - excluded_files += 1 - continue - pruned_dirs.append(d) - dirs[:] = pruned_dirs - - for name in files: - path = root_path / name - total_files += 1 - - # 文件忽略判定 - if '.git' in path.parts or filter_obj.should_exclude(path): - excluded_files += 1 - print(f" 排除: {path.relative_to(project_root)}") - continue - - arcname = path.relative_to(project_root) - tar.add(path, arcname=arcname) - included_files += 1 - print(f" 备份: {arcname}") - - print(f"\n{'='*60}") - print("备份完成!") - print(f"{'='*60}") - print(f"总文件数: {total_files}") - print(f"已备份: {included_files} 个文件") - print(f"已排除: {excluded_files} 个文件/目录") - print(f"压缩包大小: {output_file.stat().st_size / 1024 / 1024:.2f} MB") - print(f"{'='*60}") - - return True - - except Exception as e: - print(f"\n❌ 备份失败: {e}") - import traceback - traceback.print_exc() - return False - - -def main(): - parser = argparse.ArgumentParser( - description='快速备份项目(根据 .gitignore 排除文件)', - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" -使用示例: - # 基本用法(备份到 backups/gz/ 目录) - python backups/快速备份.py - - # 指定输出文件 - python backups/快速备份.py -o my_backup.tar.gz - - # 指定项目根目录 - python backups/快速备份.py -p /path/to/project - """ - ) - - parser.add_argument( - '-p', '--project', - type=str, - default='.', - help='项目根目录路径(默认: 当前目录)' - ) - - parser.add_argument( - '-o', '--output', - type=str, - help='输出文件路径(默认: backups/备份_YYYYMMDD_HHMMSS.tar.gz)' - ) - - parser.add_argument( - '-g', '--gitignore', - type=str, - default='.gitignore', - help='.gitignore 文件路径(默认: .gitignore)' - ) - - args = parser.parse_args() - - # 解析路径 - project_root = Path(args.project).resolve() - gitignore_path = Path(args.gitignore).resolve() - - if not project_root.exists(): - print(f"❌ 错误: 项目目录不存在: {project_root}") - sys.exit(1) - - # 确定输出文件路径 - if args.output: - output_file = Path(args.output).resolve() - else: - # 默认输出到 backups/gz/ 目录 - backup_dir = project_root / 'backups' / 'gz' - backup_dir.mkdir(parents=True, exist_ok=True) - - timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') - output_file = backup_dir / f'备份_{timestamp}.tar.gz' - - # 确保输出目录存在 - output_file.parent.mkdir(parents=True, exist_ok=True) - - # 创建过滤器 - filter_obj = GitignoreFilter(gitignore_path, project_root) - - # 执行备份 - success = create_backup(project_root, output_file, filter_obj) - - sys.exit(0 if success else 1) - - -if __name__ == '__main__': - main()