@@ -230,6 +230,29 @@ function parseRemoteRefWithRemoteNames(
230230 return null ;
231231}
232232
233+ function parseStashBranchName ( subject : string ) : string | null {
234+ const trimmed = subject . trim ( ) ;
235+ if ( trimmed . length === 0 ) {
236+ return null ;
237+ }
238+
239+ const match = / ^ (?: W I P o n | O n ) \s + ( .+ ?) : \s / . exec ( trimmed ) ;
240+ const branchName = match ?. [ 1 ] ?. trim ( ) ?? "" ;
241+ return branchName . length > 0 ? branchName : null ;
242+ }
243+
244+ function parseStashCounts ( stdout : string ) : ReadonlyMap < string , number > {
245+ const counts = new Map < string , number > ( ) ;
246+ for ( const line of stdout . split ( "\n" ) ) {
247+ const branchName = parseStashBranchName ( line ) ;
248+ if ( ! branchName ) {
249+ continue ;
250+ }
251+ counts . set ( branchName , ( counts . get ( branchName ) ?? 0 ) + 1 ) ;
252+ }
253+ return counts ;
254+ }
255+
233256function parseUpstreamRef (
234257 value : string ,
235258) : { upstreamRef : string ; remoteName : string ; upstreamBranch : string } | null {
@@ -1662,36 +1685,57 @@ export const makeGitCore = (options?: { executeOverride?: GitCoreShape["execute"
16621685 ) ,
16631686 ) ;
16641687
1665- const [ defaultRef , worktreeList , remoteBranchResult , remoteNamesResult , branchLastCommit ] =
1666- yield * Effect . all (
1667- [
1668- executeGit (
1669- "GitCore.listBranches.defaultRef" ,
1670- input . cwd ,
1671- [ "symbolic-ref" , "refs/remotes/origin/HEAD" ] ,
1672- {
1673- timeoutMs : 5_000 ,
1674- allowNonZeroExit : true ,
1675- } ,
1676- ) ,
1677- executeGit (
1678- "GitCore.listBranches.worktreeList" ,
1679- input . cwd ,
1680- [ "worktree" , "list" , "--porcelain" ] ,
1681- {
1682- timeoutMs : 5_000 ,
1683- allowNonZeroExit : true ,
1684- } ,
1685- ) ,
1686- remoteBranchResultEffect ,
1687- remoteNamesResultEffect ,
1688- branchRecencyPromise ,
1689- ] ,
1690- { concurrency : "unbounded" } ,
1691- ) ;
1688+ const stashListResultEffect = executeGit ( "GitCore.listBranches.stashList" , input . cwd , [
1689+ "stash" ,
1690+ "list" ,
1691+ "--format=%gs" ,
1692+ ] ) . pipe (
1693+ Effect . catch ( ( error ) =>
1694+ Effect . logWarning (
1695+ `GitCore.listBranches: stash lookup failed for ${ input . cwd } : ${ error . message } . Falling back to an empty stash list.` ,
1696+ ) . pipe ( Effect . as ( { code : 1 , stdout : "" , stderr : "" } ) ) ,
1697+ ) ,
1698+ ) ;
1699+
1700+ const [
1701+ defaultRef ,
1702+ worktreeList ,
1703+ remoteBranchResult ,
1704+ remoteNamesResult ,
1705+ stashListResult ,
1706+ branchLastCommit ,
1707+ ] = yield * Effect . all (
1708+ [
1709+ executeGit (
1710+ "GitCore.listBranches.defaultRef" ,
1711+ input . cwd ,
1712+ [ "symbolic-ref" , "refs/remotes/origin/HEAD" ] ,
1713+ {
1714+ timeoutMs : 5_000 ,
1715+ allowNonZeroExit : true ,
1716+ } ,
1717+ ) ,
1718+ executeGit (
1719+ "GitCore.listBranches.worktreeList" ,
1720+ input . cwd ,
1721+ [ "worktree" , "list" , "--porcelain" ] ,
1722+ {
1723+ timeoutMs : 5_000 ,
1724+ allowNonZeroExit : true ,
1725+ } ,
1726+ ) ,
1727+ remoteBranchResultEffect ,
1728+ remoteNamesResultEffect ,
1729+ stashListResultEffect ,
1730+ branchRecencyPromise ,
1731+ ] ,
1732+ { concurrency : "unbounded" } ,
1733+ ) ;
16921734
16931735 const remoteNames =
16941736 remoteNamesResult . code === 0 ? parseRemoteNames ( remoteNamesResult . stdout ) : [ ] ;
1737+ const stashCounts =
1738+ stashListResult . code === 0 ? parseStashCounts ( stashListResult . stdout ) : new Map ( ) ;
16951739 if ( remoteBranchResult . code !== 0 && remoteBranchResult . stderr . trim ( ) . length > 0 ) {
16961740 yield * Effect . logWarning (
16971741 `GitCore.listBranches: remote branch lookup returned code ${ remoteBranchResult . code } for ${ input . cwd } : ${ remoteBranchResult . stderr . trim ( ) } . Falling back to an empty remote branch list.` ,
@@ -1702,6 +1746,11 @@ export const makeGitCore = (options?: { executeOverride?: GitCoreShape["execute"
17021746 `GitCore.listBranches: remote name lookup returned code ${ remoteNamesResult . code } for ${ input . cwd } : ${ remoteNamesResult . stderr . trim ( ) } . Falling back to an empty remote name list.` ,
17031747 ) ;
17041748 }
1749+ if ( stashListResult . code !== 0 && stashListResult . stderr . trim ( ) . length > 0 ) {
1750+ yield * Effect . logWarning (
1751+ `GitCore.listBranches: stash lookup returned code ${ stashListResult . code } for ${ input . cwd } : ${ stashListResult . stderr . trim ( ) } . Falling back to an empty stash list.` ,
1752+ ) ;
1753+ }
17051754
17061755 const defaultBranch =
17071756 defaultRef . code === 0
@@ -1731,13 +1780,27 @@ export const makeGitCore = (options?: { executeOverride?: GitCoreShape["execute"
17311780 . split ( "\n" )
17321781 . map ( parseBranchLine )
17331782 . filter ( ( branch ) : branch is { name : string ; current : boolean } => branch !== null )
1734- . map ( ( branch ) => ( {
1735- name : branch . name ,
1736- current : branch . current ,
1737- isRemote : false ,
1738- isDefault : branch . name === defaultBranch ,
1739- worktreePath : worktreeMap . get ( branch . name ) ?? null ,
1740- } ) )
1783+ . map ( ( branch ) => {
1784+ const localBranch : {
1785+ name : string ;
1786+ current : boolean ;
1787+ isRemote : boolean ;
1788+ isDefault : boolean ;
1789+ worktreePath : string | null ;
1790+ stashCount ?: number ;
1791+ } = {
1792+ name : branch . name ,
1793+ current : branch . current ,
1794+ isRemote : false ,
1795+ isDefault : branch . name === defaultBranch ,
1796+ worktreePath : worktreeMap . get ( branch . name ) ?? null ,
1797+ } ;
1798+ const stashCount = stashCounts . get ( branch . name ) ;
1799+ if ( stashCount !== undefined ) {
1800+ localBranch . stashCount = stashCount ;
1801+ }
1802+ return localBranch ;
1803+ } )
17411804 . toSorted ( ( a , b ) => {
17421805 const aPriority = a . current ? 0 : a . isDefault ? 1 : 2 ;
17431806 const bPriority = b . current ? 0 : b . isDefault ? 1 : 2 ;
0 commit comments