This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
openclaw-skills/skills/obsidian-kb/src/git_sync.py
T
OpenClaw Agent f26d4d8f7f 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
2026-02-06 18:15:30 +08:00

196 lines
6.4 KiB
Python

"""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