"""Obsidian KB - Git 同步""" import os import subprocess from pathlib import Path from typing import Optional, List from datetime import datetime from .config import get_config class GitSync: """Git仓库同步管理""" def __init__(self, repo_path: Optional[str] = None): config = get_config() self.repo_path = repo_path or config.obsidian_path self.git_url = config.git_repo self.git_token = config.git_token def _run_git(self, args: List[str], cwd: Optional[str] = None) -> tuple[int, str, str]: """运行git命令""" cmd = ['git'] + args try: result = subprocess.run( cmd, cwd=cwd or self.repo_path, capture_output=True, text=True, encoding='utf-8' ) return result.returncode, result.stdout, result.stderr except Exception as e: return -1, "", str(e) def is_git_repo(self) -> bool: """检查是否为git仓库""" git_dir = os.path.join(self.repo_path, '.git') return os.path.isdir(git_dir) def init_repo(self) -> bool: """初始化git仓库""" if self.is_git_repo(): return True os.makedirs(self.repo_path, exist_ok=True) # git init code, _, _ = self._run_git(['init']) if code != 0: return False # 配置git用户(使用默认值) self._run_git(['config', 'user.email', 'obsidian-kb@localhost']) self._run_git(['config', 'user.name', 'Obsidian KB']) # 添加remote if self.git_url: # 如果提供了token,构建带认证的URL if self.git_token: auth_url = self._build_auth_url(self.git_url, self.git_token) self._run_git(['remote', 'add', 'origin', auth_url]) else: self._run_git(['remote', 'add', 'origin', self.git_url]) return True def _build_auth_url(self, url: str, token: str) -> str: """构建带认证的git URL""" if url.startswith('https://github.com/'): return url.replace('https://', f'https://x-access-token:{token}@') elif url.startswith('https://'): return url.replace('https://', f'https://oauth2:{token}@') return url def add_files(self, files: List[str]) -> bool: """添加文件到暂存区""" if not files: return True # 添加所有指定文件 for file_path in files: # 转换为相对路径 rel_path = os.path.relpath(file_path, self.repo_path) code, _, _ = self._run_git(['add', rel_path]) if code != 0: return False return True def add_all(self) -> bool: """添加所有变更""" code, _, _ = self._run_git(['add', '.']) return code == 0 def has_changes(self) -> bool: """检查是否有未提交的变更""" code, stdout, _ = self._run_git(['status', '--porcelain']) if code != 0: return False return bool(stdout.strip()) def commit(self, message: Optional[str] = None, content_title: Optional[str] = None) -> bool: """提交变更""" if not self.has_changes(): return True if not message: message = self._generate_commit_message(content_title) code, _, _ = self._run_git(['commit', '-m', message]) return code == 0 def _generate_commit_message(self, content_title: Optional[str] = None) -> str: """生成提交信息""" timestamp = datetime.now().strftime('%Y-%m-%d %H:%M') if content_title: return f"Add: {content_title[:50]}" + ("..." if len(content_title) > 50 else "") + f" [{timestamp}]" return f"Update KB notes [{timestamp}]" def push(self, branch: str = 'main') -> bool: """推送到远程""" # 检查是否有origin code, stdout, _ = self._run_git(['remote']) if code != 0 or 'origin' not in stdout: return False # 检查分支是否存在,不存在则创建 code, stdout, _ = self._run_git(['branch', '--show-current']) current_branch = stdout.strip() if not current_branch: # 创建初始分支 self._run_git(['checkout', '-b', branch]) # 推送 code, _, _ = self._run_git(['push', '-u', 'origin', branch]) return code == 0 def pull(self, branch: str = 'main') -> bool: """从远程拉取""" code, _, _ = self._run_git(['pull', 'origin', branch]) return code == 0 def sync(self, files: Optional[List[str]] = None, content_title: Optional[str] = None) -> tuple[bool, str]: """完整同步流程""" # 初始化仓库 if not self.init_repo(): return False, "Failed to initialize git repository" # 添加文件 if files: if not self.add_files(files): return False, "Failed to add files" else: if not self.add_all(): return False, "Failed to add files" # 提交 if not self.commit(content_title=content_title): if not self.has_changes(): return True, "No changes to commit" return False, "Failed to commit" # 推送 if self.git_url and not self.push(): return False, "Failed to push" return True, "Sync completed successfully" def get_status(self) -> dict: """获取仓库状态""" status = { 'is_repo': self.is_git_repo(), 'has_changes': False, 'branch': '', 'remote': '' } if not status['is_repo']: return status status['has_changes'] = self.has_changes() _, stdout, _ = self._run_git(['branch', '--show-current']) status['branch'] = stdout.strip() _, stdout, _ = self._run_git(['remote', 'get-url', 'origin']) url = stdout.strip() # 隐藏token if '@' in url: url = 'https://' + url.split('@', 1)[1] status['remote'] = url return status