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 ),
128 wxFileName effective( ResolveEffectivePath( aPath ) );
130 if( ( !aPath.empty() && aPath !=
"." ) || tl_contextIsFile )
132 effective.MakeAbsolute();
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 )
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 )
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 ) };
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;
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 );
542 return std::string();
548 return branchName.ToStdString();
554 return Capture( VCS_QUERY::BRANCH,
".",
"", 0,
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 )
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 ...
RAII helper that sets the VCS context path on construction and restores the previous value on destruc...
VCS (Version Control System) utility functions for text evaluation.
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.
wxString GetContextPath()
Return the current context path for repo-scoped VCS queries.
bool GetContextIsFile()
File/directory classification captured when the current context was activated.
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.