feat: implement obsidian-kb skill with long video chunking
- Multi-source content aggregation (Bilibili/YouTube/Zhihu) - Smart inbox management with AI quality scoring - Long video chunking analysis (30min+ auto-segmentation) - Topic-based organization - Git auto-sync to Obsidian - Template system with Jinja2 and fallback rendering - CLI commands: subscribe, inbox, process, skip, status, search - Comprehensive test suite with unit and integration tests - Documentation: README.md and SKILL.md Checkpoint 7-8 completed by Agent E
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user