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,
path.GetPath( wxPATH_GET_VOLUME ), aArgument, aOptions,
112 contextFile }, aRead );
119 git_repository* OpenRepo(
const std::string& aPath )
124 wxFileName effective( ResolveEffectivePath( aPath ) );
126 if( ( !aPath.empty() && aPath !=
"." ) || tl_contextIsFile )
128 effective.MakeAbsolute();
135 void CloseRepo( git_repository* aRepo )
138 git_repository_free( aRepo );
141 git_oid MakeZeroOid()
144 git_oid_fromstrn( &oid,
"0000000000000000000000000000000000000000", 40 );
148 git_oid GetFileCommit( git_repository* aRepo,
const std::string& aPath )
151 return MakeZeroOid();
155 if( git_oid_is_zero( &head_oid ) )
156 return MakeZeroOid();
159 if( aPath.empty() || aPath ==
"." )
162 const char* workdir = git_repository_workdir( aRepo );
165 return MakeZeroOid();
167 wxFileName file( ResolveEffectivePath( aPath ) );
170 file.GetPath(), wxString::FromUTF8( workdir ) );
172 if( !file.MakeRelativeTo( base ) )
173 return MakeZeroOid();
175 const std::string treePath = file.GetFullPath( wxPATH_UNIX ).ToStdString( wxConvUTF8 );
177 if( treePath.empty() || treePath ==
"." || treePath ==
".." || treePath.starts_with(
"../" ) )
178 return MakeZeroOid();
181 git_revwalk* walker =
nullptr;
183 if( git_revwalk_new( &walker, aRepo ) != 0 )
184 return MakeZeroOid();
186 git_revwalk_sorting( walker, GIT_SORT_TIME );
187 git_revwalk_push( walker, &head_oid );
190 git_oid
result = MakeZeroOid();
192 git_oid prev_blob_oid = MakeZeroOid();
193 bool first_commit =
true;
195 while( git_revwalk_next( &commit_oid, walker ) == 0 )
197 git_commit* commit =
nullptr;
199 if( git_commit_lookup( &commit, aRepo, &commit_oid ) != 0 )
203 git_tree* tree =
nullptr;
205 if( git_commit_tree( &tree, commit ) == 0 )
208 git_tree_entry* entry =
nullptr;
210 if( git_tree_entry_bypath( &entry, tree, treePath.c_str() ) == 0 )
212 const git_oid* blob_oid = git_tree_entry_id( entry );
217 git_oid_cpy( &prev_blob_oid, blob_oid );
218 git_oid_cpy( &
result, &commit_oid );
219 first_commit =
false;
221 else if( git_oid_cmp( blob_oid, &prev_blob_oid ) != 0 )
224 git_tree_entry_free( entry );
225 git_tree_free( tree );
226 git_commit_free( commit );
232 git_oid_cpy( &
result, &commit_oid );
235 git_tree_entry_free( entry );
237 else if( !first_commit )
241 git_tree_free( tree );
242 git_commit_free( commit );
246 git_tree_free( tree );
249 git_commit_free( commit );
252 git_revwalk_free( walker );
262 DescribeInfo ReadDescribeInfo(
const std::string& aMatch,
bool aAnyTags )
264 git_repository* repo = OpenRepo(
"." );
267 return { std::string(), 0 };
271 if( git_oid_is_zero( &head_oid ) )
274 return { std::string(), 0 };
277 git_strarray tag_names;
279 if( git_tag_list_match( &tag_names, aMatch.empty() ?
"*" : aMatch.c_str(), repo ) != 0 )
282 return { std::string(), 0 };
286 std::map<git_oid, std::string,
decltype(
287 [](
const git_oid& a,
const git_oid& b)
289 return git_oid_cmp(&a, &b) < 0;
292 for(
size_t i = 0; i < tag_names.count; ++i )
294 git_object* tag_obj =
nullptr;
296 if( git_revparse_single( &tag_obj, repo, tag_names.strings[i] ) == 0 )
298 git_object_t type = git_object_type( tag_obj );
300 if( type == GIT_OBJECT_TAG )
302 git_object* target =
nullptr;
304 if( git_tag_peel( &target, (git_tag*) tag_obj ) == 0 )
306 commit_to_tag[*git_object_id( target )] = tag_names.strings[i];
307 git_object_free( target );
310 else if( aAnyTags && type == GIT_OBJECT_COMMIT )
312 commit_to_tag[*git_object_id( tag_obj )] = tag_names.strings[i];
315 git_object_free( tag_obj );
319 git_strarray_dispose( &tag_names );
321 git_revwalk* walker =
nullptr;
323 if( git_revwalk_new( &walker, repo ) != 0 )
326 return { std::string(), 0 };
329 git_revwalk_sorting( walker, GIT_SORT_TOPOLOGICAL | GIT_SORT_TIME );
330 git_revwalk_push( walker, &head_oid );
332 DescribeInfo
result{ std::string(), 0 };
336 while( git_revwalk_next( &commit_oid, walker ) == 0 )
338 auto it = commit_to_tag.find( commit_oid );
340 if( it != commit_to_tag.end() )
350 git_revwalk_free( walker );
355 DescribeInfo GetDescribeInfo(
const std::string& aMatch,
bool aAnyTags )
357 const auto value = Capture( VCS_QUERY::DESCRIPTION,
".", aMatch, aAnyTags,
360 const auto description = ReadDescribeInfo( aMatch, aAnyTags );
361 return { description.tag, description.distance };
363 return { value.text,
static_cast<int>( value.number ) };
366 std::string ReadCommitSignatureField(
const std::string& aPath,
bool aUseCommitter,
bool aGetEmail )
368 git_repository* repo = OpenRepo( aPath );
371 return std::string();
373 git_oid oid = GetFileCommit( repo, aPath );
375 if( git_oid_is_zero( &oid ) )
378 return std::string();
381 git_commit* commit =
nullptr;
384 if( git_commit_lookup( &commit, repo, &oid ) == 0 )
386 const git_signature* sig = aUseCommitter ? git_commit_committer( commit ) : git_commit_author( commit );
390 const char* field = aGetEmail ? sig->email : sig->name;
396 git_commit_free( commit );
403 std::string GetCommitSignatureField(
const std::string& aPath,
bool aUseCommitter,
bool aGetEmail )
405 return Capture( VCS_QUERY::SIGNATURE, aPath, aPath, ( aUseCommitter ? 2 : 0 ) | ( aGetEmail ? 1 : 0 ),
406 [&]() -> VCS_VALUE {
return { ReadCommitSignatureField( aPath, aUseCommitter, aGetEmail ) }; } ).
text;
414 git_repository* repo = OpenRepo( aPath );
417 return std::string();
419 git_oid oid = GetFileCommit( repo, aPath );
421 if( git_oid_is_zero( &oid ) )
424 return std::string();
427 char hash[GIT_OID_HEXSZ + 1];
428 git_oid_tostr( hash,
sizeof( hash ), &oid );
437 const auto value = Capture( VCS_QUERY::HASH, aPath, aPath, 0,
439 return value.text.substr( 0, std::clamp( aLength, 4, GIT_OID_HEXSZ ) );
445 return GetDescribeInfo( aMatch, aAnyTags ).tag;
451 return GetDescribeInfo( aMatch, aAnyTags ).distance;
457 git_repository* repo = OpenRepo(
"." );
462 git_status_list* status =
nullptr;
463 git_status_options statusOpts;
464 git_status_options_init( &statusOpts, GIT_STATUS_OPTIONS_VERSION );
466 statusOpts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
467 statusOpts.flags = aIncludeUntracked ? GIT_STATUS_OPT_INCLUDE_UNTRACKED : GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
469 bool isDirty =
false;
471 if( git_status_list_new( &status, repo, &statusOpts ) == 0 )
473 isDirty = git_status_list_entrycount( status ) > 0;
474 git_status_list_free( status );
484 return Capture( VCS_QUERY::DIRTY,
".",
"", aIncludeUntracked,
485 [&]() -> VCS_VALUE {
return { {},
ReadIsDirty( aIncludeUntracked ) }; } ).number != 0;
491 return GetCommitSignatureField( aPath,
false,
false );
497 return GetCommitSignatureField( aPath,
false,
true );
503 return GetCommitSignatureField( aPath,
true,
false );
509 return GetCommitSignatureField( aPath,
true,
true );
515 git_repository* repo = OpenRepo(
"." );
518 return std::string();
524 return branchName.ToStdString();
530 return Capture( VCS_QUERY::BRANCH,
".",
"", 0,
537 git_repository* repo = OpenRepo( aPath );
542 git_oid oid = GetFileCommit( repo, aPath );
544 if( git_oid_is_zero( &oid ) )
550 git_commit* commit =
nullptr;
551 int64_t timestamp = 0;
553 if( git_commit_lookup( &commit, repo, &oid ) == 0 )
555 timestamp =
static_cast<int64_t
>( git_commit_time( commit ) );
556 git_commit_free( commit );
566 return Capture( VCS_QUERY::TIMESTAMP, aPath, aPath, 0,
574 return timestamp > 0 ? std::to_string( timestamp ) : std::string();
580 const auto read = [&]() -> VCS_VALUE
582 const auto& [query,
path, argument, options, contextFile] = aKey;
584 if( query == VCS_QUERY::HEAD )
589 git_repository* raw =
nullptr;
592 if( git_repository_open( &raw,
path.ToUTF8().data() ) != 0 )
598 if( git_reference_name_to_id( &oid, repo.get(),
"HEAD" ) != 0 )
601 char hash[GIT_OID_HEXSZ + 1];
602 git_oid_tostr( hash,
sizeof( hash ), &oid );
606 const bool fileQuery = ( query == VCS_QUERY::HASH || query == VCS_QUERY::SIGNATURE
607 || query == VCS_QUERY::TIMESTAMP )
608 && !argument.empty() && argument !=
".";
610 const std::string file = fileQuery ?
path.ToStdString( wxConvUTF8 ) : std::string();
614 case VCS_QUERY::HASH:
617 case VCS_QUERY::DESCRIPTION:
619 const auto description = ReadDescribeInfo( argument, options != 0 );
620 return { description.tag, description.distance };
623 case VCS_QUERY::SIGNATURE:
624 return { ReadCommitSignatureField( file, ( options & 2 ) != 0, ( options & 1 ) != 0 ) };
626 case VCS_QUERY::BRANCH:
629 case VCS_QUERY::DIRTY:
632 case VCS_QUERY::TIMESTAMP:
635 case VCS_QUERY::HEAD:
643 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.