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_common.h"
24
25#include <wx/file.h>
26#include <wx/filename.h>
27
28#include "kicad_git_memory.h"
29#include <wx/string.h>
30#include <string_utils.h>
31
32#ifndef __WINDOWS__
33#include <climits>
34#include <cstdlib>
35#endif
36
37namespace KIGIT
38{
39
40git_repository* PROJECT_GIT_UTILS::GetRepositoryForFile( const char* aFilename, wxString* aError,
41 wxString* aTrustCommand )
42{
43 GIT_BACKEND* backend = GetGitBackend();
44
45 if( !backend )
46 return nullptr;
47
48 return backend->GetRepositoryForFile( aFilename, aError, aTrustCommand );
49}
50
51
52int PROJECT_GIT_UTILS::CreateBranch( git_repository* aRepo, const wxString& aBranchName )
53{
54 GIT_BACKEND* backend = GetGitBackend();
55
56 if( !backend || !KIGIT::BranchNameError( aBranchName ).IsEmpty() )
57 return -1;
58
59 return backend->CreateBranch( aRepo, aBranchName, wxEmptyString );
60}
61
62
63bool PROJECT_GIT_UTILS::RemoveVCS( git_repository*& aRepo, const wxString& aProjectPath,
64 bool aRemoveGitDir, wxString* aErrors )
65{
66 GIT_BACKEND* backend = GetGitBackend();
67
68 if( !backend )
69 return false;
70
71 return backend->RemoveVCS( aRepo, aProjectPath, aRemoveGitDir, aErrors );
72}
73
74
76{
77 if( !aRepo )
78 return {};
79
80 using ENVIRONMENT = TEXT_EVAL::ENVIRONMENT;
81 const auto read = [&]() -> ENVIRONMENT::VCS_VALUE
82 {
83 git_oid oid{};
84
85 if( git_reference_name_to_id( &oid, aRepo, "HEAD" ) != 0 )
86 return {};
87
88 char hash[GIT_OID_HEXSZ + 1];
89 git_oid_tostr( hash, sizeof( hash ), &oid );
90 return { hash };
91 };
92 auto* environment = ENVIRONMENT::Current();
93 const char* path = git_repository_path( aRepo );
94 const auto value = environment && path
95 ? environment->VcsValue( { ENVIRONMENT::VCS_QUERY::HEAD, wxString::FromUTF8( path ), "", 0, false }, read )
96 : read();
97 git_oid oid{};
98
99 if( value.text.empty() || git_oid_fromstr( &oid, value.text.c_str() ) != 0 )
100 return {};
101
102 return oid;
103}
104
105
106wxString PROJECT_GIT_UTILS::GetCurrentHash( const wxString& aProjectFile, bool aShort )
107{
108 const auto read = [&]
109 {
110 GitRepositoryPtr repo( GetRepositoryForFile( TO_UTF8( aProjectFile ) ) );
111 const git_oid oid = GetCapturedHeadOid( repo.get() );
112
113 if( git_oid_is_zero( &oid ) )
114 return wxString( "no hash" );
115
116 char hash[GIT_OID_HEXSZ + 1];
117 git_oid_tostr( hash, sizeof( hash ), &oid );
118 return wxString::FromUTF8( hash );
119 };
120 auto* environment = TEXT_EVAL::ENVIRONMENT::Current();
121 const wxString hash = environment ? environment->GitHash( aProjectFile, read ) : read();
122 return aShort ? hash.Left( 8 ) : hash;
123}
124
125
126// The path with its symlinks resolved and a trailing separator, or the path unchanged when it
127// cannot be resolved. git reports paths under the resolved root, so a path only compares against
128// one of git's once both have been through this.
129static wxString canonicalDir( const wxString& aPath )
130{
131#ifdef __WINDOWS__
132 return aPath;
133#else
134 char resolved[PATH_MAX];
135
136 if( aPath.IsEmpty() || realpath( aPath.mb_str(), resolved ) == nullptr )
137 return aPath;
138
139 wxString out = wxString::FromUTF8( resolved );
140
141 if( !out.EndsWith( wxFileName::GetPathSeparator() ) && wxFileName::DirExists( out ) )
142 out += wxFileName::GetPathSeparator();
143
144 return out;
145#endif
146}
147
148
149wxString PROJECT_GIT_UTILS::ComputeSymlinkPreservingWorkDir( const wxString& aUserProjectPath,
150 const wxString& aCanonicalWorkDir )
151{
152#ifdef __WINDOWS__
153 return aCanonicalWorkDir;
154#else
155 if( aUserProjectPath.IsEmpty() || aCanonicalWorkDir.IsEmpty() )
156 return aCanonicalWorkDir;
157
158 char resolvedPath[PATH_MAX];
159
160 if( realpath( aUserProjectPath.mb_str(), resolvedPath ) == nullptr )
161 return aCanonicalWorkDir;
162
163 wxString canonicalUserPath = wxString::FromUTF8( resolvedPath );
164
165 if( !canonicalUserPath.EndsWith( wxFileName::GetPathSeparator() ) )
166 canonicalUserPath += wxFileName::GetPathSeparator();
167
168 wxString canonicalWorkDirNorm = aCanonicalWorkDir;
169
170 if( !canonicalWorkDirNorm.EndsWith( wxFileName::GetPathSeparator() ) )
171 canonicalWorkDirNorm += wxFileName::GetPathSeparator();
172
173 // The user project path resolves to the same directory as the git workdir (e.g., the
174 // project is at the repo root but opened via a symlinked path). Return the user path
175 // to preserve symlinks. When the project is in a subdirectory of the workdir, the
176 // paths will differ and we fall through to extract only the workdir portion below.
177 if( canonicalUserPath == canonicalWorkDirNorm )
178 {
179 return aUserProjectPath.EndsWith( wxFileName::GetPathSeparator() )
180 ? aUserProjectPath
181 : aUserProjectPath + wxFileName::GetPathSeparator();
182 }
183
184 // Walk both paths upward in lockstep until the canonical user path matches the canonical
185 // workdir. This correctly handles symlinks that compress multiple canonical path
186 // components into fewer visible components (e.g. /work/repo -> /real/deep/path/root).
187 wxFileName userFn;
188 userFn.AssignDir( aUserProjectPath );
189
190 wxFileName canonicalUserFn;
191 canonicalUserFn.AssignDir( canonicalUserPath );
192
193 wxFileName canonicalWorkDirFn;
194 canonicalWorkDirFn.AssignDir( canonicalWorkDirNorm );
195
196 while( canonicalUserFn.GetFullPath() != canonicalWorkDirFn.GetFullPath() )
197 {
198 if( canonicalUserFn.GetDirCount() == 0 || userFn.GetDirCount() == 0 )
199 return aCanonicalWorkDir;
200
201 canonicalUserFn.RemoveLastDir();
202 userFn.RemoveLastDir();
203 }
204
205 // One symlink can stand for several directories, so stepping up the visible path a component
206 // at a time can land somewhere that is not the repository. Only an answer that resolves back
207 // to the working directory is an alias for it.
208 if( canonicalDir( userFn.GetPathWithSep() ) != canonicalWorkDirNorm )
209 return aCanonicalWorkDir;
210
211 return userFn.GetPathWithSep();
212#endif
213}
214
215
216static bool pathIsUnder( const wxString& aAbsPath, const wxString& aProjectPath )
217{
218 wxString absPath = aAbsPath;
219 wxString projectPath = aProjectPath;
220
221#ifdef __WINDOWS__
222 absPath.Replace( wxT( "\\" ), wxT( "/" ) );
223 projectPath.Replace( wxT( "\\" ), wxT( "/" ) );
224#endif
225
226 if( !projectPath.EndsWith( wxT( "/" ) ) )
227 projectPath += wxT( "/" );
228
229#ifdef __WINDOWS__
230 return absPath.Lower().StartsWith( projectPath.Lower() );
231#else
232 return absPath.StartsWith( projectPath );
233#endif
234}
235
236
237bool PROJECT_GIT_UTILS::IsWithinProjectPath( const wxString& aAbsPath, const wxString& aProjectPath )
238{
239 if( aProjectPath.IsEmpty() )
240 return false;
241
242 if( pathIsUnder( aAbsPath, aProjectPath ) )
243 return true;
244
245 // The project can be reached through a symlink the repository path does not share, and then
246 // the two only line up once both are resolved.
247 return pathIsUnder( canonicalDir( aAbsPath ), canonicalDir( aProjectPath ) );
248}
249
250
251static std::optional<wxString> prefixUnder( const wxString& aWorkDir, const wxString& aProjectPath )
252{
253 wxString workDir = aWorkDir;
254 wxString projectPath = aProjectPath;
255
256 workDir.Replace( wxS( "\\" ), wxS( "/" ) );
257 projectPath.Replace( wxS( "\\" ), wxS( "/" ) );
258
259 if( !workDir.EndsWith( wxS( "/" ) ) )
260 workDir += wxS( "/" );
261
262 if( !projectPath.EndsWith( wxS( "/" ) ) )
263 projectPath += wxS( "/" );
264
265#ifdef __WINDOWS__
266 // Windows paths can differ in case.
267 if( !projectPath.Lower().StartsWith( workDir.Lower() ) )
268 return std::nullopt;
269#else
270 if( !projectPath.StartsWith( workDir ) )
271 return std::nullopt;
272#endif
273
274 return projectPath.Mid( workDir.length() );
275}
276
277
278std::optional<wxString> PROJECT_GIT_UTILS::ProjectPrefix( const wxString& aWorkDir, const wxString& aProjectPath )
279{
280 if( aWorkDir.IsEmpty() || aProjectPath.IsEmpty() )
281 return std::nullopt;
282
283 std::optional<wxString> prefix = prefixUnder( aWorkDir, aProjectPath );
284
285 // Resolve symlinks when the project and repository use different paths to the same folder.
286 if( !prefix )
287 prefix = prefixUnder( canonicalDir( aWorkDir ), canonicalDir( aProjectPath ) );
288
289 return prefix;
290}
291
292
293bool PROJECT_GIT_UTILS::PathUsesGitLfs( git_repository* aRepo, const wxString& aPath, const git_oid* aCommit )
294{
295 if( !aRepo || aPath.IsEmpty() )
296 return false;
297
298 const char* value = nullptr;
299 git_attr_options opts = {};
300 opts.version = GIT_ATTR_OPTIONS_VERSION;
301 opts.flags = GIT_ATTR_CHECK_INDEX_THEN_FILE;
302
303 if( aCommit )
304 {
305 // The rules a version carries decide what its own files are, whatever is in hand.
306 opts.flags |= GIT_ATTR_CHECK_INCLUDE_COMMIT;
307 git_oid_cpy( &opts.attr_commit_id, aCommit );
308 }
309
310 if( git_attr_get_ext( &value, aRepo, &opts, aPath.utf8_string().c_str(), "filter" ) != GIT_OK )
311 {
312 return false;
313 }
314
315 return git_attr_value( value ) == GIT_ATTR_VALUE_STRING && strcmp( value, "lfs" ) == 0;
316}
317
318
319namespace
320{
321
322 struct LFS_WALK
323 {
324 git_repository* repo;
325 std::string prefix;
326 wxString example;
327 const git_oid* commit = nullptr;
328 };
329
330
331 int lfsWalkEntry( const char* aRoot, const git_tree_entry* aEntry, void* aPayload )
332 {
333 LFS_WALK* walk = static_cast<LFS_WALK*>( aPayload );
334 std::string path = std::string( aRoot ) + git_tree_entry_name( aEntry );
335
336 if( git_tree_entry_type( aEntry ) == GIT_OBJECT_TREE )
337 {
338 std::string dir = path + "/";
339
340 // Descend only where the project's folder can be.
341 if( walk->prefix.compare( 0, dir.length(), dir ) != 0
342 && dir.compare( 0, walk->prefix.length(), walk->prefix ) != 0 )
343 {
344 return 1;
345 }
346
347 return 0;
348 }
349
350 if( git_tree_entry_type( aEntry ) != GIT_OBJECT_BLOB )
351 return 0;
352
353 if( path.compare( 0, walk->prefix.length(), walk->prefix ) != 0 )
354 return 0;
355
356 if( PROJECT_GIT_UTILS::PathUsesGitLfs( walk->repo, wxString::FromUTF8( path.c_str() ), walk->commit ) )
357 {
358 walk->example = wxString::FromUTF8( path.c_str() );
359 return -1;
360 }
361
362 return 0;
363 }
364
365} // namespace
366
367
369{
370 if( !aCommon || !aCommon->GetRepo() )
371 return wxEmptyString;
372
373 if( !aCommon->GetProjectPrefix() )
374 {
375 return _( "Could not work out which folder this project occupies in the repository, "
376 "so the operation was cancelled." );
377 }
378
379 wxString example;
380
381 if( !ProjectUsesGitLfs( *aCommon, &example ) )
382 return wxEmptyString;
383
384 return wxString::Format( _( "This project keeps '%s' in Git LFS, which KiCad cannot read or "
385 "write. Use git on the command line for this repository." ),
386 example );
387}
388
389
390bool PROJECT_GIT_UTILS::ProjectUsesGitLfs( const KIGIT_COMMON& aCommon, wxString* aExample, const git_oid* aCommit )
391{
392 const std::optional<wxString> prefix = aCommon.GetProjectPrefix();
393
394 if( !prefix )
395 return false;
396
397 git_repository* repo = aCommon.GetRepo();
398 git_oid commitOid;
399
400 if( aCommit )
401 git_oid_cpy( &commitOid, aCommit );
402 else if( git_reference_name_to_id( &commitOid, repo, "HEAD" ) != GIT_OK )
403 return false;
404
405 git_commit* commit = nullptr;
406
407 if( git_commit_lookup( &commit, repo, &commitOid ) != GIT_OK )
408 return false;
409
410 KIGIT::GitCommitPtr commitPtr( commit );
411 git_tree* tree = nullptr;
412
413 if( git_commit_tree( &tree, commit ) != GIT_OK )
414 return false;
415
416 KIGIT::GitTreePtr treePtr( tree );
417 LFS_WALK walk{ repo, prefix->utf8_string(), wxEmptyString, aCommit };
418
419 git_tree_walk( tree, GIT_TREEWALK_PRE, lfsWalkEntry, &walk );
420
421 if( aExample )
422 *aExample = walk.example;
423
424 return !walk.example.IsEmpty();
425}
426
427
429{
430 static const std::vector<wxString> patterns = {
431 wxS( ".history/" ),
432 wxS( "*-backups/" ),
433 wxS( "_autosave-*" ),
434 wxS( "fp-info-cache" ),
435 wxS( "~*" ),
436 wxS( "*.[lL][cC][kK]" ),
437 wxS( "*-bak" ),
438 wxS( "*.[kK][iI][cC][aA][dD]_[pP][rR][lL]" ),
439
440 // Recovery copies that earlier builds left beside the design files.
441 wxS( "*.mine" ),
442 wxS( "*.mine.[0-9]" ),
443 wxS( "*.mine.[0-9][0-9]" ),
444 wxS( "*.theirs" ),
445 wxS( "*.theirs.[0-9]" ),
446 wxS( "*.theirs.[0-9][0-9]" ),
447 };
448
449 return patterns;
450}
451
452
453namespace
454{
455
456 using PATHSPEC_PTR = std::unique_ptr<git_pathspec, decltype( &git_pathspec_free )>;
457
458 // A slash limits a rule to directories. The other rules also match directory names in Git.
459 PATHSPEC_PTR transientPathspec( bool aDirectory )
460 {
461 std::vector<std::string> patterns;
462
463 for( wxString pattern : PROJECT_GIT_UTILS::TransientGitIgnorePatterns() )
464 {
465 if( pattern.EndsWith( wxS( "/" ) ) )
466 {
467 if( !aDirectory )
468 continue;
469
470 pattern.RemoveLast();
471 }
472
473 patterns.push_back( pattern.utf8_string() );
474 }
475
476 std::vector<char*> paths;
477
478 for( std::string& pattern : patterns )
479 paths.push_back( pattern.data() );
480
481 git_strarray pathArray = { paths.data(), paths.size() };
482 git_pathspec* spec = nullptr;
483
484 if( git_pathspec_new( &spec, &pathArray ) != GIT_OK )
485 return PATHSPEC_PTR( nullptr, git_pathspec_free );
486
487 return PATHSPEC_PTR( spec, git_pathspec_free );
488 }
489
490} // namespace
491
492
493bool PROJECT_GIT_UTILS::IsTransientGitFile( const wxFileName& aFile )
494{
495 static const PATHSPEC_PTR filePatterns = transientPathspec( false );
496 static const PATHSPEC_PTR directoryPatterns = transientPathspec( true );
497
498 if( filePatterns && git_pathspec_matches_path( filePatterns.get(), 0, aFile.GetFullName().utf8_str() ) )
499 return true;
500
501 for( const wxString& dir : aFile.GetDirs() )
502 {
503 if( directoryPatterns && git_pathspec_matches_path( directoryPatterns.get(), 0, dir.utf8_str() ) )
504 return true;
505 }
506
507 return false;
508}
509
510
511void PROJECT_GIT_UTILS::SafeFreeRepository( std::shared_ptr<KIGIT_COMMON> aCommon, const std::string& aLabel )
512{
513 if( !aCommon )
514 return;
515
516 // Raised before any exit below, so a session being let go never reads as live whatever
517 // state it is in.
518 aCommon->SetShuttingDown( true );
519
520 git_repository* repo = aCommon->GetRepo();
521
522 if( !repo )
523 return;
524
525 // A null backend means libgit2 is already shut down at process exit. Freeing now would
526 // call into a torn-down library, so drop the handle and let the OS reclaim it.
527 if( !GetGitBackend() )
528 {
529 aCommon->SetRepo( nullptr );
530 return;
531 }
532
533 {
534 std::unique_lock<std::mutex> gitLock = aCommon->TryLockAction();
535
536 if( gitLock.owns_lock() )
537 {
538 aCommon->SetRepo( nullptr );
539 git_repository_free( repo );
540 return;
541 }
542 }
543
544 // An operation still holds the action mutex, so the handle must stay valid until it
545 // finishes. The orphan registry frees it once the mutex is released.
546 auto cleanup = [repo, old = std::move( aCommon )]() mutable
547 {
548 std::unique_lock<std::mutex> g = old->LockAction();
549 old->SetRepo( nullptr );
550 git_repository_free( repo );
551 };
552
553 GIT_BACKEND* backend = GetGitBackend();
554
555 if( !backend || !backend->OrphanRegistry().Register( aLabel, std::move( cleanup ) ) )
556 {
557 // No registry is available, so free synchronously rather than leak. Blocking on
558 // the mutex here is safe because libgit2 shutdown is gated on the registry.
559 cleanup();
560 }
561}
562
563
564bool PROJECT_GIT_UTILS::WriteBlobToFile( git_repository* aRepo, const wxString& aRef, const wxString& aPath,
565 const wxString& aDestPath )
566{
567 if( !aRepo || aRef.IsEmpty() || aPath.IsEmpty() )
568 return false;
569
570 KIGIT::GitTreePtr treePtr( KIGIT::ResolveRefToTree( aRepo, aRef ) );
571
572 if( !treePtr )
573 return false;
574
575 git_tree_entry* entry = nullptr;
576
577 if( git_tree_entry_bypath( &entry, treePtr.get(), aPath.ToUTF8().data() ) != 0 )
578 return false;
579
580 KIGIT::GitTreeEntryPtr entryPtr( entry );
581
582 if( git_tree_entry_type( entry ) != GIT_OBJECT_BLOB )
583 return false;
584
585 git_object* blobObj = nullptr;
586
587 if( git_tree_entry_to_object( &blobObj, aRepo, entry ) != 0 )
588 return false;
589
590 KIGIT::GitObjectPtr blobObjPtr( blobObj );
591 git_blob* blob = reinterpret_cast<git_blob*>( blobObj );
592
593 // Committed file contents can be sensitive and the destination lives in the shared temp
594 // directory, so create it readable and writable only by the current user.
595 wxFile out;
596
597 if( !out.Create( aDestPath, /* overwrite */ true, wxS_IRUSR | wxS_IWUSR ) )
598 return false;
599
600 const void* raw = git_blob_rawcontent( blob );
601 git_off_t size = git_blob_rawsize( blob );
602
603 return out.Write( raw, static_cast<size_t>( size ) ) == static_cast<size_t>( size );
604}
605
606
607bool PROJECT_GIT_UTILS::WriteTreeToDir( git_repository* aRepo, const wxString& aRef, const wxString& aDestDir )
608{
609 if( !aRepo || aRef.IsEmpty() || aDestDir.IsEmpty() )
610 return false;
611
612 KIGIT::GitTreePtr treePtr( KIGIT::ResolveRefToTree( aRepo, aRef ) );
613
614 if( !treePtr )
615 return false;
616
617 // Owner-only, matching the 0600 blob files written into it.
618 if( !wxFileName::Mkdir( aDestDir, 0700, wxPATH_MKDIR_FULL ) )
619 return false;
620
621 std::string destUtf8 = aDestDir.utf8_string();
622 git_checkout_options opts;
623 git_checkout_options_init( &opts, GIT_CHECKOUT_OPTIONS_VERSION );
624
625 // Without DONT_UPDATE_INDEX, git_checkout_tree rewrites .git/index to the checked-out
626 // tree, replacing whatever the user has staged.
627 opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_RECREATE_MISSING | GIT_CHECKOUT_DONT_UPDATE_INDEX;
628 opts.target_directory = destUtf8.c_str();
629
630 return git_checkout_tree( aRepo, reinterpret_cast<git_object*>( treePtr.get() ), &opts ) == GIT_OK;
631}
632
633} // namespace KIGIT
virtual int CreateBranch(git_repository *aRepo, const wxString &aBranchName, const wxString &aFrom)=0
KIGIT_ORPHAN_REGISTRY & OrphanRegistry()
Return the process-wide orphan thread registry owned by this backend.
Definition git_backend.h:92
virtual git_repository * GetRepositoryForFile(const char *aFilename, wxString *aError, wxString *aTrustCommand)=0
aError receives why git refused to open the repository, and aTrustCommand the command that trusts it ...
virtual bool RemoveVCS(git_repository *&aRepo, const wxString &aProjectPath, bool aRemoveGitDir, wxString *aErrors)=0
static bool WriteBlobToFile(git_repository *aRepo, const wxString &aRef, const wxString &aPath, const wxString &aDestPath)
Write a file as it exists at a git ref to a path on disk, so a committed version can be diffed.
static bool PathUsesGitLfs(git_repository *aRepo, const wxString &aPath, const git_oid *aCommit=nullptr)
Whether git routes aPath through the Git LFS filter.
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, wxString *aError=nullptr, wxString *aTrustCommand=nullptr)
Discover and open the repository that contains the given file.
static bool IsTransientGitFile(const wxFileName &aFile)
Match a project-relative path against the shared transient patterns.
static const std::vector< wxString > & TransientGitIgnorePatterns()
Shared patterns for new .gitignore files and the untracked-file filter.
static void SafeFreeRepository(std::shared_ptr< KIGIT_COMMON > aCommon, const std::string &aLabel)
Free the session's repository handle without pulling it out from under a running operation.
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 WriteTreeToDir(git_repository *aRepo, const wxString &aRef, const wxString &aDestDir)
Check out a whole tree into a directory, leaving the repository's working copy and index alone.
static bool ProjectUsesGitLfs(const KIGIT_COMMON &aCommon, wxString *aExample=nullptr, const git_oid *aCommit=nullptr)
Whether any committed file in the project's folder goes through Git LFS.
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 wxString LfsRefusal(KIGIT_COMMON *aCommon)
Return a refusal message for LFS files or an unknown project folder, otherwise empty.
static int CreateBranch(git_repository *aRepo, const wxString &aBranchName)
Create a new branch based on HEAD.
static std::optional< wxString > ProjectPrefix(const wxString &aWorkDir, const wxString &aProjectPath)
The project's folder inside the repository, ending in a slash.
The project's git session.
std::optional< wxString > GetProjectPrefix() const
Project folder relative to the repository, with a trailing slash.
git_repository * GetRepo() const
bool Register(const std::string &aLabel, F &&aWork)
Spawn a tracked orphan thread running aWork.
A text evaluation frame with one frozen clock and memoized external queries.
static ENVIRONMENT * Current()
#define _(s)
GIT_BACKEND * GetGitBackend()
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition idf_helpers.h:37
std::unique_ptr< git_tree, decltype([](git_tree *aTree) { git_tree_free(aTree); })> GitTreePtr
A unique pointer for git_tree objects with automatic cleanup.
static bool pathIsUnder(const wxString &aAbsPath, const wxString &aProjectPath)
wxString BranchNameError(const wxString &aName)
Empty when aName is a branch name git would accept, otherwise why it would not, in the words to show ...
std::unique_ptr< git_repository, decltype([](git_repository *aRepo) { git_repository_free(aRepo); })> GitRepositoryPtr
A unique pointer for git_repository objects with automatic cleanup.
git_tree * ResolveRefToTree(git_repository *aRepo, const wxString &aRef)
Resolve a string ref (branch name, short OID, full OID, tag) to its tree.
std::unique_ptr< git_commit, decltype([](git_commit *aCommit) { git_commit_free(aCommit); })> GitCommitPtr
A unique pointer for git_commit objects with automatic cleanup.
static wxString canonicalDir(const wxString &aPath)
std::unique_ptr< git_tree_entry, decltype([](git_tree_entry *aEntry) { git_tree_entry_free(aEntry); })> GitTreeEntryPtr
A unique pointer for git_tree_entry objects with automatic cleanup.
static std::optional< wxString > prefixUnder(const wxString &aWorkDir, const wxString &aProjectPath)
std::unique_ptr< git_object, decltype([](git_object *aObject) { git_object_free(aObject); })> GitObjectPtr
A unique pointer for git_object objects with automatic cleanup.
std::string path