Fix git status icons not showing when project opened via symlink

When a project is opened through a symlinked path, libgit2's
git_repository_workdir() returns the canonical path with symlinks
resolved. This caused a path mismatch between the tree cache
(which uses the user-provided symlinked path) and git status
results (which used canonical paths), preventing status icons
from being displayed.

The fix introduces SetProjectDir()/GetProjectDir() methods to
KIGIT_COMMON to store and use the user-provided project path.
A helper function computeSymlinkPreservingWorkDir() computes
the git working directory path while preserving symlinks from
the original user path.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/21092
This commit is contained in:
Seth Hillbrand
2026-01-11 20:04:12 -08:00
parent 1143df89b1
commit 23765b6ebe
7 changed files with 161 additions and 24 deletions
+81
View File
@@ -24,9 +24,15 @@
#include "project_git_utils.h"
#include "git_backend.h"
#include <wx/filename.h>
#include <wx/string.h>
#include <string_utils.h>
#ifndef __WINDOWS__
#include <climits>
#include <cstdlib>
#endif
namespace KIGIT
{
@@ -76,4 +82,79 @@ wxString PROJECT_GIT_UTILS::GetCurrentHash( const wxString& aProjectFile, bool a
return result;
}
wxString PROJECT_GIT_UTILS::ComputeSymlinkPreservingWorkDir( const wxString& aUserProjectPath,
const wxString& aCanonicalWorkDir )
{
#ifdef __WINDOWS__
return aCanonicalWorkDir;
#else
if( aUserProjectPath.IsEmpty() || aCanonicalWorkDir.IsEmpty() )
return aCanonicalWorkDir;
char resolvedPath[PATH_MAX];
if( realpath( aUserProjectPath.mb_str(), resolvedPath ) == nullptr )
return aCanonicalWorkDir;
wxString canonicalUserPath = wxString::FromUTF8( resolvedPath );
if( !canonicalUserPath.EndsWith( wxFileName::GetPathSeparator() ) )
canonicalUserPath += wxFileName::GetPathSeparator();
wxString canonicalWorkDirNorm = aCanonicalWorkDir;
if( !canonicalWorkDirNorm.EndsWith( wxFileName::GetPathSeparator() ) )
canonicalWorkDirNorm += wxFileName::GetPathSeparator();
// The workdir could be at or above the project directory
if( canonicalUserPath.StartsWith( canonicalWorkDirNorm ) )
{
return aUserProjectPath.EndsWith( wxFileName::GetPathSeparator() )
? aUserProjectPath
: aUserProjectPath + wxFileName::GetPathSeparator();
}
// The workdir is above the user path - find the portion that corresponds to the workdir
wxFileName userFn( aUserProjectPath );
wxFileName workDirFn( aCanonicalWorkDir );
wxArrayString workDirParts = workDirFn.GetDirs();
size_t workDirDepth = workDirParts.GetCount();
wxFileName canonicalUserFn( canonicalUserPath );
wxArrayString canonicalUserParts = canonicalUserFn.GetDirs();
if( canonicalUserParts.GetCount() < workDirDepth )
return aCanonicalWorkDir;
wxArrayString canonicalWorkDirParts = workDirFn.GetDirs();
for( size_t i = 0; i < workDirDepth; ++i )
{
if( canonicalUserParts[i] != canonicalWorkDirParts[i] )
return aCanonicalWorkDir;
}
wxArrayString userParts = userFn.GetDirs();
if( userParts.GetCount() < workDirDepth )
return aCanonicalWorkDir;
wxString result = userFn.GetVolume();
if( !result.IsEmpty() )
result += wxFileName::GetVolumeSeparator();
result += wxFileName::GetPathSeparator();
for( size_t i = 0; i < workDirDepth; ++i )
{
result += userParts[i];
result += wxFileName::GetPathSeparator();
}
return result;
#endif
}
} // namespace KIGIT