Skip to main content

def refile_with_link_update(old_id, new_id, old_path, new_path, workspace):

  1. def refile_with_link_update(old_id, new_id, old_path, new_path, workspace):
        # 0. 前置检查:工作区必须干净
        if not is_working_tree_clean():
            raise RefileError(
                "Refile requires a clean working tree.\n"
                "Please commit or stash your changes first:\n"
                "  syn commit -m 'WIP'  或  git stash"
            )
        
        try:
            # 1. 移动源文件(暂存)
            git_mv(old_path, new_path)
            
            # 1b. 更新源文件的 Frontmatter(jd_number 字段)
            source_content = read_file(new_path)  # 文件已移动,从新路径读取
            source_fm, source_body = parse_frontmatter(source_content)
            source_fm['jd_number'] = new_id  # 更新 ID
            source_fm['modified'] = datetime.now().isoformat()  # 更新修改时间
            write_file(new_path, serialize_frontmatter(source_fm) + source_body)
            git_add(new_path)  # ⚠️ 必须暂存源文件的内容修改(git_mv 只暂存重命名)
            
            # 2. 查找并更新所有引用(其他文件)
            # ⚠️ 顺序重要:先 YAML 精确处理,再全文替换,避免竞态
            modified_files = set()
            files_with_related = set()  # 记录有 related 字段的文件
            
            # 2a. 先处理 Frontmatter 中的 related 字段(YAML 精确解析)
            # 必须先于全文替换,否则 old_id 已被替换,条件判断会失败
            related_refs = db_query(
                "SELECT file_path FROM resources WHERE related LIKE ?",
                (f'%"{old_id}"%',)  # 在 JSON 数组字符串中搜索
            )
            for (file_path,) in related_refs:
                content = read_file(file_path)
                frontmatter, body = parse_frontmatter(content)
                # 安全检查:确保 related 是非空列表
                related = frontmatter.get('related')
                if isinstance(related, list) and old_id in related:
                    # 精确更新 YAML 数组
                    frontmatter['related'] = [
                        new_id if rid == old_id else rid 
                        for rid in related
                    ]
                    # 只替换 wiki-style 链接(避免误改非引用上下文)
                    body = body.replace(f"[[{old_id}]]", f"[[{new_id}]]")
                    content = serialize_frontmatter(frontmatter) + body
                    write_file(file_path, content)
                    modified_files.add(file_path)
                    files_with_related.add(file_path)
            
            # 2b. 搜索 wiki-style 链接:[[old_id]]
            # ⚠️ 只更新明确的引用语法,不盲目替换正文中的 ID 字符串
            wiki_refs = rg_search(f"\\[\\[{old_id}\\]\\]", workspace)
            for file_path in wiki_refs:
                if file_path in files_with_related:
                    continue  # 已被 2a 处理,跳过
                content = read_file(file_path)
                content = content.replace(f"[[{old_id}]]", f"[[{new_id}]]")
                write_file(file_path, content)
                modified_files.add(file_path)
            
            # 3. 暂存所有修改
            for f in modified_files:
                git_add(f)
            
            # 4. 提交事务(+1 是源文件本身)
            git_commit(f"refactor: refile {old_id}{new_id}, update {len(modified_files) + 1} files")
            
        except Exception as e:
            # 4. 安全回滚(因为工作区干净,此时可以安全 reset)
            git_reset_hard()
            logger.error(f"Refile failed, rolled back: {e}")
            raise RefileError("操作失败,已回滚所有修改")
    
    def is_working_tree_clean() -> bool:
        """检查工作区是否干净"""
        result = subprocess.run(
            ["git", "status", "--porcelain"],
            capture_output=True, text=True
        )
        return len(result.stdout.strip()) == 0
    


    策略 B:智能回滚(复杂场景)

    如果必须允许脏工作区 Refile,回滚时需精确还原:

    
    def smart_rollback(modified_files: list, old_path: str, new_path: str):
        """精确回滚,不影响其他未提交修改"""
        # 1. Unstage 暂存的文件
        for f in modified_files:
            subprocess.run(["git", "reset", "HEAD", f])
        
        # 2. 还原被修改的文件内容
        for f in modified_files:
            subprocess.run(["git", "checkout", "--", f])
        
        # 3. 移回源文件
        if os.path.exists(new_path):
            subprocess.run(["git", "mv", new_path, old_path])