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 );
188 if( git_revwalk_push( walker, &head_oid ) != 0 )
190 git_revwalk_free( walker );
191 return MakeZeroOid();
195 git_oid
result = MakeZeroOid();
197 git_oid prev_blob_oid = MakeZeroOid();
198 bool first_commit =
true;
200 while( git_revwalk_next( &commit_oid, walker ) == 0 )
202 git_commit* commit =
nullptr;
204 if( git_commit_lookup( &commit, aRepo, &commit_oid ) != 0 )
208 git_tree* tree =
nullptr;
210 if( git_commit_tree( &tree, commit ) == 0 )
213 git_tree_entry* entry =
nullptr;
215 if( git_tree_entry_bypath( &entry, tree, treePath.c_str() ) == 0 )
217 const git_oid* blob_oid = git_tree_entry_id( entry );
222 git_oid_cpy( &prev_blob_oid, blob_oid );
223 git_oid_cpy( &
result, &commit_oid );
224 first_commit =
false;
226 else if( git_oid_cmp( blob_oid, &prev_blob_oid ) != 0 )
229 git_tree_entry_free( entry );
230 git_tree_free( tree );
231 git_commit_free( commit );
237 git_oid_cpy( &
result, &commit_oid );
240 git_tree_entry_free( entry );
242 else if( !first_commit )
246 git_tree_free( tree );
247 git_commit_free( commit );
251 git_tree_free( tree );
254 git_commit_free( commit );
257 git_revwalk_free( walker );
267 DescribeInfo ReadDescribeInfo(
const std::string& aMatch,
bool aAnyTags )
269 git_repository* repo = OpenRepo(
"." );
272 return { std::string(), 0 };
276 if( git_oid_is_zero( &head_oid ) )
279 return { std::string(), 0 };
282 git_strarray tag_names;
284 if( git_tag_list_match( &tag_names, aMatch.empty() ?
"*" : aMatch.c_str(), repo ) != 0 )
287 return { std::string(), 0 };
291 std::map<git_oid, std::string,
decltype(
292 [](
const git_oid& a,
const git_oid& b)
294 return git_oid_cmp(&a, &b) < 0;
297 for(
size_t i = 0; i < tag_names.count; ++i )
299 git_object* tag_obj =
nullptr;
301 if( git_revparse_single( &tag_obj, repo, tag_names.strings[i] ) == 0 )
303 git_object_t type = git_object_type( tag_obj );
305 if( type == GIT_OBJECT_TAG )
307 git_object* target =
nullptr;
309 if( git_tag_peel( &target, (git_tag*) tag_obj ) == 0 )
311 commit_to_tag[*git_object_id( target )] = tag_names.strings[i];
312 git_object_free( target );
315 else if( aAnyTags && type == GIT_OBJECT_COMMIT )
317 commit_to_tag[*git_object_id( tag_obj )] = tag_names.strings[i];
320 git_object_free( tag_obj );
324 git_strarray_dispose( &tag_names );
326 git_revwalk* walker =
nullptr;
328 if( git_revwalk_new( &walker, repo ) != 0 )
331 return { std::string(), 0 };
334 git_revwalk_sorting( walker, GIT_SORT_TOPOLOGICAL | GIT_SORT_TIME );
336 if( git_revwalk_push( walker, &head_oid ) != 0 )
338 git_revwalk_free( walker );
340 return { std::string(), 0 };
343 DescribeInfo
result{ std::string(), 0 };
347 while( git_revwalk_next( &commit_oid, walker ) == 0 )
349 auto it = commit_to_tag.find( commit_oid );
351 if( it != commit_to_tag.end() )
361 git_revwalk_free( walker );
366 DescribeInfo GetDescribeInfo(
const std::string& aMatch,
bool aAnyTags )
368 const auto value = Capture( VCS_QUERY::DESCRIPTION,
".", aMatch, aAnyTags,
371 const auto description = ReadDescribeInfo( aMatch, aAnyTags );
372 return { description.tag, description.distance };
374 return { value.text,
static_cast<int>( value.number ) };
377 std::string ReadCommitSignatureField(
const std::string& aPath,
bool aUseCommitter,
bool aGetEmail )
379 git_repository* repo = OpenRepo( aPath );
382 return std::string();
384 git_oid oid = GetFileCommit( repo, aPath );
386 if( git_oid_is_zero( &oid ) )
389 return std::string();
392 git_commit* commit =
nullptr;
395 if( git_commit_lookup( &commit, repo, &oid ) == 0 )
397 const git_signature* sig = aUseCommitter ? git_commit_committer( commit ) : git_commit_author( commit );
401 const char* field = aGetEmail ? sig->email : sig->name;
407 git_commit_free( commit );
414 std::string GetCommitSignatureField(
const std::string& aPath,
bool aUseCommitter,
bool aGetEmail )
416 return Capture( VCS_QUERY::SIGNATURE, aPath, aPath, ( aUseCommitter ? 2 : 0 ) | ( aGetEmail ? 1 : 0 ),
417 [&]() -> VCS_VALUE {
return { ReadCommitSignatureField( aPath, aUseCommitter, aGetEmail ) }; } ).
text;
425 git_repository* repo = OpenRepo( aPath );
428 return std::string();
430 git_oid oid = GetFileCommit( repo, aPath );
432 if( git_oid_is_zero( &oid ) )
435 return std::string();
438 char hash[GIT_OID_HEXSZ + 1];
439 git_oid_tostr( hash,
sizeof( hash ), &oid );
448 const auto value = Capture( VCS_QUERY::HASH, aPath, aPath, 0,
450 return value.text.substr( 0, std::clamp( aLength, 4, GIT_OID_HEXSZ ) );
456 return GetDescribeInfo( aMatch, aAnyTags ).tag;
462 return GetDescribeInfo( aMatch, aAnyTags ).distance;
468 git_repository* repo = OpenRepo(
"." );
473 git_status_list* status =
nullptr;
474 git_status_options statusOpts;
475 git_status_options_init( &statusOpts, GIT_STATUS_OPTIONS_VERSION );
477 statusOpts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
478 statusOpts.flags = aIncludeUntracked ? GIT_STATUS_OPT_INCLUDE_UNTRACKED : GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
480 bool isDirty =
false;
482 if( git_status_list_new( &status, repo, &statusOpts ) == 0 )
484 isDirty = git_status_list_entrycount( status ) > 0;
485 git_status_list_free( status );
495 return Capture( VCS_QUERY::DIRTY,
".",
"", aIncludeUntracked,
496 [&]() -> VCS_VALUE {
return { {},
ReadIsDirty( aIncludeUntracked ) }; } ).number != 0;
502 return GetCommitSignatureField( aPath,
false,
false );
508 return GetCommitSignatureField( aPath,
false,
true );
514 return GetCommitSignatureField( aPath,
true,
false );
520 return GetCommitSignatureField( aPath,
true,
true );
526 git_repository* repo = OpenRepo(
"." );
529 return std::string();
535 return branchName.ToStdString();
541 return Capture( VCS_QUERY::BRANCH,
".",
"", 0,
548 git_repository* repo = OpenRepo( aPath );
553 git_oid oid = GetFileCommit( repo, aPath );
555 if( git_oid_is_zero( &oid ) )
561 git_commit* commit =
nullptr;
562 int64_t timestamp = 0;
564 if( git_commit_lookup( &commit, repo, &oid ) == 0 )
566 timestamp =
static_cast<int64_t
>( git_commit_time( commit ) );
567 git_commit_free( commit );
577 return Capture( VCS_QUERY::TIMESTAMP, aPath, aPath, 0,
585 return timestamp > 0 ? std::to_string( timestamp ) : std::string();
591 const auto read = [&]() -> VCS_VALUE
593 const auto& [query,
path, argument, options, contextFile] = aKey;
595 if( query == VCS_QUERY::HEAD )
600 git_repository* raw =
nullptr;
603 if( git_repository_open( &raw,
path.ToUTF8().data() ) != 0 )
609 if( git_reference_name_to_id( &oid, repo.get(),
"HEAD" ) != 0 )
612 char hash[GIT_OID_HEXSZ + 1];
613 git_oid_tostr( hash,
sizeof( hash ), &oid );
617 const bool fileQuery = ( query == VCS_QUERY::HASH || query == VCS_QUERY::SIGNATURE
618 || query == VCS_QUERY::TIMESTAMP )
619 && !argument.empty() && argument !=
".";
621 const std::string file = fileQuery ?
path.ToStdString( wxConvUTF8 ) : std::string();
625 case VCS_QUERY::HASH:
628 case VCS_QUERY::DESCRIPTION:
630 const auto description = ReadDescribeInfo( argument, options != 0 );
631 return { description.tag, description.distance };
634 case VCS_QUERY::SIGNATURE:
635 return { ReadCommitSignatureField( file, ( options & 2 ) != 0, ( options & 1 ) != 0 ) };
637 case VCS_QUERY::BRANCH:
640 case VCS_QUERY::DIRTY:
643 case VCS_QUERY::TIMESTAMP:
646 case VCS_QUERY::HEAD:
654 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 GetCommitDate(const std::string &aPath)
Get the commit date of the HEAD commit as a timestamp string.
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.