git.ts
1 import { memoize } from 'lodash-es' 2 import { execFileNoThrow } from './execFileNoThrow.js' 3 4 export const getIsGit = memoize(async (): Promise<boolean> => { 5 const { code } = await execFileNoThrow('git', [ 6 'rev-parse', 7 '--is-inside-work-tree', 8 ]) 9 return code === 0 10 }) 11 12 export const getHead = async (): Promise<string> => { 13 const { stdout } = await execFileNoThrow('git', ['rev-parse', 'HEAD']) 14 return stdout.trim() 15 } 16 17 export const getBranch = async (): Promise<string> => { 18 const { stdout } = await execFileNoThrow( 19 'git', 20 ['rev-parse', '--abbrev-ref', 'HEAD'], 21 undefined, 22 undefined, 23 false, 24 ) 25 return stdout.trim() 26 } 27 28 export const getRemoteUrl = async (): Promise<string | null> => { 29 // This might fail if there is no remote called origin 30 const { stdout, code } = await execFileNoThrow( 31 'git', 32 ['remote', 'get-url', 'origin'], 33 undefined, 34 undefined, 35 false, 36 ) 37 return code === 0 ? stdout.trim() : null 38 } 39 40 export const getIsHeadOnRemote = async (): Promise<boolean> => { 41 const { code } = await execFileNoThrow( 42 'git', 43 ['rev-parse', '@{u}'], 44 undefined, 45 undefined, 46 false, 47 ) 48 return code === 0 49 } 50 51 export const getIsClean = async (): Promise<boolean> => { 52 const { stdout } = await execFileNoThrow( 53 'git', 54 ['status', '--porcelain'], 55 undefined, 56 undefined, 57 false, 58 ) 59 return stdout.trim().length === 0 60 } 61 62 export interface GitRepoState { 63 commitHash: string 64 branchName: string 65 remoteUrl: string | null 66 isHeadOnRemote: boolean 67 isClean: boolean 68 } 69 70 export async function getGitState(): Promise<GitRepoState | null> { 71 try { 72 const [commitHash, branchName, remoteUrl, isHeadOnRemote, isClean] = 73 await Promise.all([ 74 getHead(), 75 getBranch(), 76 getRemoteUrl(), 77 getIsHeadOnRemote(), 78 getIsClean(), 79 ]) 80 81 return { 82 commitHash, 83 branchName, 84 remoteUrl, 85 isHeadOnRemote, 86 isClean, 87 } 88 } catch (_) { 89 // Fail silently - git state is best effort 90 return null 91 } 92 }