@@ -243,52 +243,65 @@ export async function revertFile(cwd: string, gitPath: string, filePath: string)
243243
244244export type FileStatus = "M" | "A" | "R" | "U" | "" ;
245245
246- export async function getFileStatuses ( cwd : string , gitPath : string ) : Promise < Map < string , FileStatus > > {
246+ function parsePorcelainV1Z ( stdout : string ) : Map < string , FileStatus > {
247247 const statusMap = new Map < string , FileStatus > ( ) ;
248+ if ( ! stdout ) return statusMap ;
248249
249- try {
250- const stdout = await runGit ( { cwd, gitPath, args : [ "status" , "--porcelain=v1" , "-z" ] } ) ;
251- if ( ! stdout ) return statusMap ;
252-
253- const parts = stdout . split ( "\0" ) . filter ( Boolean ) ;
250+ const parts = stdout . split ( "\0" ) . filter ( Boolean ) ;
254251
255- for ( let i = 0 ; i < parts . length ; i ++ ) {
256- const entry = parts [ i ] ;
257- const xy = entry . slice ( 0 , 2 ) ;
258- let filePath = entry . slice ( 3 ) ;
252+ for ( let i = 0 ; i < parts . length ; i ++ ) {
253+ const entry = parts [ i ] ;
254+ const xy = entry . slice ( 0 , 2 ) ;
255+ let filePath = entry . slice ( 3 ) ;
259256
260- // Rename/copy entries include an extra NUL-separated "new path"
261- if ( xy . startsWith ( "R" ) || xy . startsWith ( "C" ) ) {
262- const newPath = parts [ ++ i ] ;
263- if ( newPath ) filePath = newPath ;
264- }
257+ if ( xy . includes ( "R" ) || xy . includes ( "C" ) ) {
258+ const newPath = parts [ ++ i ] ;
259+ if ( newPath ) filePath = newPath ;
260+ }
265261
266- // Determine status: X is staged, Y is unstaged
267- let status : FileStatus = "" ;
268-
269- if ( xy . includes ( "U" ) ) {
270- status = "U" ; // Unmerged / conflict (highest priority)
271- } else if ( xy === "??" ) {
272- status = "A" ; // Untracked = new file
273- } else if ( xy . includes ( "A" ) ) {
274- status = "A" ; // Added
275- } else if ( xy . includes ( "M" ) ) {
276- status = "M" ; // Modified
277- } else if ( xy . includes ( "R" ) || xy . includes ( "C" ) ) {
278- status = "R" ; // Renamed / copied
279- }
262+ let status : FileStatus = "" ;
263+ if ( xy . includes ( "U" ) ) {
264+ status = "U" ;
265+ } else if ( xy === "??" ) {
266+ status = "A" ;
267+ } else if ( xy . includes ( "A" ) ) {
268+ status = "A" ;
269+ } else if ( xy . includes ( "M" ) ) {
270+ status = "M" ;
271+ } else if ( xy . includes ( "R" ) || xy . includes ( "C" ) ) {
272+ status = "R" ;
273+ }
280274
281- if ( filePath && status ) {
282- statusMap . set ( filePath , status ) ;
283- }
275+ if ( filePath && status ) {
276+ statusMap . set ( filePath , status ) ;
284277 }
285- } catch {
286- // Not a git repo or git error
287278 }
288279
289280 return statusMap ;
290281}
291282
283+ export async function getFileStatuses ( cwd : string , gitPath : string ) : Promise < Map < string , FileStatus > > {
284+ try {
285+ const stdout = await runGit ( { cwd, gitPath, args : [ "status" , "--porcelain=v1" , "-z" ] } ) ;
286+ return parsePorcelainV1Z ( stdout ) ;
287+ } catch {
288+ return new Map < string , FileStatus > ( ) ;
289+ }
290+ }
291+
292+ export async function getTrackedFiles ( cwd : string , gitPath : string ) : Promise < Set < string > > {
293+ const tracked = new Set < string > ( ) ;
294+ try {
295+ const stdout = await runGit ( { cwd, gitPath, args : [ "ls-files" , "-z" ] } ) ;
296+ if ( stdout ) {
297+ stdout . split ( "\0" ) . filter ( Boolean ) . forEach ( ( p ) => tracked . add ( p ) ) ;
298+ }
299+ } catch {
300+ // Ignore
301+ }
302+ return tracked ;
303+ }
304+
292305// Get remote default branch (main/master)
293306export async function getRemoteDefaultBranch ( cwd : string , gitPath : string ) : Promise < string | null > {
294307 try {
0 commit comments