|
| 1 | +/** |
| 2 | + * Pure parsers for git command output (used by server and tests). |
| 3 | + */ |
| 4 | + |
| 5 | +function parseStatusPorcelain(output) { |
| 6 | + const lines = output ? output.split("\n").filter(Boolean) : []; |
| 7 | + const files = []; |
| 8 | + let stagedCount = 0; |
| 9 | + let unstagedCount = 0; |
| 10 | + let untrackedCount = 0; |
| 11 | + let conflictCount = 0; |
| 12 | + for (const line of lines) { |
| 13 | + const xy = line.slice(0, 2); |
| 14 | + const x = xy[0]; |
| 15 | + const y = xy[1]; |
| 16 | + const filePath = line.slice(3).trim().replace(/^["']|["']$/g, ""); |
| 17 | + if (filePath) { |
| 18 | + let status = "modified"; |
| 19 | + if (xy === "??") status = "untracked"; |
| 20 | + else if (x === "A" || x === "M" || x === "D" || x === "R" || x === "C") status = "added"; |
| 21 | + else if (y === "M" || y === "D") status = "modified"; |
| 22 | + else if (x === "D" || y === "D") status = "deleted"; |
| 23 | + else if (x === "U" || y === "U") status = "unmerged"; |
| 24 | + files.push({ path: filePath, status }); |
| 25 | + } |
| 26 | + if (xy === "??") { |
| 27 | + untrackedCount += 1; |
| 28 | + } else { |
| 29 | + if (x !== " " && x !== "?") stagedCount += 1; |
| 30 | + if (y !== " " && y !== "?") unstagedCount += 1; |
| 31 | + if (x === "U" || y === "U") conflictCount += 1; |
| 32 | + } |
| 33 | + } |
| 34 | + return { files, summary: { stagedCount, unstagedCount, untrackedCount, conflictCount } }; |
| 35 | +} |
| 36 | + |
| 37 | +/** Parse "git status -sb" first line for branch, upstream, ahead, behind */ |
| 38 | +function parseStatusBranchLine(line) { |
| 39 | + if (!line || !line.startsWith("## ")) { |
| 40 | + return { branch: "unknown", hasUpstream: false, ahead: 0, behind: 0, upstreamRef: null }; |
| 41 | + } |
| 42 | + const rest = line.slice(3).trim(); |
| 43 | + const branchMatch = rest.match(/^([^\s.]+)(?:\.\.\.(\S+))?(?:\s+\[(.*)\])?/); |
| 44 | + const branch = branchMatch ? branchMatch[1] : "unknown"; |
| 45 | + const upstreamRef = branchMatch && branchMatch[2] ? branchMatch[2] : null; |
| 46 | + const bracket = branchMatch && branchMatch[3] ? branchMatch[3] : ""; |
| 47 | + let ahead = 0; |
| 48 | + let behind = 0; |
| 49 | + const aheadM = bracket.match(/ahead\s+(\d+)/); |
| 50 | + const behindM = bracket.match(/behind\s+(\d+)/); |
| 51 | + if (aheadM) ahead = parseInt(aheadM[1], 10) || 0; |
| 52 | + if (behindM) behind = parseInt(behindM[1], 10) || 0; |
| 53 | + return { branch, hasUpstream: !!upstreamRef, ahead, behind, upstreamRef }; |
| 54 | +} |
| 55 | + |
| 56 | +module.exports = { |
| 57 | + parseStatusPorcelain, |
| 58 | + parseStatusBranchLine, |
| 59 | +}; |
0 commit comments