KiCad PCB EDA Suite
Loading...
Searching...
No Matches
text_eval_vcs.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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
22#include <git/git_backend.h>
25#include <wx/string.h>
26#include <wx/arrstr.h> // REQUIRED for wxString vector export on MSVC
27#include <map>
28
30{
31// Private implementation details
32namespace
33{
34 git_repository* OpenRepo( const std::string& aPath )
35 {
36 if( !GetGitBackend() )
37 return nullptr;
38
40 }
41
42 void CloseRepo( git_repository* aRepo )
43 {
44 if( aRepo )
45 git_repository_free( aRepo );
46 }
47
48 git_oid MakeZeroOid()
49 {
50 git_oid oid;
51 git_oid_fromstrn( &oid, "0000000000000000000000000000000000000000", 40 );
52 return oid;
53 }
54
55 git_oid GetFileCommit( git_repository* aRepo, const std::string& aPath )
56 {
57 if( !aRepo )
58 return MakeZeroOid();
59
60 // Get HEAD commit
61 git_oid head_oid;
62
63 if( git_reference_name_to_id( &head_oid, aRepo, "HEAD" ) != 0 )
64 return MakeZeroOid();
65
66 // For repo-level query (empty or "."), just return HEAD
67 if( aPath.empty() || aPath == "." )
68 return head_oid;
69
70 // For file-specific query, walk history to find last commit that touched this file
71 git_revwalk* walker = nullptr;
72
73 if( git_revwalk_new( &walker, aRepo ) != 0 )
74 return MakeZeroOid();
75
76 git_revwalk_sorting( walker, GIT_SORT_TIME );
77 git_revwalk_push( walker, &head_oid );
78
79 // Walk through commits to find when the file was last modified
80 git_oid result = MakeZeroOid();
81 git_oid commit_oid;
82 git_oid prev_blob_oid = MakeZeroOid();
83 bool first_commit = true;
84
85 while( git_revwalk_next( &commit_oid, walker ) == 0 )
86 {
87 git_commit* commit = nullptr;
88
89 if( git_commit_lookup( &commit, aRepo, &commit_oid ) != 0 )
90 continue;
91
92 // Get the tree for this commit
93 git_tree* tree = nullptr;
94
95 if( git_commit_tree( &tree, commit ) == 0 )
96 {
97 // Try to find the file in this tree
98 git_tree_entry* entry = nullptr;
99
100 if( git_tree_entry_bypath( &entry, tree, aPath.c_str() ) == 0 )
101 {
102 const git_oid* blob_oid = git_tree_entry_id( entry );
103
104 if( first_commit )
105 {
106 // First time we see this file, remember its blob ID
107 git_oid_cpy( &prev_blob_oid, blob_oid );
108 git_oid_cpy( &result, &commit_oid );
109 first_commit = false;
110 }
111 else if( git_oid_cmp( blob_oid, &prev_blob_oid ) != 0 )
112 {
113 // File content changed - previous commit is where it changed
114 git_tree_entry_free( entry );
115 git_tree_free( tree );
116 git_commit_free( commit );
117 break;
118 }
119 else
120 {
121 // File unchanged, keep looking
122 git_oid_cpy( &result, &commit_oid );
123 }
124
125 git_tree_entry_free( entry );
126 }
127 else if( !first_commit )
128 {
129 // File doesn't exist in this commit, but existed before
130 // So the previous commit is where it was added/last modified
131 git_tree_free( tree );
132 git_commit_free( commit );
133 break;
134 }
135
136 git_tree_free( tree );
137 }
138
139 git_commit_free( commit );
140 }
141
142 git_revwalk_free( walker );
143 return result;
144 }
145
146 struct DescribeInfo
147 {
148 std::string tag;
149 int distance;
150 };
151
152 DescribeInfo GetDescribeInfo( const std::string& aMatch, bool aAnyTags )
153 {
154 git_repository* repo = OpenRepo( "." );
155
156 if( !repo )
157 return { std::string(), 0 };
158
159 git_oid head_oid;
160
161 if( git_reference_name_to_id( &head_oid, repo, "HEAD" ) != 0 )
162 {
163 CloseRepo( repo );
164 return { std::string(), 0 };
165 }
166
167 git_strarray tag_names;
168
169 if( git_tag_list_match( &tag_names, aMatch.empty() ? "*" : aMatch.c_str(), repo ) != 0 )
170 {
171 CloseRepo( repo );
172 return { std::string(), 0 };
173 }
174
175 // Build map of commit OID -> tag name upfront
176 std::map<git_oid, std::string, decltype(
177 [](const git_oid& a, const git_oid& b)
178 {
179 return git_oid_cmp(&a, &b) < 0;
180 } )> commit_to_tag;
181
182 for( size_t i = 0; i < tag_names.count; ++i )
183 {
184 git_object* tag_obj = nullptr;
185
186 if( git_revparse_single( &tag_obj, repo, tag_names.strings[i] ) == 0 )
187 {
188 git_object_t type = git_object_type( tag_obj );
189
190 if( type == GIT_OBJECT_TAG )
191 {
192 git_object* target = nullptr;
193
194 if( git_tag_peel( &target, (git_tag*) tag_obj ) == 0 )
195 {
196 commit_to_tag[*git_object_id( target )] = tag_names.strings[i];
197 git_object_free( target );
198 }
199 }
200 else if( aAnyTags && type == GIT_OBJECT_COMMIT )
201 {
202 commit_to_tag[*git_object_id( tag_obj )] = tag_names.strings[i];
203 }
204
205 git_object_free( tag_obj );
206 }
207 }
208
209 git_strarray_dispose( &tag_names );
210
211 git_revwalk* walker = nullptr;
212
213 if( git_revwalk_new( &walker, repo ) != 0 )
214 {
215 CloseRepo( repo );
216 return { std::string(), 0 };
217 }
218
219 git_revwalk_sorting( walker, GIT_SORT_TOPOLOGICAL | GIT_SORT_TIME );
220 git_revwalk_push( walker, &head_oid );
221
222 DescribeInfo result{ std::string(), 0 };
223 int distance = 0;
224 git_oid commit_oid;
225
226 while( git_revwalk_next( &commit_oid, walker ) == 0 )
227 {
228 auto it = commit_to_tag.find( commit_oid );
229
230 if( it != commit_to_tag.end() )
231 {
232 result.tag = it->second;
233 result.distance = distance;
234 break;
235 }
236
237 distance++;
238 }
239
240 git_revwalk_free( walker );
241 CloseRepo( repo );
242 return result;
243 }
244
245 std::string GetCommitSignatureField( const std::string& aPath, bool aUseCommitter, bool aGetEmail )
246 {
247 git_repository* repo = OpenRepo( aPath );
248
249 if( !repo )
250 return std::string();
251
252 git_oid oid = GetFileCommit( repo, aPath );
253
254 if( git_oid_is_zero( &oid ) )
255 {
256 CloseRepo( repo );
257 return std::string();
258 }
259
260 git_commit* commit = nullptr;
261 std::string result;
262
263 if( git_commit_lookup( &commit, repo, &oid ) == 0 )
264 {
265 const git_signature* sig = aUseCommitter ? git_commit_committer( commit ) : git_commit_author( commit );
266
267 if( sig )
268 {
269 const char* field = aGetEmail ? sig->email : sig->name;
270
271 if( field )
272 result = field;
273 }
274
275 git_commit_free( commit );
276 }
277
278 CloseRepo( repo );
279 return result;
280 }
281
282} // anonymous namespace
283
284
285std::string GetCommitHash( const std::string& aPath, int aLength )
286{
287 git_repository* repo = OpenRepo( aPath );
288
289 if( !repo )
290 return std::string();
291
292 git_oid oid = GetFileCommit( repo, aPath );
293
294 if( git_oid_is_zero( &oid ) )
295 {
296 CloseRepo( repo );
297 return std::string();
298 }
299
300 int length = std::max( 4, std::min( aLength, GIT_OID_HEXSZ ) );
301 char hash[GIT_OID_HEXSZ + 1];
302 git_oid_tostr( hash, length + 1, &oid );
303
304 CloseRepo( repo );
305 return hash;
306}
307
308
309std::string GetNearestTag( const std::string& aMatch, bool aAnyTags )
310{
311 return GetDescribeInfo( aMatch, aAnyTags ).tag;
312}
313
314
315int GetDistanceFromTag( const std::string& aMatch, bool aAnyTags )
316{
317 return GetDescribeInfo( aMatch, aAnyTags ).distance;
318}
319
320
321bool IsDirty( bool aIncludeUntracked )
322{
323 git_repository* repo = OpenRepo( "." );
324
325 if( !repo )
326 return false;
327
328 git_status_list* status = nullptr;
329 git_status_options statusOpts;
330 git_status_options_init( &statusOpts, GIT_STATUS_OPTIONS_VERSION );
331
332 statusOpts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
333 statusOpts.flags = aIncludeUntracked ? GIT_STATUS_OPT_INCLUDE_UNTRACKED : GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
334
335 bool isDirty = false;
336
337 if( git_status_list_new( &status, repo, &statusOpts ) == 0 )
338 {
339 isDirty = git_status_list_entrycount( status ) > 0;
340 git_status_list_free( status );
341 }
342
343 CloseRepo( repo );
344 return isDirty;
345}
346
347
348std::string GetAuthor( const std::string& aPath )
349{
350 return GetCommitSignatureField( aPath, false, false );
351}
352
353
354std::string GetAuthorEmail( const std::string& aPath )
355{
356 return GetCommitSignatureField( aPath, false, true );
357}
358
359
360std::string GetCommitter( const std::string& aPath )
361{
362 return GetCommitSignatureField( aPath, true, false );
363}
364
365
366std::string GetCommitterEmail( const std::string& aPath )
367{
368 return GetCommitSignatureField( aPath, true, true );
369}
370
371
372std::string GetBranch()
373{
374 git_repository* repo = OpenRepo( "." );
375
376 if( !repo )
377 return std::string();
378
379 KIGIT_COMMON common( repo );
380 wxString branchName = common.GetCurrentBranchName();
381
382 CloseRepo( repo );
383 return branchName.ToStdString();
384}
385
386
387int64_t GetCommitTimestamp( const std::string& aPath )
388{
389 git_repository* repo = OpenRepo( aPath );
390
391 if( !repo )
392 return 0;
393
394 git_oid oid = GetFileCommit( repo, aPath );
395
396 if( git_oid_is_zero( &oid ) )
397 {
398 CloseRepo( repo );
399 return 0;
400 }
401
402 git_commit* commit = nullptr;
403 int64_t timestamp = 0;
404
405 if( git_commit_lookup( &commit, repo, &oid ) == 0 )
406 {
407 timestamp = static_cast<int64_t>( git_commit_time( commit ) );
408 git_commit_free( commit );
409 }
410
411 CloseRepo( repo );
412 return timestamp;
413}
414
415
416std::string GetCommitDate( const std::string& aPath )
417{
418 int64_t timestamp = GetCommitTimestamp( aPath );
419 return timestamp > 0 ? std::to_string( timestamp ) : std::string();
420}
421
422} // namespace TEXT_EVAL_VCS
wxString GetCommitHash()
Get the commit hash as a string.
static git_repository * GetRepositoryForFile(const char *aFilename)
Discover and open the repository that contains the given file.
wxString GetCurrentBranchName() const
GIT_BACKEND * GetGitBackend()
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.
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.
std::string GetCommitter(const std::string &aPath)
Get the committer name of the HEAD commit.
std::string GetBranch()
Get the current branch name.
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.
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
wxString result
Test unit parsing edge cases and error handling.