@@ -57,34 +57,81 @@ public static string GetWorktreePipeSuffix(string directory)
5757 }
5858
5959 /// <summary>
60- /// Detects if the given directory is a git worktree. If so, returns
61- /// a WorktreeInfo with the worktree name, git dir path, and shared
62- /// git dir path. Returns null if not a worktree.
60+ /// Detects if the given directory (or any ancestor) is a git worktree.
61+ /// Walks up from <paramref name="directory"/> looking for a <c>.git</c>
62+ /// file (not directory) containing a <c>gitdir:</c> pointer. Returns
63+ /// null if not inside a worktree.
6364 /// </summary>
6465 public static WorktreeInfo TryGetWorktreeInfo ( string directory )
6566 {
66- string dotGitPath = Path . Combine ( directory , ".git" ) ;
67+ return TryGetWorktreeInfo ( directory , out _ ) ;
68+ }
6769
68- if ( ! File . Exists ( dotGitPath ) || Directory . Exists ( dotGitPath ) )
70+ /// <summary>
71+ /// Detects if the given directory (or any ancestor) is a git worktree.
72+ /// Walks up from <paramref name="directory"/> looking for a <c>.git</c>
73+ /// file (not directory) containing a <c>gitdir:</c> pointer. Returns
74+ /// null if not inside a worktree, with an error message if an I/O
75+ /// error prevented detection.
76+ /// </summary>
77+ public static WorktreeInfo TryGetWorktreeInfo ( string directory , out string error )
78+ {
79+ error = null ;
80+
81+ if ( string . IsNullOrEmpty ( directory ) )
6982 {
7083 return null ;
7184 }
7285
86+ // Canonicalize to an absolute path so walk-up and Path.Combine
87+ // behave consistently regardless of the caller's CWD.
88+ string current = Path . GetFullPath ( directory ) ;
89+ while ( current != null )
90+ {
91+ string dotGitPath = Path . Combine ( current , ".git" ) ;
92+
93+ if ( Directory . Exists ( dotGitPath ) )
94+ {
95+ // Found a real .git directory — this is a primary worktree, not a linked worktree
96+ return null ;
97+ }
98+
99+ if ( File . Exists ( dotGitPath ) )
100+ {
101+ return TryParseWorktreeGitFile ( current , dotGitPath , out error ) ;
102+ }
103+
104+ string parent = Path . GetDirectoryName ( current ) ;
105+ if ( parent == current )
106+ {
107+ break ;
108+ }
109+
110+ current = parent ;
111+ }
112+
113+ return null ;
114+ }
115+
116+ private static WorktreeInfo TryParseWorktreeGitFile ( string worktreeRoot , string dotGitPath , out string error )
117+ {
118+ error = null ;
119+
73120 try
74121 {
75122 string gitdirLine = File . ReadAllText ( dotGitPath ) . Trim ( ) ;
76- if ( ! gitdirLine . StartsWith ( "gitdir: " ) )
123+ if ( ! gitdirLine . StartsWith ( GVFSConstants . DotGit . GitDirPrefix ) )
77124 {
78125 return null ;
79126 }
80127
81- string gitdirPath = gitdirLine . Substring ( "gitdir: " . Length ) . Trim ( ) ;
128+ string gitdirPath = gitdirLine . Substring ( GVFSConstants . DotGit . GitDirPrefix . Length ) . Trim ( ) ;
82129 gitdirPath = gitdirPath . Replace ( '/' , Path . DirectorySeparatorChar ) ;
83130
84131 // Resolve relative paths against the worktree directory
85132 if ( ! Path . IsPathRooted ( gitdirPath ) )
86133 {
87- gitdirPath = Path . GetFullPath ( Path . Combine ( directory , gitdirPath ) ) ;
134+ gitdirPath = Path . GetFullPath ( Path . Combine ( worktreeRoot , gitdirPath ) ) ;
88135 }
89136
90137 string worktreeName = Path . GetFileName ( gitdirPath ) ;
@@ -93,31 +140,34 @@ public static WorktreeInfo TryGetWorktreeInfo(string directory)
93140 return null ;
94141 }
95142
96- // Read commondir to find the shared .git/ directory
97- // commondir file contains a relative path like "../../.."
98- string commondirFile = Path . Combine ( gitdirPath , "commondir" ) ;
99- string sharedGitDir = null ;
100- if ( File . Exists ( commondirFile ) )
143+ // Read commondir to find the shared .git/ directory.
144+ // All valid worktrees must have a commondir file.
145+ string commondirFile = Path . Combine ( gitdirPath , GVFSConstants . DotGit . CommonDirName ) ;
146+ if ( ! File . Exists ( commondirFile ) )
101147 {
102- string commondirContent = File . ReadAllText ( commondirFile ) . Trim ( ) ;
103- sharedGitDir = Path . GetFullPath ( Path . Combine ( gitdirPath , commondirContent ) ) ;
148+ return null ;
104149 }
105150
151+ string commondirContent = File . ReadAllText ( commondirFile ) . Trim ( ) ;
152+ string sharedGitDir = Path . GetFullPath ( Path . Combine ( gitdirPath , commondirContent ) ) ;
153+
106154 return new WorktreeInfo
107155 {
108156 Name = worktreeName ,
109- WorktreePath = directory ,
157+ WorktreePath = worktreeRoot ,
110158 WorktreeGitDir = gitdirPath ,
111159 SharedGitDir = sharedGitDir ,
112160 PipeSuffix = "_WT_" + worktreeName . ToUpper ( ) ,
113161 } ;
114162 }
115- catch ( IOException )
163+ catch ( IOException e )
116164 {
165+ error = e . Message ;
117166 return null ;
118167 }
119- catch ( UnauthorizedAccessException )
168+ catch ( UnauthorizedAccessException e )
120169 {
170+ error = e . Message ;
121171 return null ;
122172 }
123173 }
0 commit comments