@@ -116,30 +116,30 @@ type Rebase struct {
116116}
117117
118118type Git struct {
119- configErr error
120- config * ini.File
119+ commonCfgErr error
121120 Working * GitStatus
122121 Staging * GitStatus
123122 commit * Commit
124123 Rebase * Rebase
125124 User * User
126- ShortHash string
125+ commonCfg * ini. File
127126 Hash string
128- BranchStatus string
129127 HEAD string
130128 UpstreamIcon string
131129 UpstreamURL string
132130 Ref string
133131 RawUpstreamURL string
134132 mainWorktree string
133+ BranchStatus string
134+ ShortHash string
135135 Scm
136- stashCount int
137136 Ahead int
138- PushAhead int
139137 PushBehind int
140138 Behind int
141139 worktreeCount int
142- configOnce sync.Once
140+ PushAhead int
141+ stashCount int
142+ commonCfgOnce sync.Once
143143 mainWorktreeOnce sync.Once
144144 IsWorkTree bool
145145 Merge bool
@@ -397,15 +397,16 @@ func (g *Git) setUser() {
397397func (g * Git ) isBareRepo (gitDir * runtime.FileInfo ) bool {
398398 defer log .Trace (time .Now ())
399399
400- if gitDir .IsDir {
401- g .mainSCMDir = gitDir .Path
402- } else {
400+ bareDir := gitDir .Path
401+ if ! gitDir .IsDir {
403402 content := g .fileContent (gitDir .ParentFolder , ".git" )
404403 dir := strings .TrimPrefix (content , "gitdir: " )
405- g . mainSCMDir = resolveGitPath (gitDir .ParentFolder , g .convertToLinuxPath (dir ))
404+ bareDir = resolveGitPath (gitDir .ParentFolder , g .convertToLinuxPath (dir ))
406405 }
407406
408- cfg , err := g .getGitConfig ()
407+ g .mainSCMDir = bareDir
408+
409+ cfg , err := loadGitConfig (g .env , bareDir )
409410 if err != nil {
410411 log .Error (err )
411412 return false
@@ -484,8 +485,8 @@ func (g *Git) hasWorktree(gitdir *runtime.FileInfo) bool {
484485 g .repoRootDir = g .convertToLinuxPath (g .repoRootDir )
485486 // resolve relative paths (worktree.useRelativePaths = true)
486487 g .repoRootDir = resolveGitPath (g .scmDir , g .repoRootDir )
487- g .scmDir = moduleDir [:worktreeIndex ]
488488 g .mainSCMDir = g .scmDir
489+ g .scmDir = moduleDir [:worktreeIndex ]
489490 g .IsWorkTree = true
490491 return true
491492 }
@@ -512,13 +513,9 @@ func (g *Git) hasWorktree(gitdir *runtime.FileInfo) bool {
512513 }
513514 }
514515
515- // check for separate git folder(--separate-git-dir)
516- // check if the folder contains a HEAD file
517516 if g .env .HasFilesInDir (g .mainSCMDir , "HEAD" ) {
518- gitFolder : = strings .TrimSuffix (g .scmDir , ".git" )
517+ g . repoRootDir = strings .TrimSuffix (g .scmDir , ".git" )
519518 g .scmDir = g .mainSCMDir
520- g .mainSCMDir = gitFolder
521- g .repoRootDir = gitFolder
522519 return true
523520 }
524521
@@ -561,7 +558,7 @@ func (g *Git) setPushStatus() {
561558 return
562559 }
563560
564- pushRemote := g .getPushRemote ()
561+ pushRemote := g .pushRef ()
565562 if pushRemote == "" {
566563 return
567564 }
@@ -583,64 +580,68 @@ func (g *Git) setPushStatus() {
583580 wg .Wait ()
584581}
585582
586- func (g * Git ) getPushRemote () string {
587- upstream := g .Upstream
588- if idx := strings .Index (upstream , "/" ); idx != - 1 {
589- upstream = upstream [:idx ]
583+ // pushRef resolves the destination of a push once, so both counts below describe the
584+ // same comparison. An empty rev-list result is a genuine failure, never a retry signal.
585+ func (g * Git ) pushRef () string {
586+ if ref := g .getGitCommandOutput ("rev-parse" , "--abbrev-ref" , "@{push}" ); ref != "" {
587+ return ref
590588 }
591589
592- if upstream == "" {
593- upstream = origin
594- }
590+ return g .getPushRemote ()
591+ }
595592
593+ func (g * Git ) getPushRemote () string {
596594 branch := g .Ref
597595 if branch == "" {
598596 return ""
599597 }
600598
601- cfg , err := g .getGitConfig ()
602- if err != nil {
603- pushRemote := g .getGitCommandOutput ("config" , "--get" , "remote.pushDefault" )
604- if pushRemote == "" {
605- pushRemote = upstream
606- }
607-
608- return strings .TrimSpace (pushRemote ) + "/" + branch
599+ pushRemote := g .getGitCommandOutput ("config" , "--get" , fmt .Sprintf ("branch.%s.pushRemote" , branch ))
600+ if pushRemote == "" {
601+ pushRemote = g .getGitCommandOutput ("config" , "--get" , "remote.pushDefault" )
609602 }
610603
611- sectionName := fmt .Sprintf (`branch "%s"` , branch )
612- section := cfg .Section (sectionName )
613- pushRemote := section .Key ("pushRemote" ).String ()
614604 if pushRemote == "" {
615- pushRemote = cfg . Section ( "remote" ). Key ( "pushDefault" ). String ( )
605+ pushRemote = regex . ReplaceAllString ( "/.*" , g . Upstream , "" )
616606 }
617607
618608 if pushRemote == "" {
619- pushRemote = upstream
609+ pushRemote = origin
620610 }
621611
622- return pushRemote + "/" + branch
612+ return strings . TrimSpace ( pushRemote ) + "/" + branch
623613}
624614
625- func (g * Git ) getGitConfig () (* ini.File , error ) {
626- g .configOnce .Do (func () {
627- configData := g .fileContent (g .mainSCMDir , "config" )
628- if configData == "" {
629- log .Debug ("git config file not found" )
630- g .configErr = fmt .Errorf ("git config file not found" )
631- return
632- }
615+ func loadGitConfigFile (env runtime.Environment , dir , file string ) (* ini.File , error ) {
616+ if dir == "" {
617+ return nil , fmt .Errorf ("no git directory to read %s from" , file )
618+ }
633619
634- cfg , err := ini .Load (configData )
635- if err != nil {
636- g .configErr = err
637- return
638- }
620+ configData := strings .Trim (env .FileContent (dir + "/" + file ), " \r \n " )
621+ if configData == "" {
622+ return nil , fmt .Errorf ("%s not found" , file )
623+ }
639624
640- g .config = cfg
625+ return ini .Load (configData )
626+ }
627+
628+ func loadGitConfig (env runtime.Environment , dir string ) (* ini.File , error ) {
629+ return loadGitConfigFile (env , dir , "config" )
630+ }
631+
632+ // commonConfig reads the repository's shared config. It refuses to memoize a failure
633+ // against an unknown directory, which a cache-restored segment would otherwise poison.
634+ func (g * Git ) commonConfig () (* ini.File , error ) {
635+ commonDir := g .commonGitDir ()
636+ if commonDir == "" {
637+ return nil , fmt .Errorf ("common git directory is unknown" )
638+ }
639+
640+ g .commonCfgOnce .Do (func () {
641+ g .commonCfg , g .commonCfgErr = loadGitConfig (g .env , commonDir )
641642 })
642643
643- return g .config , g .configErr
644+ return g .commonCfg , g .commonCfgErr
644645}
645646
646647func (g * Git ) cleanUpstreamURL (url string ) string {
@@ -1111,15 +1112,19 @@ func (g *Git) WorktreeCount() int {
11111112 return g .worktreeCount
11121113 }
11131114
1114- worktreesFolder := filepath .Join (g .mainSCMDir , "worktrees" )
1115+ commonDir := g .commonGitDir ()
1116+ if commonDir == "" {
1117+ return 0
1118+ }
1119+
1120+ worktreesFolder := filepath .Join (commonDir , "worktrees" )
11151121
11161122 if ! g .env .HasFolder (worktreesFolder ) {
11171123 return 0
11181124 }
11191125
1120- worktreeFolders := g .env .LsDir (worktreesFolder )
11211126 var count int
1122- for _ , folder := range worktreeFolders {
1127+ for _ , folder := range g . env . LsDir ( worktreesFolder ) {
11231128 if folder .IsDir () {
11241129 count ++
11251130 }
@@ -1189,12 +1194,18 @@ func (g *Git) ensureMainWorktreeContext() bool {
11891194}
11901195
11911196func (g * Git ) commonGitDir () string {
1197+ // scmDir is the common git directory at every discovery exit. The worktrees cut
1198+ // below is only for partially initialized state, where scmDir is not yet set.
1199+ if g .scmDir != "" {
1200+ return filepath .ToSlash (g .scmDir )
1201+ }
1202+
11921203 mainSCMDir := filepath .ToSlash (g .mainSCMDir )
11931204 if worktreeIndex := strings .LastIndex (mainSCMDir , "/worktrees/" ); worktreeIndex > - 1 {
11941205 return mainSCMDir [:worktreeIndex ]
11951206 }
11961207
1197- return filepath . ToSlash ( g . scmDir )
1208+ return ""
11981209}
11991210
12001211// isModuleAdminDir reports whether target is a submodule administrative directory
@@ -1220,9 +1231,12 @@ func (g *Git) isModuleAdminDir(target, parent string) bool {
12201231 return true
12211232 }
12221233
1223- cfg , err := ini .Load (g .fileContent (target , "config" ))
1234+ // A missing or unreadable config is simply not a submodule git dir, not an error
1235+ // worth reporting: every --separate-git-dir target spelled with a modules component
1236+ // lands here.
1237+ cfg , err := loadGitConfig (g .env , target )
12241238 if err != nil {
1225- log .Error ( err )
1239+ log .Debug ( "no readable config in" , target , "- not a submodule git dir" )
12261240 return false
12271241 }
12281242
@@ -1282,24 +1296,23 @@ func (g *Git) getRemoteURL() string {
12821296 upstream = origin
12831297 }
12841298
1285- cfg , err := g . getGitConfig ()
1286- if err != nil {
1287- return g . getGitCommandOutput ( "remote" , "get- url" , upstream )
1299+ // Ask git first because it applies insteadOf rewriting and reads the merged configuration.
1300+ if url := g . getGitCommandOutput ( "remote" , "get-url" , upstream ); url != "" {
1301+ return url
12881302 }
12891303
1290- url := cfg .Section ("remote \" " + upstream + "\" " ).Key ("url" ).String ()
1291- if len (url ) != 0 {
1292- log .Debug ("remote url found in config:" , url )
1293- return url
1304+ cfg , err := g .commonConfig ()
1305+ if err != nil {
1306+ return ""
12941307 }
12951308
1296- return g . getGitCommandOutput ("remote" , "get-url" , upstream )
1309+ return cfg . Section ("remote \" " + upstream + " \" " ). Key ( "url" ). String ( )
12971310}
12981311
12991312func (g * Git ) Remotes () map [string ]string {
13001313 var remotes = make (map [string ]string )
13011314
1302- cfg , err := g .getGitConfig ()
1315+ cfg , err := g .commonConfig ()
13031316 if err != nil {
13041317 return remotes
13051318 }
@@ -1347,10 +1360,50 @@ func (g *Git) repoName() string {
13471360 return path .Base (g .convertToLinuxPath (g .repoRootDir ))
13481361 }
13491362
1350- ind := strings .LastIndex (g .mainSCMDir , ".git/worktrees" )
1351- if ind > - 1 {
1352- return path .Base (g .mainSCMDir [:ind ])
1363+ commonDir := g .commonGitDir ()
1364+ if commonDir == "" {
1365+ return ""
1366+ }
1367+
1368+ if parent := filepath .Dir (commonDir ); g .gitEntryResolvesTo (parent , commonDir ) {
1369+ return path .Base (g .convertToLinuxPath (parent ))
1370+ }
1371+
1372+ for _ , file := range []string {"config.worktree" , "config" } {
1373+ cfg , err := loadGitConfigFile (g .env , commonDir , file )
1374+ if err != nil {
1375+ continue
1376+ }
1377+
1378+ worktree := cfg .Section ("core" ).Key ("worktree" ).String ()
1379+ if worktree == "" {
1380+ continue
1381+ }
1382+
1383+ return path .Base (g .convertToLinuxPath (resolveGitPath (commonDir , worktree )))
13531384 }
13541385
13551386 return ""
13561387}
1388+
1389+ func (g * Git ) gitEntryResolvesTo (parent , commonDir string ) bool {
1390+ gitEntry := parent + "/.git"
1391+ commonDir = filepath .ToSlash (filepath .Clean (commonDir ))
1392+
1393+ if g .env .HasFolder (gitEntry ) {
1394+ return filepath .ToSlash (filepath .Clean (gitEntry )) == commonDir
1395+ }
1396+
1397+ if ! g .env .HasFilesInDir (parent , ".git" ) {
1398+ return false
1399+ }
1400+
1401+ content := strings .Trim (g .env .FileContent (gitEntry ), " \r \n " )
1402+ target , found := strings .CutPrefix (content , "gitdir: " )
1403+ if ! found {
1404+ return false
1405+ }
1406+
1407+ target = g .convertToLinuxPath (target )
1408+ return filepath .ToSlash (filepath .Clean (resolveGitPath (parent , target ))) == commonDir
1409+ }
0 commit comments