KiCad PCB EDA Suite
Loading...
Searching...
No Matches
project_git_utils.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
21#include "project_git_utils.h"
22#include "git_backend.h"
23#include "kicad_git_memory.h"
24
25#include <wx/filename.h>
26#include <wx/string.h>
27#include <string_utils.h>
28
29#ifndef __WINDOWS__
30#include <climits>
31#include <cstdlib>
32#endif
33
34namespace KIGIT
35{
36
37git_repository* PROJECT_GIT_UTILS::GetRepositoryForFile( const char* aFilename )
38{
39 GIT_BACKEND* backend = GetGitBackend();
40
41 if( !backend )
42 return nullptr;
43
44 return backend->GetRepositoryForFile( aFilename );
45}
46
47
48int PROJECT_GIT_UTILS::CreateBranch( git_repository* aRepo, const wxString& aBranchName )
49{
50 GIT_BACKEND* backend = GetGitBackend();
51
52 if( !backend )
53 return -1;
54
55 return backend->CreateBranch( aRepo, aBranchName );
56}
57
58
59bool PROJECT_GIT_UTILS::RemoveVCS( git_repository*& aRepo, const wxString& aProjectPath,
60 bool aRemoveGitDir, wxString* aErrors )
61{
62 GIT_BACKEND* backend = GetGitBackend();
63
64 if( !backend )
65 return false;
66
67 return backend->RemoveVCS( aRepo, aProjectPath, aRemoveGitDir, aErrors );
68}
69
70
71git_oid PROJECT_GIT_UTILS::GetCapturedHeadOid( git_repository* aRepo )
72{
73 if( !aRepo )
74 return {};
75
76 using ENVIRONMENT = TEXT_EVAL::ENVIRONMENT;
77 const auto read = [&]() -> ENVIRONMENT::VCS_VALUE
78 {
79 git_oid oid{};
80
81 if( git_reference_name_to_id( &oid, aRepo, "HEAD" ) != 0 )
82 return {};
83
84 char hash[GIT_OID_HEXSZ + 1];
85 git_oid_tostr( hash, sizeof( hash ), &oid );
86 return { hash };
87 };
88 auto* environment = ENVIRONMENT::Current();
89 const char* path = git_repository_path( aRepo );
90 const auto value = environment && path
91 ? environment->VcsValue( { ENVIRONMENT::VCS_QUERY::HEAD, wxString::FromUTF8( path ), "", 0, false }, read )
92 : read();
93 git_oid oid{};
94
95 if( value.text.empty() || git_oid_fromstr( &oid, value.text.c_str() ) != 0 )
96 return {};
97
98 return oid;
99}
100
101
102wxString PROJECT_GIT_UTILS::GetCurrentHash( const wxString& aProjectFile, bool aShort )
103{
104 const auto read = [&]
105 {
106 GitRepositoryPtr repo( GetRepositoryForFile( TO_UTF8( aProjectFile ) ) );
107 const git_oid oid = GetCapturedHeadOid( repo.get() );
108
109 if( git_oid_is_zero( &oid ) )
110 return wxString( "no hash" );
111
112 char hash[GIT_OID_HEXSZ + 1];
113 git_oid_tostr( hash, sizeof( hash ), &oid );
114 return wxString::FromUTF8( hash );
115 };
116 auto* environment = TEXT_EVAL::ENVIRONMENT::Current();
117 const wxString hash = environment ? environment->GitHash( aProjectFile, read ) : read();
118 return aShort ? hash.Left( 8 ) : hash;
119}
120
121
122wxString PROJECT_GIT_UTILS::ComputeSymlinkPreservingWorkDir( const wxString& aUserProjectPath,
123 const wxString& aCanonicalWorkDir )
124{
125#ifdef __WINDOWS__
126 return aCanonicalWorkDir;
127#else
128 if( aUserProjectPath.IsEmpty() || aCanonicalWorkDir.IsEmpty() )
129 return aCanonicalWorkDir;
130
131 char resolvedPath[PATH_MAX];
132
133 if( realpath( aUserProjectPath.mb_str(), resolvedPath ) == nullptr )
134 return aCanonicalWorkDir;
135
136 wxString canonicalUserPath = wxString::FromUTF8( resolvedPath );
137
138 if( !canonicalUserPath.EndsWith( wxFileName::GetPathSeparator() ) )
139 canonicalUserPath += wxFileName::GetPathSeparator();
140
141 wxString canonicalWorkDirNorm = aCanonicalWorkDir;
142
143 if( !canonicalWorkDirNorm.EndsWith( wxFileName::GetPathSeparator() ) )
144 canonicalWorkDirNorm += wxFileName::GetPathSeparator();
145
146 // The user project path resolves to the same directory as the git workdir (e.g., the
147 // project is at the repo root but opened via a symlinked path). Return the user path
148 // to preserve symlinks. When the project is in a subdirectory of the workdir, the
149 // paths will differ and we fall through to extract only the workdir portion below.
150 if( canonicalUserPath == canonicalWorkDirNorm )
151 {
152 return aUserProjectPath.EndsWith( wxFileName::GetPathSeparator() )
153 ? aUserProjectPath
154 : aUserProjectPath + wxFileName::GetPathSeparator();
155 }
156
157 // Walk both paths upward in lockstep until the canonical user path matches the canonical
158 // workdir. This correctly handles symlinks that compress multiple canonical path
159 // components into fewer visible components (e.g. /work/repo -> /real/deep/path/root).
160 wxFileName userFn;
161 userFn.AssignDir( aUserProjectPath );
162
163 wxFileName canonicalUserFn;
164 canonicalUserFn.AssignDir( canonicalUserPath );
165
166 wxFileName canonicalWorkDirFn;
167 canonicalWorkDirFn.AssignDir( canonicalWorkDirNorm );
168
169 while( canonicalUserFn.GetFullPath() != canonicalWorkDirFn.GetFullPath() )
170 {
171 if( canonicalUserFn.GetDirCount() == 0 || userFn.GetDirCount() == 0 )
172 return aCanonicalWorkDir;
173
174 canonicalUserFn.RemoveLastDir();
175 userFn.RemoveLastDir();
176 }
177
178 return userFn.GetPathWithSep();
179#endif
180}
181
182
183bool PROJECT_GIT_UTILS::IsWithinProjectPath( const wxString& aAbsPath, const wxString& aProjectPath )
184{
185 if( aProjectPath.IsEmpty() )
186 return false;
187
188 wxString projectPath = aProjectPath;
189
190 if( !projectPath.EndsWith( wxT( "/" ) ) && !projectPath.EndsWith( wxT( "\\" ) ) )
191 projectPath += wxT( "/" );
192
193 return aAbsPath.StartsWith( projectPath );
194}
195
196} // namespace KIGIT
virtual int CreateBranch(git_repository *aRepo, const wxString &aBranchName)=0
virtual bool RemoveVCS(git_repository *&aRepo, const wxString &aProjectPath, bool aRemoveGitDir, wxString *aErrors)=0
virtual git_repository * GetRepositoryForFile(const char *aFilename)=0
static git_oid GetCapturedHeadOid(git_repository *aRepo)
Return HEAD for commit-based text queries.
static wxString GetCurrentHash(const wxString &aProjectFile, bool aShort)
Return the current HEAD commit hash for the repository containing aProjectFile.
static git_repository * GetRepositoryForFile(const char *aFilename)
Discover and open the repository that contains the given file.
static wxString ComputeSymlinkPreservingWorkDir(const wxString &aUserProjectPath, const wxString &aCanonicalWorkDir)
Compute a working directory path that preserves symlinks from the user's project path.
static bool IsWithinProjectPath(const wxString &aAbsPath, const wxString &aProjectPath)
Test whether an absolute file path lives inside the current project directory.
static bool RemoveVCS(git_repository *&aRepo, const wxString &aProjectPath=wxEmptyString, bool aRemoveGitDir=false, wxString *aErrors=nullptr)
Remove version control from a directory by freeing the repository and optionally removing the ....
static int CreateBranch(git_repository *aRepo, const wxString &aBranchName)
Create a new branch based on HEAD.
A text evaluation frame with one frozen clock and memoized external queries.
static ENVIRONMENT * Current()
GIT_BACKEND * GetGitBackend()
std::unique_ptr< git_repository, decltype([](git_repository *aRepo) { git_repository_free(aRepo); })> GitRepositoryPtr
A unique pointer for git_repository objects with automatic cleanup.
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
std::string path