(cwd: string, filePath: string)
| 122 | } |
| 123 | |
| 124 | export async function getGitFileDiff(cwd: string, filePath: string): Promise<GitFileDiffResponse> { |
| 125 | const repositoryRoot = await findRepositoryRoot(cwd); |
| 126 | if (!repositoryRoot || !isWithinPath(repositoryRoot, filePath)) return { supported: false }; |
| 127 | |
| 128 | const resolvedFilePath = path.resolve(filePath); |
| 129 | let stat: fs.Stats; |
| 130 | try { |
| 131 | stat = fs.lstatSync(resolvedFilePath); |
| 132 | } catch { |
| 133 | return { supported: false }; |
| 134 | } |
| 135 | if (!stat.isFile() || stat.size > TEXT_PREVIEW_MAX_BYTES) return { supported: false }; |
| 136 | |
| 137 | const relativePath = toGitPath(path.relative(repositoryRoot, resolvedFilePath)); |
| 138 | const entries = await readStatusEntries(repositoryRoot); |
| 139 | const entry = entries.find((candidate) => candidate.path === relativePath); |
| 140 | if (!entry) return { supported: false }; |
| 141 | |
| 142 | const { status } = classifyGitStatus(entry); |
| 143 | if (status === "deleted") return { supported: false }; |
| 144 | |
| 145 | const currentBuffer = fs.readFileSync(resolvedFilePath); |
| 146 | if (hasNullByte(currentBuffer)) return { supported: false }; |
| 147 | const newContent = currentBuffer.toString("utf8"); |
| 148 | |
| 149 | let patch: string; |
| 150 | if (status === "untracked") { |
| 151 | patch = createAddedFilePatch(relativePath, newContent); |
| 152 | } else { |
| 153 | const trackedPatch = await createTrackedFilePatch(repositoryRoot, relativePath, entry.originalPath); |
| 154 | if (trackedPatch === null) { |
| 155 | if (status !== "added") return { supported: false }; |
| 156 | patch = createAddedFilePatch(relativePath, newContent); |
| 157 | } else { |
| 158 | patch = trackedPatch; |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | if (!patch.includes("\n@@ ")) return { supported: false }; |
| 163 | return { supported: true, status, patch }; |
| 164 | } |
no test coverage detected