39 thread_local wxString tl_contextPath;
40 thread_local bool tl_contextIsFile =
false;
46 const bool isFile = !aPath.IsEmpty() && wxFileName( aPath ).FileExists();
47 tl_contextPath = aPath;
48 tl_contextIsFile = isFile;
54 return tl_contextPath.IsEmpty() ? wxString( wxT(
"." ) ) : tl_contextPath;
60 return tl_contextIsFile;
82 wxString ResolveEffectivePath(
const std::string& aPath )
84 if( aPath.empty() || aPath ==
"." )
87 wxFileName
path( wxString::FromUTF8( aPath ) );
89 if(
path.IsRelative() )
92 context.MakeAbsolute();
93 path.MakeAbsolute( tl_contextIsFile ? context.GetPath() : context.GetFullPath() );
96 return path.GetFullPath();
101 using VCS_VALUE = TEXT_EVAL::ENVIRONMENT::VCS_VALUE;
103 VCS_VALUE Capture( VCS_QUERY aQuery,
const std::string& aPath,
const std::string& aArgument,
104 int aOptions,
const std::function<VCS_VALUE()>& aRead )
108 wxFileName
path = wxFileName::DirName( ResolveEffectivePath( aPath ) );
110 const bool contextFile = tl_contextIsFile && ( aPath.empty() || aPath ==
"." );
111 return environment->VcsValue( { aQuery,
112 path.GetPath( (
int) wxPATH_GET_VOLUME ),
123 git_repository* OpenRepo(
const std::string& aPath )
128 wxFileName effective( ResolveEffectivePath( aPath ) );
130 if( ( !aPath.empty() && aPath !=
"." ) || tl_contextIsFile )
132 effective.MakeAbsolute();
139 void CloseRepo( git_repository* aRepo )
142 git_repository_free( aRepo );
145 git_oid MakeZeroOid()
148 git_oid_fromstrn( &oid,
"0000000000000000000000000000000000000000", 40 );
152 git_oid GetFileCommit( git_repository* aRepo,
const std::string& aPath )
155 return MakeZeroOid();
159 if( git_oid_is_zero( &head_oid ) )
160 return MakeZeroOid();
163 if( aPath.empty() || aPath ==
"." )
166 const char* workdir = git_repository_workdir( aRepo );
169 return MakeZeroOid();
171 wxFileName file( ResolveEffectivePath( aPath ) );
174 file.GetPath(), wxString::FromUTF8( workdir ) );
176 if( !file.MakeRelativeTo( base ) )
177 return MakeZeroOid();
179 const std::string treePath = file.GetFullPath( wxPATH_UNIX ).ToStdString( wxConvUTF8 );
181 if( treePath.empty() || treePath ==
"." || treePath ==
".." || treePath.starts_with(
"../" ) )
182 return MakeZeroOid();
185 git_revwalk* walker =
nullptr;
187 if( git_revwalk_new( &walker, aRepo ) != 0 )
188 return MakeZeroOid();
190 git_revwalk_sorting( walker, GIT_SORT_TIME );
192 if( git_revwalk_push( walker, &head_oid ) != 0 )
194 git_revwalk_free( walker );
195 return MakeZeroOid();
199 git_oid
result = MakeZeroOid();
201 git_oid prev_blob_oid = MakeZeroOid();
202 bool first_commit =
true;
204 while( git_revwalk_next( &commit_oid, walker ) == 0 )
206 git_commit* commit =
nullptr;
208 if( git_commit_lookup( &commit, aRepo, &commit_oid ) != 0 )
212 git_tree* tree =
nullptr;
214 if( git_commit_tree( &tree, commit ) == 0 )
217 git_tree_entry* entry =
nullptr;
219 if( git_tree_entry_bypath( &entry, tree, treePath.c_str() ) == 0 )
221 const git_oid* blob_oid = git_tree_entry_id( entry );
226 git_oid_cpy( &prev_blob_oid, blob_oid );
227 git_oid_cpy( &
result, &commit_oid );
228 first_commit =
false;
230 else if( git_oid_cmp( blob_oid, &prev_blob_oid ) != 0 )
233 git_tree_entry_free( entry );
234 git_tree_free( tree );
235 git_commit_free( commit );
241 git_oid_cpy( &
result, &commit_oid );
244 git_tree_entry_free( entry );
246 else if( !first_commit )
250 git_tree_free( tree );
251 git_commit_free( commit );
255 git_tree_free( tree );
258 git_commit_free( commit );
261 git_revwalk_free( walker );
271 DESCRIBE_INFO ReadDescribeInfo(
const std::string& aMatch,
bool aAnyTags )
273 git_repository* repo = OpenRepo(
"." );
276 return { std::string(), 0 };
280 if( git_oid_is_zero( &head_oid ) )
283 return { std::string(), 0 };
286 git_strarray tag_names;
288 if( git_tag_list_match( &tag_names, aMatch.empty() ?
"*" : aMatch.c_str(), repo ) != 0 )
291 return { std::string(), 0 };
295 std::map<git_oid, std::string,
decltype(
296 [](
const git_oid& a,
const git_oid& b)
298 return git_oid_cmp(&a, &b) < 0;
301 for(
size_t i = 0; i < tag_names.count; ++i )
303 git_object* tag_obj =
nullptr;
305 if( git_revparse_single( &tag_obj, repo, tag_names.strings[i] ) == 0 )
307 git_object_t type = git_object_type( tag_obj );
309 if( type == GIT_OBJECT_TAG )
311 git_object* target =
nullptr;
313 if( git_tag_peel( &target, (git_tag*) tag_obj ) == 0 )
315 commit_to_tag[*git_object_id( target )] = tag_names.strings[i];
316 git_object_free( target );
319 else if( aAnyTags && type == GIT_OBJECT_COMMIT )
321 commit_to_tag[*git_object_id( tag_obj )] = tag_names.strings[i];
324 git_object_free( tag_obj );
328 git_strarray_dispose( &tag_names );
330 git_revwalk* walker =
nullptr;
332 if( git_revwalk_new( &walker, repo ) != 0 )
335 return { std::string(), 0 };
338 git_revwalk_sorting( walker, GIT_SORT_TOPOLOGICAL | GIT_SORT_TIME );
340 if( git_revwalk_push( walker, &head_oid ) != 0 )
342 git_revwalk_free( walker );
344 return { std::string(), 0 };
347 DESCRIBE_INFO
result{ std::string(), 0 };
351 while( git_revwalk_next( &commit_oid, walker ) == 0 )
353 auto it = commit_to_tag.find( commit_oid );
355 if( it != commit_to_tag.end() )
365 git_revwalk_free( walker );
370 DESCRIBE_INFO GetDescribeInfo(
const std::string& aMatch,
bool aAnyTags )
372 const VCS_VALUE value = Capture( VCS_QUERY::DESCRIPTION,
".", aMatch, aAnyTags,
375 const DESCRIBE_INFO description = ReadDescribeInfo( aMatch, aAnyTags );
376 return { description.tag, description.distance };
378 return { value.text,
static_cast<int>( value.number ) };
381 std::string ReadCommitSignatureField(
const std::string& aPath,
bool aUseCommitter,
bool aGetEmail )
383 git_repository* repo = OpenRepo( aPath );
386 return std::string();
388 git_oid oid = GetFileCommit( repo, aPath );
390 if( git_oid_is_zero( &oid ) )
393 return std::string();
396 git_commit* commit =
nullptr;
399 if( git_commit_lookup( &commit, repo, &oid ) == 0 )
401 const git_signature* sig = aUseCommitter ? git_commit_committer( commit ) : git_commit_author( commit );
405 const char* field = aGetEmail ? sig->email : sig->name;
411 git_commit_free( commit );
418 std::string GetCommitSignatureField(
const std::string& aPath,
bool aUseCommitter,
bool aGetEmail )
420 return Capture( VCS_QUERY::SIGNATURE, aPath, aPath, ( aUseCommitter ? 2 : 0 ) | ( aGetEmail ? 1 : 0 ),
423 return { ReadCommitSignatureField( aPath, aUseCommitter, aGetEmail ) };
432 git_repository* repo = OpenRepo( aPath );
435 return std::string();
437 git_oid oid = GetFileCommit( repo, aPath );
439 if( git_oid_is_zero( &oid ) )
442 return std::string();
445 char hash[GIT_OID_HEXSZ + 1];
446 git_oid_tostr( hash,
sizeof( hash ), &oid );
455 const VCS_VALUE value = Capture( VCS_QUERY::HASH, aPath, aPath, 0,
460 return value.text.substr( 0, std::clamp( aLength, 4, GIT_OID_HEXSZ ) );
466 return GetDescribeInfo( aMatch, aAnyTags ).tag;
472 return GetDescribeInfo( aMatch, aAnyTags ).distance;
478 git_repository* repo = OpenRepo(
"." );
483 git_status_list* status =
nullptr;
484 git_status_options statusOpts;
485 git_status_options_init( &statusOpts, GIT_STATUS_OPTIONS_VERSION );
487 statusOpts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
488 statusOpts.flags = aIncludeUntracked ? GIT_STATUS_OPT_INCLUDE_UNTRACKED : GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
490 bool isDirty =
false;
492 if( git_status_list_new( &status, repo, &statusOpts ) == 0 )
494 isDirty = git_status_list_entrycount( status ) > 0;
495 git_status_list_free( status );
505 return Capture( VCS_QUERY::DIRTY,
".",
"", aIncludeUntracked,
515 return GetCommitSignatureField( aPath,
false,
false );
521 return GetCommitSignatureField( aPath,
false,
true );
527 return GetCommitSignatureField( aPath,
true,
false );
533 return GetCommitSignatureField( aPath,
true,
true );
539 git_repository* repo = OpenRepo(
"." );
542 return std::string();
548 return branchName.ToStdString();
554 return Capture( VCS_QUERY::BRANCH,
".",
"", 0,
564 git_repository* repo = OpenRepo( aPath );
569 git_oid oid = GetFileCommit( repo, aPath );
571 if( git_oid_is_zero( &oid ) )
577 git_commit* commit =
nullptr;
578 int64_t timestamp = 0;
580 if( git_commit_lookup( &commit, repo, &oid ) == 0 )
582 timestamp =
static_cast<int64_t
>( git_commit_time( commit ) );
583 git_commit_free( commit );
593 return Capture( VCS_QUERY::TIMESTAMP, aPath, aPath, 0,
604 return timestamp > 0 ? std::to_string( timestamp ) : std::string();
613 const auto& [query,
path, argument, options, contextFile] = aKey;
615 if( query == VCS_QUERY::HEAD )
620 git_repository* raw =
nullptr;
623 if( git_repository_open( &raw,
path.ToUTF8().data() ) != 0 )
629 if( git_reference_name_to_id( &oid, repo.get(),
"HEAD" ) != 0 )
632 char hash[GIT_OID_HEXSZ + 1];
633 git_oid_tostr( hash,
sizeof( hash ), &oid );
637 const bool fileQuery = ( query == VCS_QUERY::HASH
638 || query == VCS_QUERY::SIGNATURE
639 || query == VCS_QUERY::TIMESTAMP )
643 const std::string file = fileQuery ?
path.ToStdString( wxConvUTF8 ) : std::string();
647 case VCS_QUERY::HASH:
650 case VCS_QUERY::DESCRIPTION:
652 const DESCRIBE_INFO description = ReadDescribeInfo( argument, options != 0 );
653 return { description.tag, description.distance };
656 case VCS_QUERY::SIGNATURE:
657 return { ReadCommitSignatureField( file, ( options & 2 ) != 0, ( options & 1 ) != 0 ) };
659 case VCS_QUERY::BRANCH:
662 case VCS_QUERY::DIRTY:
665 case VCS_QUERY::TIMESTAMP:
668 case VCS_QUERY::HEAD:
676 return environment->VcsValue( aKey, read );
std::tuple< VCS_QUERY, wxString, std::string, int, bool > VCS_KEY
Query, absolute path (Git directory for HEAD), selector or pattern, options, and whether the context ...
std::string GetAuthor(const std::string &aPath)
Get the author name of the HEAD commit.
bool IsDirty(bool aIncludeUntracked)
Check if the repository has uncommitted changes.
TEXT_EVAL::ENVIRONMENT::VCS_VALUE ReadSource(const TEXT_EVAL::ENVIRONMENT::VCS_KEY &aKey)
Re-read an owned query descriptor using any active frame memo.
std::string GetCommitterEmail(const std::string &aPath)
Get the committer email of the HEAD commit.
std::string GetCommitDate(const std::string &aPath)
Get the commit date of the HEAD commit as a timestamp string.
std::string GetAuthorEmail(const std::string &aPath)
Get the author email of the HEAD commit.
void SetContextPath(const wxString &aPath)
Set the filesystem path used for repository discovery and relative file queries.
std::string GetCommitter(const std::string &aPath)
Get the committer name of the HEAD commit.
std::string GetNearestTag(const std::string &aMatch, bool aAnyTags)
Get the nearest tag/label from HEAD.
int64_t GetCommitTimestamp(const std::string &aPath)
Get the commit timestamp (Unix time) of the HEAD commit.
int GetDistanceFromTag(const std::string &aMatch, bool aAnyTags)
Get the number of commits since the nearest matching tag.