KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_git_compare_handler.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
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/gpl-3.0.html
19 */
20
26
28
32
33#include <git2.h>
34
35#include <fstream>
36#include <map>
37
38#include <wx/filename.h>
39#include <wx/stdpaths.h>
40#include <wx/utils.h>
41
42
43static const char* TEST_AUTHOR_NAME = "Test Author";
45
46
53{
55 m_repo( nullptr ),
56 m_ready( false )
57 {
58 git_libgit2_init();
59
60 wxString tmp = wxStandardPaths::Get().GetTempDir() + wxFileName::GetPathSeparator()
61 + wxString::Format( wxS( "kicad_qa_cmp_%lu_%ld" ),
62 static_cast<unsigned long>( wxGetProcessId() ),
63 s_counter++ );
64
65 wxFileName::Mkdir( tmp, 0700, wxPATH_MKDIR_FULL );
66 m_repoPath = tmp;
67
68 m_ready = initRepo();
69 }
70
71
73 {
74 if( m_repo )
75 git_repository_free( m_repo );
76
77 if( !m_repoPath.IsEmpty() )
78 wxFileName::Rmdir( m_repoPath, wxPATH_RMDIR_RECURSIVE );
79
80 git_libgit2_shutdown();
81 }
82
83
85 void writeFile( const wxString& aFile, const wxString& aContent )
86 {
87 wxString filePath = m_repoPath + wxFileName::GetPathSeparator() + aFile;
88 std::ofstream f( filePath.ToStdString(), std::ios::trunc );
89 f << aContent.ToStdString();
90 }
91
92
93 void removeFile( const wxString& aFile )
94 {
95 wxRemoveFile( m_repoPath + wxFileName::GetPathSeparator() + aFile );
96 }
97
98
100 bool commitAll( const wxString& aMessage )
101 {
102 git_index* index = nullptr;
103
104 if( git_repository_index( &index, m_repo ) != 0 )
105 return false;
106
107 KIGIT::GitIndexPtr indexPtr( index );
108
109 char* paths[] = { const_cast<char*>( "*" ) };
110 git_strarray pathspec = { paths, 1 };
111
112 if( git_index_add_all( index, &pathspec, GIT_INDEX_ADD_DEFAULT, nullptr, nullptr ) != 0 )
113 return false;
114
115 git_index_write( index );
116
117 git_oid treeOid;
118
119 if( git_index_write_tree( &treeOid, index ) != 0 )
120 return false;
121
122 git_tree* tree = nullptr;
123
124 if( git_tree_lookup( &tree, m_repo, &treeOid ) != 0 )
125 return false;
126
127 KIGIT::GitTreePtr treePtr( tree );
128
129 git_signature* sig = nullptr;
130 git_signature_now( &sig, TEST_AUTHOR_NAME, TEST_AUTHOR_EMAIL );
131 KIGIT::GitSignaturePtr sigPtr( sig );
132
133 git_commit* parent = nullptr;
134 git_reference* headRef = nullptr;
135
136 if( git_repository_head( &headRef, m_repo ) == 0 )
137 {
138 git_reference_peel( reinterpret_cast<git_object**>( &parent ), headRef,
139 GIT_OBJECT_COMMIT );
140 git_reference_free( headRef );
141 }
142
143 KIGIT::GitCommitPtr parentPtr( parent );
144 const git_commit* parents[1] = { parent };
145 const git_commit** parentsPtr = parent ? parents : nullptr;
146 size_t parentsCount = parent ? 1 : 0;
147
148 git_oid commitOid;
149
150 return git_commit_create( &commitOid, m_repo, "HEAD", sig, sig, nullptr,
151 aMessage.ToUTF8().data(), tree, parentsCount, parentsPtr )
152 == 0;
153 }
154
155
156 git_repository* repo() const { return m_repo; }
157 bool ready() const { return m_ready; }
158
159
160private:
161 bool initRepo()
162 {
163 // Pin the initial branch to "master" so assertions are independent of the
164 // user's init.defaultBranch (often "main") in their gitconfig.
165 git_repository_init_options initOpts;
166 git_repository_init_options_init( &initOpts, GIT_REPOSITORY_INIT_OPTIONS_VERSION );
167 initOpts.initial_head = "master";
168
169 if( git_repository_init_ext( &m_repo, m_repoPath.ToUTF8().data(), &initOpts ) != 0 )
170 return false;
171
172 git_config* cfg = nullptr;
173
174 if( git_repository_config( &cfg, m_repo ) == 0 )
175 {
176 git_config_set_string( cfg, "user.name", TEST_AUTHOR_NAME );
177 git_config_set_string( cfg, "user.email", TEST_AUTHOR_EMAIL );
178 git_config_free( cfg );
179 }
180
181 writeFile( wxT( "a.txt" ), wxT( "alpha\n" ) );
182 writeFile( wxT( "b.txt" ), wxT( "bravo\n" ) );
183
184 if( !commitAll( wxT( "First commit" ) ) )
185 return false;
186
187 writeFile( wxT( "a.txt" ), wxT( "alpha modified\n" ) );
188 removeFile( wxT( "b.txt" ) );
189 writeFile( wxT( "c.txt" ), wxT( "charlie\n" ) );
190
191 return commitAll( wxT( "Second commit" ) );
192 }
193
194
195 git_repository* m_repo;
196 wxString m_repoPath;
198
199 static long s_counter;
200};
201
203
204
205BOOST_FIXTURE_TEST_SUITE( GitCompareHandler, GIT_COMPARE_FIXTURE )
206
207
208BOOST_AUTO_TEST_CASE( ResolveRefToTree_Valid )
209{
210 BOOST_REQUIRE( ready() );
211
212 KIGIT::GitTreePtr head( KIGIT::ResolveRefToTree( repo(), wxT( "HEAD" ) ) );
213 BOOST_CHECK( head.get() != nullptr );
214
215 KIGIT::GitTreePtr prev( KIGIT::ResolveRefToTree( repo(), wxT( "HEAD~1" ) ) );
216 BOOST_CHECK( prev.get() != nullptr );
217
218 // HEAD and its parent point at different trees.
219 BOOST_CHECK( head.get() != prev.get() );
220}
221
222
223BOOST_AUTO_TEST_CASE( ResolveRefToTree_Invalid )
224{
225 BOOST_REQUIRE( ready() );
226
227 BOOST_CHECK( KIGIT::ResolveRefToTree( repo(), wxT( "does-not-exist" ) ) == nullptr );
228 BOOST_CHECK( KIGIT::ResolveRefToTree( nullptr, wxT( "HEAD" ) ) == nullptr );
229}
230
231
232BOOST_AUTO_TEST_CASE( CollectDiffDeltas_NullDiffIsNoOp )
233{
234 int calls = 0;
235 KIGIT::CollectDiffDeltas( nullptr, [&calls]( const git_diff_delta& ) { ++calls; } );
236 BOOST_CHECK_EQUAL( calls, 0 );
237}
238
239
240BOOST_AUTO_TEST_CASE( CollectDiffDeltas_WalksEveryDelta )
241{
242 BOOST_REQUIRE( ready() );
243
244 KIGIT::GitTreePtr baseTree( KIGIT::ResolveRefToTree( repo(), wxT( "HEAD~1" ) ) );
245 KIGIT::GitTreePtr headTree( KIGIT::ResolveRefToTree( repo(), wxT( "HEAD" ) ) );
246
247 BOOST_REQUIRE( baseTree.get() );
248 BOOST_REQUIRE( headTree.get() );
249
250 git_diff* diff = nullptr;
251 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
252
253 BOOST_REQUIRE_EQUAL(
254 git_diff_tree_to_tree( &diff, repo(), baseTree.get(), headTree.get(), &opts ), 0 );
255
256 KIGIT::GitDiffPtr diffPtr( diff );
257
258 std::map<wxString, git_delta_t> seen;
260 [&seen]( const git_diff_delta& aDelta )
261 {
262 const char* path = aDelta.new_file.path ? aDelta.new_file.path
263 : aDelta.old_file.path;
264 seen[wxString::FromUTF8( path )] = aDelta.status;
265 } );
266
267 BOOST_CHECK_EQUAL( seen.size(), 3u );
268 BOOST_CHECK( seen.at( wxT( "a.txt" ) ) == GIT_DELTA_MODIFIED );
269 BOOST_CHECK( seen.at( wxT( "b.txt" ) ) == GIT_DELTA_DELETED );
270 BOOST_CHECK( seen.at( wxT( "c.txt" ) ) == GIT_DELTA_ADDED );
271}
272
273
274BOOST_AUTO_TEST_CASE( CompareRefs_ReportsExpectedChanges )
275{
276 BOOST_REQUIRE( ready() );
277
278 std::vector<KIGIT::CHANGED_FILE> changes =
279 KIGIT::CompareRefs( repo(), wxT( "HEAD~1" ), wxT( "HEAD" ) );
280
281 std::map<wxString, KIGIT::FILE_CHANGE_STATUS> byPath;
282
283 for( const KIGIT::CHANGED_FILE& f : changes )
284 byPath[f.path] = f.status;
285
286 BOOST_CHECK_EQUAL( byPath.size(), 3u );
287 BOOST_CHECK( byPath.at( wxT( "a.txt" ) ) == KIGIT::FILE_CHANGE_STATUS::MODIFIED );
288 BOOST_CHECK( byPath.at( wxT( "b.txt" ) ) == KIGIT::FILE_CHANGE_STATUS::REMOVED );
289 BOOST_CHECK( byPath.at( wxT( "c.txt" ) ) == KIGIT::FILE_CHANGE_STATUS::ADDED );
290}
291
292
293BOOST_AUTO_TEST_CASE( CompareRefs_InvalidRefReturnsEmpty )
294{
295 BOOST_REQUIRE( ready() );
296
297 BOOST_CHECK( KIGIT::CompareRefs( repo(), wxT( "HEAD" ), wxT( "nope" ) ).empty() );
298 BOOST_CHECK( KIGIT::CompareRefs( nullptr, wxT( "HEAD~1" ), wxT( "HEAD" ) ).empty() );
299}
300
301
int index
static bool empty(const wxTextEntryBase *aCtrl)
std::unique_ptr< git_tree, decltype([](git_tree *aTree) { git_tree_free(aTree); })> GitTreePtr
A unique pointer for git_tree objects with automatic cleanup.
std::vector< CHANGED_FILE > CompareRefs(git_repository *aRepo, const wxString &aBaseRef, const wxString &aHeadRef)
Compare two git refs (branch / tag / commit OID) within a repository and return the per-file change l...
git_tree * ResolveRefToTree(git_repository *aRepo, const wxString &aRef)
Resolve a string ref (branch name, short OID, full OID, tag) to its tree.
std::unique_ptr< git_commit, decltype([](git_commit *aCommit) { git_commit_free(aCommit); })> GitCommitPtr
A unique pointer for git_commit objects with automatic cleanup.
void CollectDiffDeltas(git_diff *aDiff, const std::function< void(const git_diff_delta &)> &aCallback)
Walk every delta in a computed diff, invoking aCallback once per delta.
std::unique_ptr< git_signature, decltype([](git_signature *aSignature) { git_signature_free(aSignature); })> GitSignaturePtr
A unique pointer for git_signature objects with automatic cleanup.
std::unique_ptr< git_index, decltype([](git_index *aIndex) { git_index_free(aIndex); })> GitIndexPtr
A unique pointer for git_index objects with automatic cleanup.
std::unique_ptr< git_diff, decltype([](git_diff *aDiff) { git_diff_free(aDiff); })> GitDiffPtr
A unique pointer for git_diff objects with automatic cleanup.
Build a temp working repo with two commits.
git_repository * repo() const
void removeFile(const wxString &aFile)
bool commitAll(const wxString &aMessage)
Stage every change (including deletions) and create a commit on HEAD.
void writeFile(const wxString &aFile, const wxString &aContent)
Write a file in the working tree (or remove it if aRemove is set).
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
static const char * TEST_AUTHOR_NAME
BOOST_AUTO_TEST_CASE(ResolveRefToTree_Valid)
static const char * TEST_AUTHOR_EMAIL
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
std::string path
BOOST_CHECK_EQUAL(result, "25.4")