KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_libgit_backend.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, see <https://www.gnu.org/licenses/>.
18 */
19
26
28
29#include <git/git_backend.h>
30#include <git/libgit_backend.h>
36
37#include <git2.h>
38
39#include <fstream>
40
41#include <wx/ffile.h>
42#include <wx/filename.h>
43#include <wx/textfile.h>
44#include <wx/utils.h>
45
46
47static const char* TEST_AUTHOR_NAME = "Test Author";
49
50
57{
59 {
61 m_backend->Init();
63
64 m_tempBase = wxFileName::GetTempDir() + wxFileName::GetPathSeparator()
65 + wxString::Format( "kicad_libgit_backend_test_%ld_%ld", wxGetProcessId(), s_counter++ );
66 wxFileName::Mkdir( m_tempBase, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
67
68 m_repoPath = m_tempBase + wxFileName::GetPathSeparator() + wxT( "repo" );
69 m_remotePath = m_tempBase + wxFileName::GetPathSeparator() + wxT( "remote.git" );
70
71 wxFileName::Mkdir( m_repoPath, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
72
74 }
75
76
78 {
79 if( wxFileName::DirExists( m_tempBase ) )
80 wxFileName::Rmdir( m_tempBase, wxPATH_RMDIR_RECURSIVE );
81
82 SetGitBackend( nullptr );
83 m_backend->Shutdown();
84 delete m_backend;
85 }
86
87
88 bool ready() const { return m_ready; }
89 const wxString& repoPath() const { return m_repoPath; }
90 const wxString& remotePath() const { return m_remotePath; }
91
92
94 git_repository* openRepo() const
95 {
96 git_repository* repo = nullptr;
97 git_repository_open( &repo, m_repoPath.ToUTF8().data() );
98 return repo;
99 }
100
101
103 git_oid createCommit( git_repository* aRepo, const wxString& aFile, const wxString& aContent,
104 const wxString& aMessage )
105 {
106 git_oid commitOid = {};
107
108 wxString filePath = m_repoPath + wxFileName::GetPathSeparator() + aFile;
109 {
110 std::ofstream f( filePath.ToStdString() );
111 f << aContent.ToStdString();
112 }
113
114 git_index* index = nullptr;
115
116 if( git_repository_index( &index, aRepo ) != 0 )
117 return commitOid;
118
119 git_index_add_bypath( index, aFile.ToUTF8().data() );
120 git_index_write( index );
121
122 git_oid treeOid;
123 git_index_write_tree( &treeOid, index );
124 git_index_free( index );
125
126 git_tree* tree = nullptr;
127
128 if( git_tree_lookup( &tree, aRepo, &treeOid ) != 0 )
129 return commitOid;
130
131 git_signature* sig = nullptr;
132 git_signature_now( &sig, TEST_AUTHOR_NAME, TEST_AUTHOR_EMAIL );
133
134 // Parent is HEAD if it resolves, otherwise this becomes the root commit.
135 git_commit* parent = nullptr;
136 git_reference* headRef = nullptr;
137
138 if( git_repository_head( &headRef, aRepo ) == 0 )
139 {
140 git_reference_peel( (git_object**) &parent, headRef, GIT_OBJECT_COMMIT );
141 git_reference_free( headRef );
142 }
143
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_commit_create( &commitOid, aRepo, "HEAD", sig, sig, nullptr, aMessage.ToUTF8().data(), tree, parentsCount,
149 parentsPtr );
150
151 if( parent )
152 git_commit_free( parent );
153
154 git_signature_free( sig );
155 git_tree_free( tree );
156
157 return commitOid;
158 }
159
160
162 bool addOrigin( git_repository* aRepo )
163 {
164 wxString fileUrl = wxS( "file://" ) + m_remotePath;
165 git_remote* remote = nullptr;
166 int rc = git_remote_create( &remote, aRepo, "origin", fileUrl.ToUTF8().data() );
167
168 if( remote )
169 git_remote_free( remote );
170
171 return rc == 0;
172 }
173
174
177 bool clearUpstreamConfig( git_repository* aRepo, const wxString& aBranch )
178 {
179 git_config* cfg = nullptr;
180
181 if( git_repository_config( &cfg, aRepo ) != 0 )
182 return false;
183
184 wxString remoteKey = wxString::Format( "branch.%s.remote", aBranch );
185 wxString mergeKey = wxString::Format( "branch.%s.merge", aBranch );
186
187 git_config_delete_entry( cfg, remoteKey.ToUTF8().data() );
188 git_config_delete_entry( cfg, mergeKey.ToUTF8().data() );
189
190 git_config_free( cfg );
191 return true;
192 }
193
194
196 bool setUpstreamConfig( git_repository* aRepo, const wxString& aBranch )
197 {
198 git_config* cfg = nullptr;
199
200 if( git_repository_config( &cfg, aRepo ) != 0 )
201 return false;
202
203 wxString remoteKey = wxString::Format( "branch.%s.remote", aBranch );
204 wxString mergeKey = wxString::Format( "branch.%s.merge", aBranch );
205 wxString mergeVal = wxString::Format( "refs/heads/%s", aBranch );
206
207 git_config_set_string( cfg, remoteKey.ToUTF8().data(), "origin" );
208 git_config_set_string( cfg, mergeKey.ToUTF8().data(), mergeVal.ToUTF8().data() );
209 git_config_free( cfg );
210 return true;
211 }
212
213
215 wxString readConfig( git_repository* aRepo, const wxString& aKey )
216 {
217 git_config* cfg = nullptr;
218
219 if( git_repository_config( &cfg, aRepo ) != 0 )
220 return wxEmptyString;
221
222 git_buf buf = { nullptr, 0, 0 };
223 wxString result;
224
225 if( git_config_get_string_buf( &buf, cfg, aKey.ToUTF8().data() ) == 0 && buf.ptr )
226 result = wxString::FromUTF8( buf.ptr );
227
228 git_buf_dispose( &buf );
229 git_config_free( cfg );
230 return result;
231 }
232
233
234private:
236 {
237 git_repository* repo = nullptr;
238
239 // Force "master" as the initial branch so the tests are independent of the
240 // user's init.defaultBranch (often "main") in system or global gitconfig.
241 git_repository_init_options initOpts;
242 git_repository_init_options_init( &initOpts, GIT_REPOSITORY_INIT_OPTIONS_VERSION );
243 initOpts.initial_head = "master";
244
245 if( git_repository_init_ext( &repo, m_repoPath.ToUTF8().data(), &initOpts ) != 0 )
246 return false;
247
248 git_config* cfg = nullptr;
249
250 if( git_repository_config( &cfg, repo ) == 0 )
251 {
252 git_config_set_string( cfg, "user.name", TEST_AUTHOR_NAME );
253 git_config_set_string( cfg, "user.email", TEST_AUTHOR_EMAIL );
254 git_config_free( cfg );
255 }
256
257 createCommit( repo, wxT( "file.txt" ), wxT( "initial\n" ), wxT( "Initial commit" ) );
258 git_repository_free( repo );
259 return true;
260 }
261
262
264 {
265 git_clone_options opts;
266 git_clone_init_options( &opts, GIT_CLONE_OPTIONS_VERSION );
267 opts.bare = 1;
268
269 wxString sourceUrl = wxS( "file://" ) + m_repoPath;
270
271 git_repository* bare = nullptr;
272 int rc = git_clone( &bare, sourceUrl.ToUTF8().data(), m_remotePath.ToUTF8().data(), &opts );
273
274 if( bare )
275 git_repository_free( bare );
276
277 return rc == 0;
278 }
279
280
282 wxString m_tempBase;
283 wxString m_repoPath;
284 wxString m_remotePath;
286
287 static long s_counter;
288};
289
291
292
293BOOST_FIXTURE_TEST_SUITE( LibgitBackend, GIT_BACKEND_FIXTURE )
294
295
296
301BOOST_AUTO_TEST_CASE( GetUpstreamShorthand_NoUpstreamFallsBackToRemoteSlashBranch )
302{
303 BOOST_TEST_REQUIRE( ready() );
304
305 git_repository* repo = openRepo();
306 BOOST_TEST_REQUIRE( repo );
307
308 // No upstream config, no remote-tracking ref. Should still produce a useful
309 // shorthand by combining the default remote name with the current branch.
310 clearUpstreamConfig( repo, wxT( "master" ) );
311
312 KIGIT_COMMON common( repo );
313 BOOST_CHECK_EQUAL( common.GetUpstreamShorthand(), wxString( "origin/master" ) );
314
315 git_repository_free( repo );
316}
317
318
327BOOST_AUTO_TEST_CASE( GetDifferentFiles_SameTreeAmendHidesAheadFiles )
328{
329 BOOST_TEST_REQUIRE( ready() );
330
331 git_repository* repo = openRepo();
332 BOOST_TEST_REQUIRE( repo );
333
334 // Add a second commit so the merge-base in GetDifferentFiles isn't the root.
335 git_oid c1 = createCommit( repo, wxT( "file.txt" ), wxT( "edited\n" ), wxT( "Edit file" ) );
336
337 // Simulate "pushed": create the remote-tracking ref pointing at C1.
338 git_reference* trackingRef = nullptr;
339 git_reference_create( &trackingRef, repo, "refs/remotes/origin/master", &c1, 1, nullptr );
340
341 if( trackingRef )
342 git_reference_free( trackingRef );
343
344 addOrigin( repo );
345 setUpstreamConfig( repo, wxT( "master" ) );
346
347 // Amend C1 message-only. Same tree, different commit OID.
348 git_reference* headRef = nullptr;
349 git_repository_head( &headRef, repo );
350
351 git_commit* headCommit = nullptr;
352 git_reference_peel( (git_object**) &headCommit, headRef, GIT_OBJECT_COMMIT );
353
354 git_tree* tree = nullptr;
355 git_commit_tree( &tree, headCommit );
356
357 git_signature* sig = nullptr;
358 git_signature_now( &sig, TEST_AUTHOR_NAME, TEST_AUTHOR_EMAIL );
359
360 git_oid amendedOid;
361 int rc = git_commit_amend( &amendedOid, headCommit, "HEAD", sig, sig, nullptr, "Edit file (message-only amend)",
362 tree );
363 BOOST_TEST_REQUIRE( rc == 0 );
364
365 git_tree_free( tree );
366 git_commit_free( headCommit );
367 git_reference_free( headRef );
368 git_signature_free( sig );
369
370 // Now: HEAD points at C1', upstream still at C1, both have the same tree.
371 // Pre-filter: AHEAD = { file.txt } (touched in C1 relative to root).
372 // Post-filter (the fix under test): AHEAD = {} because HEAD's blob == upstream's blob.
373 KIGIT_COMMON common( repo );
374 auto [ahead, behind] = common.GetDifferentFiles();
375
376 BOOST_CHECK_MESSAGE( ahead.empty(), "Message-only amend should not report any AHEAD files; got "
377 + std::to_string( ahead.size() ) );
378 BOOST_CHECK_MESSAGE( behind.empty(), "Message-only amend should not report any BEHIND files; got "
379 + std::to_string( behind.size() ) );
380
381 git_repository_free( repo );
382}
383
384
390BOOST_AUTO_TEST_CASE( GetDifferentFiles_DifferentTreeAmendKeepsAheadFile )
391{
392 BOOST_TEST_REQUIRE( ready() );
393
394 git_repository* repo = openRepo();
395 BOOST_TEST_REQUIRE( repo );
396
397 git_oid c1 = createCommit( repo, wxT( "file.txt" ), wxT( "edited\n" ), wxT( "Edit file" ) );
398
399 git_reference* trackingRef = nullptr;
400 git_reference_create( &trackingRef, repo, "refs/remotes/origin/master", &c1, 1, nullptr );
401
402 if( trackingRef )
403 git_reference_free( trackingRef );
404
405 addOrigin( repo );
406 setUpstreamConfig( repo, wxT( "master" ) );
407
408 // Make a new commit on top with different content (replacing C1 effectively).
409 // Use the same path so the file remains "the same file" to the diff.
410 createCommit( repo, wxT( "file.txt" ), wxT( "edited again\n" ), wxT( "Re-edit file" ) );
411
412 KIGIT_COMMON common( repo );
413 auto [ahead, behind] = common.GetDifferentFiles();
414
415 BOOST_CHECK_MESSAGE( ahead.count( wxT( "file.txt" ) ) == 1,
416 "AHEAD should contain file.txt when content differs from upstream" );
417
418 git_repository_free( repo );
419}
420
421
429BOOST_AUTO_TEST_CASE( PerformPull_NoUpstreamConfig_FallbackSucceedsAndWritesUpstream )
430{
431 BOOST_TEST_REQUIRE( ready() );
432
433 git_repository* repo = openRepo();
434 BOOST_TEST_REQUIRE( repo );
435
436 addOrigin( repo );
437 clearUpstreamConfig( repo, wxT( "master" ) );
438
439 KIGIT_COMMON common( repo );
440 GIT_PULL_HANDLER handler( &common );
441
442 PullResult result = handler.PerformPull();
443
444 // Without the fallback, this would be PullResult::Error with "Could not lookup commit".
447 "Pull without upstream config should succeed via fallback; got "
448 + std::to_string( static_cast<int>( result ) ) + ", err='"
449 + handler.GetErrorString().ToStdString() + "'" );
450
451 // The fallback also wires up tracking so subsequent pulls take the normal path.
452 BOOST_CHECK_EQUAL( readConfig( repo, wxT( "branch.master.remote" ) ), wxString( "origin" ) );
453 BOOST_CHECK_EQUAL( readConfig( repo, wxT( "branch.master.merge" ) ), wxString( "refs/heads/master" ) );
454
455 git_repository_free( repo );
456}
457
458
464BOOST_AUTO_TEST_CASE( Push_FirstPushSetsUpstreamTracking )
465{
466 BOOST_TEST_REQUIRE( ready() );
467
468 git_repository* repo = openRepo();
469 BOOST_TEST_REQUIRE( repo );
470
471 // Add a new local commit so there is something to actually push beyond the
472 // initial state shared with the bare remote.
473 createCommit( repo, wxT( "file.txt" ), wxT( "second\n" ), wxT( "Second commit" ) );
474
475 addOrigin( repo );
476 clearUpstreamConfig( repo, wxT( "master" ) );
477
478 KIGIT_COMMON common( repo );
479 GIT_PUSH_HANDLER handler( &common );
480
481 PushResult result = handler.PerformPush();
482 BOOST_TEST_REQUIRE( static_cast<int>( result ) == static_cast<int>( PushResult::Success ) );
483
484 BOOST_CHECK_EQUAL( readConfig( repo, wxT( "branch.master.remote" ) ), wxString( "origin" ) );
485 BOOST_CHECK_EQUAL( readConfig( repo, wxT( "branch.master.merge" ) ), wxString( "refs/heads/master" ) );
486
487 git_repository_free( repo );
488}
489
490
495BOOST_AUTO_TEST_CASE( Amend_MessageOnlyRewritesHeadKeepsTree )
496{
497 BOOST_TEST_REQUIRE( ready() );
498
499 git_repository* repo = openRepo();
500 BOOST_TEST_REQUIRE( repo );
501
502 git_reference* headRefBefore = nullptr;
503 git_repository_head( &headRefBefore, repo );
504 git_oid oidBefore = *git_reference_target( headRefBefore );
505
506 git_commit* commitBefore = nullptr;
507 git_reference_peel( (git_object**) &commitBefore, headRefBefore, GIT_OBJECT_COMMIT );
508 git_oid treeBefore = *git_commit_tree_id( commitBefore );
509
510 git_commit_free( commitBefore );
511 git_reference_free( headRefBefore );
512
513 GIT_COMMIT_HANDLER handler( repo );
514 CommitResult result = handler.PerformAmend( {}, wxT( "Amended message" ), TEST_AUTHOR_NAME, TEST_AUTHOR_EMAIL );
515 BOOST_CHECK_EQUAL( static_cast<int>( result ), static_cast<int>( CommitResult::Success ) );
516
517 git_reference* headRefAfter = nullptr;
518 git_repository_head( &headRefAfter, repo );
519 git_oid oidAfter = *git_reference_target( headRefAfter );
520
521 git_commit* commitAfter = nullptr;
522 git_reference_peel( (git_object**) &commitAfter, headRefAfter, GIT_OBJECT_COMMIT );
523 git_oid treeAfter = *git_commit_tree_id( commitAfter );
524
525 BOOST_CHECK( !git_oid_equal( &oidBefore, &oidAfter ) );
526 BOOST_CHECK( git_oid_equal( &treeBefore, &treeAfter ) );
527
528 wxString amendedMsg = wxString::FromUTF8( git_commit_message( commitAfter ) );
529 BOOST_CHECK_EQUAL( amendedMsg.Trim(), wxString( "Amended message" ) );
530
531 git_commit_free( commitAfter );
532 git_reference_free( headRefAfter );
533 git_repository_free( repo );
534}
535
536
540BOOST_AUTO_TEST_CASE( Amend_StagedFileChangesTreeAndKeepsParent )
541{
542 BOOST_TEST_REQUIRE( ready() );
543
544 git_repository* repo = openRepo();
545 BOOST_TEST_REQUIRE( repo );
546
547 git_reference* headRefBefore = nullptr;
548 git_repository_head( &headRefBefore, repo );
549
550 git_commit* commitBefore = nullptr;
551 git_reference_peel( (git_object**) &commitBefore, headRefBefore, GIT_OBJECT_COMMIT );
552 git_oid treeBefore = *git_commit_tree_id( commitBefore );
553 unsigned int parentCountBefore = git_commit_parentcount( commitBefore );
554
555 git_commit_free( commitBefore );
556 git_reference_free( headRefBefore );
557
558 // Add a new file to the working tree so the amend has something to stage.
559 wxString newFile = repoPath() + wxFileName::GetPathSeparator() + wxT( "new.txt" );
560 {
561 std::ofstream f( newFile.ToStdString() );
562 f << "new content\n";
563 }
564
565 GIT_COMMIT_HANDLER handler( repo );
566 CommitResult result = handler.PerformAmend( { wxT( "new.txt" ) }, wxT( "Amended with file" ), TEST_AUTHOR_NAME,
568 BOOST_CHECK_EQUAL( static_cast<int>( result ), static_cast<int>( CommitResult::Success ) );
569
570 git_reference* headRefAfter = nullptr;
571 git_repository_head( &headRefAfter, repo );
572
573 git_commit* commitAfter = nullptr;
574 git_reference_peel( (git_object**) &commitAfter, headRefAfter, GIT_OBJECT_COMMIT );
575 git_oid treeAfter = *git_commit_tree_id( commitAfter );
576 unsigned int parentCountAfter = git_commit_parentcount( commitAfter );
577
578 BOOST_CHECK( !git_oid_equal( &treeBefore, &treeAfter ) );
579 BOOST_CHECK_EQUAL( parentCountAfter, parentCountBefore );
580
581 // new.txt should now be in HEAD's tree.
582 git_tree* treeObj = nullptr;
583 git_commit_tree( &treeObj, commitAfter );
584
585 const git_tree_entry* entry = git_tree_entry_byname( treeObj, "new.txt" );
586 BOOST_CHECK( entry != nullptr );
587
588 git_tree_free( treeObj );
589 git_commit_free( commitAfter );
590 git_reference_free( headRefAfter );
591 git_repository_free( repo );
592}
593
594
599BOOST_AUTO_TEST_CASE( Amend_UnbornBranchReturnsError )
600{
601 BOOST_TEST_REQUIRE( ready() );
602
603 wxString unbornDir = repoPath() + wxT( "_unborn" );
604 wxFileName::Mkdir( unbornDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
605
606 git_repository* unbornRepo = nullptr;
607 BOOST_TEST_REQUIRE( git_repository_init( &unbornRepo, unbornDir.ToUTF8().data(), 0 ) == 0 );
608
609 GIT_COMMIT_HANDLER handler( unbornRepo );
610 CommitResult result = handler.PerformAmend( {}, wxT( "should fail" ), TEST_AUTHOR_NAME, TEST_AUTHOR_EMAIL );
611
612 BOOST_CHECK_EQUAL( static_cast<int>( result ), static_cast<int>( CommitResult::Error ) );
613 BOOST_CHECK( !handler.GetErrorString().IsEmpty() );
614
615 git_repository_free( unbornRepo );
616
617 if( wxFileName::DirExists( unbornDir ) )
618 wxFileName::Rmdir( unbornDir, wxPATH_RMDIR_RECURSIVE );
619}
620
621
626BOOST_AUTO_TEST_CASE( InitializeRepository_SeedsGitignoreWithKicadEntries )
627{
628 BOOST_TEST_REQUIRE( ready() );
629
630 wxString freshDir = repoPath() + wxT( "_fresh" );
631 wxFileName::Mkdir( freshDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
632
633 KIGIT_COMMON common( nullptr );
634 GIT_INIT_HANDLER handler( &common );
635
636 InitResult result = handler.InitializeRepository( freshDir );
637 BOOST_CHECK_EQUAL( static_cast<int>( result ), static_cast<int>( InitResult::Success ) );
638
639 wxFileName gitignoreFile( freshDir, wxT( ".gitignore" ) );
640 BOOST_TEST_REQUIRE( gitignoreFile.FileExists() );
641
642 wxString contents;
643 {
644 wxFFile f( gitignoreFile.GetFullPath(), wxT( "r" ) );
645 f.ReadAll( &contents );
646 }
647
648 BOOST_CHECK( contents.Contains( wxT( ".history/" ) ) );
649 BOOST_CHECK( contents.Contains( wxT( "*-backups/" ) ) );
650 BOOST_CHECK( contents.Contains( wxT( "_autosave-*" ) ) );
651 BOOST_CHECK( contents.Contains( wxT( "fp-info-cache" ) ) );
652 BOOST_CHECK( contents.Contains( wxT( "~*.lck" ) ) );
653
654 if( wxFileName::DirExists( freshDir ) )
655 wxFileName::Rmdir( freshDir, wxPATH_RMDIR_RECURSIVE );
656}
657
658
664BOOST_AUTO_TEST_CASE( InitializeRepository_GitignoreDoesNotDuplicateEntries )
665{
666 BOOST_TEST_REQUIRE( ready() );
667
668 wxString freshDir = repoPath() + wxT( "_dedup" );
669 wxFileName::Mkdir( freshDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
670
671 wxFileName gitignoreFile( freshDir, wxT( ".gitignore" ) );
672 {
673 wxFFile f( gitignoreFile.GetFullPath(), wxT( "w" ) );
674 f.Write( wxT( "# my custom ignores\n.history/\nbuild/\n" ) );
675 }
676
677 KIGIT_COMMON common( nullptr );
678 GIT_INIT_HANDLER handler( &common );
679
680 InitResult result = handler.InitializeRepository( freshDir );
681 BOOST_CHECK_EQUAL( static_cast<int>( result ), static_cast<int>( InitResult::Success ) );
682
683 wxTextFile tf;
684 BOOST_TEST_REQUIRE( tf.Open( gitignoreFile.GetFullPath() ) );
685
686 int historyCount = 0;
687 int buildCount = 0;
688 int fpInfoCount = 0;
689
690 for( size_t i = 0; i < tf.GetLineCount(); ++i )
691 {
692 wxString line = tf.GetLine( i );
693 line.Trim().Trim( false );
694
695 if( line == wxT( ".history/" ) )
696 historyCount++;
697 else if( line == wxT( "build/" ) )
698 buildCount++;
699 else if( line == wxT( "fp-info-cache" ) )
700 fpInfoCount++;
701 }
702
703 BOOST_CHECK_EQUAL( historyCount, 1 ); // was already present; not duplicated
704 BOOST_CHECK_EQUAL( buildCount, 1 ); // user's custom entry preserved
705 BOOST_CHECK_EQUAL( fpInfoCount, 1 ); // KiCad default that was missing got appended
706
707 if( wxFileName::DirExists( freshDir ) )
708 wxFileName::Rmdir( freshDir, wxPATH_RMDIR_RECURSIVE );
709}
710
711
int index
wxString GetErrorString() const
CommitResult PerformAmend(const std::vector< wxString > &aFiles, const wxString &aMessage, const wxString &aAuthorName, const wxString &aAuthorEmail)
InitResult InitializeRepository(const wxString &aPath)
Initialize a new git repository in the specified directory.
PullResult PerformPull()
PushResult PerformPush(bool aForce=false)
std::pair< std::set< wxString >, std::set< wxString > > GetDifferentFiles() const
Return a pair of sets of files that differ locally from the remote repository The first set is files ...
wxString GetUpstreamShorthand() const
Returns the upstream shorthand for the current branch (e.g.
wxString GetErrorString()
void SetGitBackend(GIT_BACKEND *aBackend)
CommitResult
Definition git_backend.h:52
InitResult
PullResult
PushResult
Build a temp directory tree containing a local working repo with one commit and a bare "remote" repo ...
const wxString & repoPath() const
wxString readConfig(git_repository *aRepo, const wxString &aKey)
Read a string value from the repo config, or "" if absent.
git_oid createCommit(git_repository *aRepo, const wxString &aFile, const wxString &aContent, const wxString &aMessage)
Create a new commit on the current branch from a single (path, content) pair.
git_repository * openRepo() const
Open the working repo. Caller frees with git_repository_free.
bool clearUpstreamConfig(git_repository *aRepo, const wxString &aBranch)
Drop branch.
bool addOrigin(git_repository *aRepo)
Add an origin remote pointing at the bare remote on disk.
const wxString & remotePath() const
bool setUpstreamConfig(git_repository *aRepo, const wxString &aBranch)
Set branch.<aBranch>.merge = refs/heads/<aBranch>, branch.<aBranch>.remote = origin.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
static const char * TEST_AUTHOR_NAME
static const char * TEST_AUTHOR_EMAIL
BOOST_AUTO_TEST_SUITE_END()
static const char * TEST_AUTHOR_NAME
static const char * TEST_AUTHOR_EMAIL
BOOST_AUTO_TEST_CASE(GetUpstreamShorthand_NoUpstreamFallsBackToRemoteSlashBranch)
When the local branch has no upstream configured, GetUpstreamShorthand must synthesise <remote>/<bran...
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")