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, you may find one here:
18 * http://www.gnu.org/licenses/gpl-3.0.html
19 */
20
27
29
30#include <git/git_backend.h>
31#include <git/libgit_backend.h>
37
38#include <git2.h>
39
40#include <fstream>
41
42#include <wx/ffile.h>
43#include <wx/filename.h>
44#include <wx/textfile.h>
45#include <wx/utils.h>
46
47
48static const char* TEST_AUTHOR_NAME = "Test Author";
50
51
58{
60 {
62 m_backend->Init();
64
65 m_tempBase = wxFileName::GetTempDir() + wxFileName::GetPathSeparator()
66 + wxString::Format( "kicad_libgit_backend_test_%ld_%ld", wxGetProcessId(), s_counter++ );
67 wxFileName::Mkdir( m_tempBase, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
68
69 m_repoPath = m_tempBase + wxFileName::GetPathSeparator() + wxT( "repo" );
70 m_remotePath = m_tempBase + wxFileName::GetPathSeparator() + wxT( "remote.git" );
71
72 wxFileName::Mkdir( m_repoPath, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
73
75 }
76
77
79 {
80 if( wxFileName::DirExists( m_tempBase ) )
81 wxFileName::Rmdir( m_tempBase, wxPATH_RMDIR_RECURSIVE );
82
83 SetGitBackend( nullptr );
84 m_backend->Shutdown();
85 delete m_backend;
86 }
87
88
89 bool ready() const { return m_ready; }
90 const wxString& repoPath() const { return m_repoPath; }
91 const wxString& remotePath() const { return m_remotePath; }
92
93
95 git_repository* openRepo() const
96 {
97 git_repository* repo = nullptr;
98 git_repository_open( &repo, m_repoPath.ToUTF8().data() );
99 return repo;
100 }
101
102
104 git_oid createCommit( git_repository* aRepo, const wxString& aFile, const wxString& aContent,
105 const wxString& aMessage )
106 {
107 git_oid commitOid = {};
108
109 wxString filePath = m_repoPath + wxFileName::GetPathSeparator() + aFile;
110 {
111 std::ofstream f( filePath.ToStdString() );
112 f << aContent.ToStdString();
113 }
114
115 git_index* index = nullptr;
116
117 if( git_repository_index( &index, aRepo ) != 0 )
118 return commitOid;
119
120 git_index_add_bypath( index, aFile.ToUTF8().data() );
121 git_index_write( index );
122
123 git_oid treeOid;
124 git_index_write_tree( &treeOid, index );
125 git_index_free( index );
126
127 git_tree* tree = nullptr;
128
129 if( git_tree_lookup( &tree, aRepo, &treeOid ) != 0 )
130 return commitOid;
131
132 git_signature* sig = nullptr;
133 git_signature_now( &sig, TEST_AUTHOR_NAME, TEST_AUTHOR_EMAIL );
134
135 // Parent is HEAD if it resolves, otherwise this becomes the root commit.
136 git_commit* parent = nullptr;
137 git_reference* headRef = nullptr;
138
139 if( git_repository_head( &headRef, aRepo ) == 0 )
140 {
141 git_reference_peel( (git_object**) &parent, headRef, GIT_OBJECT_COMMIT );
142 git_reference_free( headRef );
143 }
144
145 const git_commit* parents[1] = { parent };
146 const git_commit** parentsPtr = parent ? parents : nullptr;
147 size_t parentsCount = parent ? 1 : 0;
148
149 git_commit_create( &commitOid, aRepo, "HEAD", sig, sig, nullptr, aMessage.ToUTF8().data(), tree, parentsCount,
150 parentsPtr );
151
152 if( parent )
153 git_commit_free( parent );
154
155 git_signature_free( sig );
156 git_tree_free( tree );
157
158 return commitOid;
159 }
160
161
163 bool addOrigin( git_repository* aRepo )
164 {
165 wxString fileUrl = wxS( "file://" ) + m_remotePath;
166 git_remote* remote = nullptr;
167 int rc = git_remote_create( &remote, aRepo, "origin", fileUrl.ToUTF8().data() );
168
169 if( remote )
170 git_remote_free( remote );
171
172 return rc == 0;
173 }
174
175
178 bool clearUpstreamConfig( git_repository* aRepo, const wxString& aBranch )
179 {
180 git_config* cfg = nullptr;
181
182 if( git_repository_config( &cfg, aRepo ) != 0 )
183 return false;
184
185 wxString remoteKey = wxString::Format( "branch.%s.remote", aBranch );
186 wxString mergeKey = wxString::Format( "branch.%s.merge", aBranch );
187
188 git_config_delete_entry( cfg, remoteKey.ToUTF8().data() );
189 git_config_delete_entry( cfg, mergeKey.ToUTF8().data() );
190
191 git_config_free( cfg );
192 return true;
193 }
194
195
197 bool setUpstreamConfig( git_repository* aRepo, const wxString& aBranch )
198 {
199 git_config* cfg = nullptr;
200
201 if( git_repository_config( &cfg, aRepo ) != 0 )
202 return false;
203
204 wxString remoteKey = wxString::Format( "branch.%s.remote", aBranch );
205 wxString mergeKey = wxString::Format( "branch.%s.merge", aBranch );
206 wxString mergeVal = wxString::Format( "refs/heads/%s", aBranch );
207
208 git_config_set_string( cfg, remoteKey.ToUTF8().data(), "origin" );
209 git_config_set_string( cfg, mergeKey.ToUTF8().data(), mergeVal.ToUTF8().data() );
210 git_config_free( cfg );
211 return true;
212 }
213
214
216 wxString readConfig( git_repository* aRepo, const wxString& aKey )
217 {
218 git_config* cfg = nullptr;
219
220 if( git_repository_config( &cfg, aRepo ) != 0 )
221 return wxEmptyString;
222
223 git_buf buf = { nullptr, 0, 0 };
224 wxString result;
225
226 if( git_config_get_string_buf( &buf, cfg, aKey.ToUTF8().data() ) == 0 && buf.ptr )
227 result = wxString::FromUTF8( buf.ptr );
228
229 git_buf_dispose( &buf );
230 git_config_free( cfg );
231 return result;
232 }
233
234
235private:
237 {
238 git_repository* repo = nullptr;
239
240 if( git_repository_init( &repo, m_repoPath.ToUTF8().data(), 0 ) != 0 )
241 return false;
242
243 git_config* cfg = nullptr;
244
245 if( git_repository_config( &cfg, repo ) == 0 )
246 {
247 git_config_set_string( cfg, "user.name", TEST_AUTHOR_NAME );
248 git_config_set_string( cfg, "user.email", TEST_AUTHOR_EMAIL );
249 git_config_free( cfg );
250 }
251
252 createCommit( repo, wxT( "file.txt" ), wxT( "initial\n" ), wxT( "Initial commit" ) );
253 git_repository_free( repo );
254 return true;
255 }
256
257
259 {
260 git_clone_options opts;
261 git_clone_init_options( &opts, GIT_CLONE_OPTIONS_VERSION );
262 opts.bare = 1;
263
264 wxString sourceUrl = wxS( "file://" ) + m_repoPath;
265
266 git_repository* bare = nullptr;
267 int rc = git_clone( &bare, sourceUrl.ToUTF8().data(), m_remotePath.ToUTF8().data(), &opts );
268
269 if( bare )
270 git_repository_free( bare );
271
272 return rc == 0;
273 }
274
275
277 wxString m_tempBase;
278 wxString m_repoPath;
279 wxString m_remotePath;
281
282 static long s_counter;
283};
284
286
287
288BOOST_FIXTURE_TEST_SUITE( LibgitBackend, GIT_BACKEND_FIXTURE )
289
290
291
296BOOST_AUTO_TEST_CASE( GetUpstreamShorthand_NoUpstreamFallsBackToRemoteSlashBranch )
297{
298 BOOST_TEST_REQUIRE( ready() );
299
300 git_repository* repo = openRepo();
301 BOOST_TEST_REQUIRE( repo );
302
303 // No upstream config, no remote-tracking ref. Should still produce a useful
304 // shorthand by combining the default remote name with the current branch.
305 clearUpstreamConfig( repo, wxT( "master" ) );
306
307 KIGIT_COMMON common( repo );
308 BOOST_CHECK_EQUAL( common.GetUpstreamShorthand(), wxString( "origin/master" ) );
309
310 git_repository_free( repo );
311}
312
313
322BOOST_AUTO_TEST_CASE( GetDifferentFiles_SameTreeAmendHidesAheadFiles )
323{
324 BOOST_TEST_REQUIRE( ready() );
325
326 git_repository* repo = openRepo();
327 BOOST_TEST_REQUIRE( repo );
328
329 // Add a second commit so the merge-base in GetDifferentFiles isn't the root.
330 git_oid c1 = createCommit( repo, wxT( "file.txt" ), wxT( "edited\n" ), wxT( "Edit file" ) );
331
332 // Simulate "pushed": create the remote-tracking ref pointing at C1.
333 git_reference* trackingRef = nullptr;
334 git_reference_create( &trackingRef, repo, "refs/remotes/origin/master", &c1, 1, nullptr );
335
336 if( trackingRef )
337 git_reference_free( trackingRef );
338
339 addOrigin( repo );
340 setUpstreamConfig( repo, wxT( "master" ) );
341
342 // Amend C1 message-only. Same tree, different commit OID.
343 git_reference* headRef = nullptr;
344 git_repository_head( &headRef, repo );
345
346 git_commit* headCommit = nullptr;
347 git_reference_peel( (git_object**) &headCommit, headRef, GIT_OBJECT_COMMIT );
348
349 git_tree* tree = nullptr;
350 git_commit_tree( &tree, headCommit );
351
352 git_signature* sig = nullptr;
353 git_signature_now( &sig, TEST_AUTHOR_NAME, TEST_AUTHOR_EMAIL );
354
355 git_oid amendedOid;
356 int rc = git_commit_amend( &amendedOid, headCommit, "HEAD", sig, sig, nullptr, "Edit file (message-only amend)",
357 tree );
358 BOOST_TEST_REQUIRE( rc == 0 );
359
360 git_tree_free( tree );
361 git_commit_free( headCommit );
362 git_reference_free( headRef );
363 git_signature_free( sig );
364
365 // Now: HEAD points at C1', upstream still at C1, both have the same tree.
366 // Pre-filter: AHEAD = { file.txt } (touched in C1 relative to root).
367 // Post-filter (the fix under test): AHEAD = {} because HEAD's blob == upstream's blob.
368 KIGIT_COMMON common( repo );
369 auto [ahead, behind] = common.GetDifferentFiles();
370
371 BOOST_CHECK_MESSAGE( ahead.empty(), "Message-only amend should not report any AHEAD files; got "
372 + std::to_string( ahead.size() ) );
373 BOOST_CHECK_MESSAGE( behind.empty(), "Message-only amend should not report any BEHIND files; got "
374 + std::to_string( behind.size() ) );
375
376 git_repository_free( repo );
377}
378
379
385BOOST_AUTO_TEST_CASE( GetDifferentFiles_DifferentTreeAmendKeepsAheadFile )
386{
387 BOOST_TEST_REQUIRE( ready() );
388
389 git_repository* repo = openRepo();
390 BOOST_TEST_REQUIRE( repo );
391
392 git_oid c1 = createCommit( repo, wxT( "file.txt" ), wxT( "edited\n" ), wxT( "Edit file" ) );
393
394 git_reference* trackingRef = nullptr;
395 git_reference_create( &trackingRef, repo, "refs/remotes/origin/master", &c1, 1, nullptr );
396
397 if( trackingRef )
398 git_reference_free( trackingRef );
399
400 addOrigin( repo );
401 setUpstreamConfig( repo, wxT( "master" ) );
402
403 // Make a new commit on top with different content (replacing C1 effectively).
404 // Use the same path so the file remains "the same file" to the diff.
405 createCommit( repo, wxT( "file.txt" ), wxT( "edited again\n" ), wxT( "Re-edit file" ) );
406
407 KIGIT_COMMON common( repo );
408 auto [ahead, behind] = common.GetDifferentFiles();
409
410 BOOST_CHECK_MESSAGE( ahead.count( wxT( "file.txt" ) ) == 1,
411 "AHEAD should contain file.txt when content differs from upstream" );
412
413 git_repository_free( repo );
414}
415
416
424BOOST_AUTO_TEST_CASE( PerformPull_NoUpstreamConfig_FallbackSucceedsAndWritesUpstream )
425{
426 BOOST_TEST_REQUIRE( ready() );
427
428 git_repository* repo = openRepo();
429 BOOST_TEST_REQUIRE( repo );
430
431 addOrigin( repo );
432 clearUpstreamConfig( repo, wxT( "master" ) );
433
434 KIGIT_COMMON common( repo );
435 GIT_PULL_HANDLER handler( &common );
436
437 PullResult result = handler.PerformPull();
438
439 // Without the fallback, this would be PullResult::Error with "Could not lookup commit".
442 "Pull without upstream config should succeed via fallback; got "
443 + std::to_string( static_cast<int>( result ) ) + ", err='"
444 + handler.GetErrorString().ToStdString() + "'" );
445
446 // The fallback also wires up tracking so subsequent pulls take the normal path.
447 BOOST_CHECK_EQUAL( readConfig( repo, wxT( "branch.master.remote" ) ), wxString( "origin" ) );
448 BOOST_CHECK_EQUAL( readConfig( repo, wxT( "branch.master.merge" ) ), wxString( "refs/heads/master" ) );
449
450 git_repository_free( repo );
451}
452
453
459BOOST_AUTO_TEST_CASE( Push_FirstPushSetsUpstreamTracking )
460{
461 BOOST_TEST_REQUIRE( ready() );
462
463 git_repository* repo = openRepo();
464 BOOST_TEST_REQUIRE( repo );
465
466 // Add a new local commit so there is something to actually push beyond the
467 // initial state shared with the bare remote.
468 createCommit( repo, wxT( "file.txt" ), wxT( "second\n" ), wxT( "Second commit" ) );
469
470 addOrigin( repo );
471 clearUpstreamConfig( repo, wxT( "master" ) );
472
473 KIGIT_COMMON common( repo );
474 GIT_PUSH_HANDLER handler( &common );
475
476 PushResult result = handler.PerformPush();
477 BOOST_TEST_REQUIRE( static_cast<int>( result ) == static_cast<int>( PushResult::Success ) );
478
479 BOOST_CHECK_EQUAL( readConfig( repo, wxT( "branch.master.remote" ) ), wxString( "origin" ) );
480 BOOST_CHECK_EQUAL( readConfig( repo, wxT( "branch.master.merge" ) ), wxString( "refs/heads/master" ) );
481
482 git_repository_free( repo );
483}
484
485
490BOOST_AUTO_TEST_CASE( Amend_MessageOnlyRewritesHeadKeepsTree )
491{
492 BOOST_TEST_REQUIRE( ready() );
493
494 git_repository* repo = openRepo();
495 BOOST_TEST_REQUIRE( repo );
496
497 git_reference* headRefBefore = nullptr;
498 git_repository_head( &headRefBefore, repo );
499 git_oid oidBefore = *git_reference_target( headRefBefore );
500
501 git_commit* commitBefore = nullptr;
502 git_reference_peel( (git_object**) &commitBefore, headRefBefore, GIT_OBJECT_COMMIT );
503 git_oid treeBefore = *git_commit_tree_id( commitBefore );
504
505 git_commit_free( commitBefore );
506 git_reference_free( headRefBefore );
507
508 GIT_COMMIT_HANDLER handler( repo );
509 CommitResult result = handler.PerformAmend( {}, wxT( "Amended message" ), TEST_AUTHOR_NAME, TEST_AUTHOR_EMAIL );
510 BOOST_CHECK_EQUAL( static_cast<int>( result ), static_cast<int>( CommitResult::Success ) );
511
512 git_reference* headRefAfter = nullptr;
513 git_repository_head( &headRefAfter, repo );
514 git_oid oidAfter = *git_reference_target( headRefAfter );
515
516 git_commit* commitAfter = nullptr;
517 git_reference_peel( (git_object**) &commitAfter, headRefAfter, GIT_OBJECT_COMMIT );
518 git_oid treeAfter = *git_commit_tree_id( commitAfter );
519
520 BOOST_CHECK( !git_oid_equal( &oidBefore, &oidAfter ) );
521 BOOST_CHECK( git_oid_equal( &treeBefore, &treeAfter ) );
522
523 wxString amendedMsg = wxString::FromUTF8( git_commit_message( commitAfter ) );
524 BOOST_CHECK_EQUAL( amendedMsg.Trim(), wxString( "Amended message" ) );
525
526 git_commit_free( commitAfter );
527 git_reference_free( headRefAfter );
528 git_repository_free( repo );
529}
530
531
535BOOST_AUTO_TEST_CASE( Amend_StagedFileChangesTreeAndKeepsParent )
536{
537 BOOST_TEST_REQUIRE( ready() );
538
539 git_repository* repo = openRepo();
540 BOOST_TEST_REQUIRE( repo );
541
542 git_reference* headRefBefore = nullptr;
543 git_repository_head( &headRefBefore, repo );
544
545 git_commit* commitBefore = nullptr;
546 git_reference_peel( (git_object**) &commitBefore, headRefBefore, GIT_OBJECT_COMMIT );
547 git_oid treeBefore = *git_commit_tree_id( commitBefore );
548 unsigned int parentCountBefore = git_commit_parentcount( commitBefore );
549
550 git_commit_free( commitBefore );
551 git_reference_free( headRefBefore );
552
553 // Add a new file to the working tree so the amend has something to stage.
554 wxString newFile = repoPath() + wxFileName::GetPathSeparator() + wxT( "new.txt" );
555 {
556 std::ofstream f( newFile.ToStdString() );
557 f << "new content\n";
558 }
559
560 GIT_COMMIT_HANDLER handler( repo );
561 CommitResult result = handler.PerformAmend( { wxT( "new.txt" ) }, wxT( "Amended with file" ), TEST_AUTHOR_NAME,
563 BOOST_CHECK_EQUAL( static_cast<int>( result ), static_cast<int>( CommitResult::Success ) );
564
565 git_reference* headRefAfter = nullptr;
566 git_repository_head( &headRefAfter, repo );
567
568 git_commit* commitAfter = nullptr;
569 git_reference_peel( (git_object**) &commitAfter, headRefAfter, GIT_OBJECT_COMMIT );
570 git_oid treeAfter = *git_commit_tree_id( commitAfter );
571 unsigned int parentCountAfter = git_commit_parentcount( commitAfter );
572
573 BOOST_CHECK( !git_oid_equal( &treeBefore, &treeAfter ) );
574 BOOST_CHECK_EQUAL( parentCountAfter, parentCountBefore );
575
576 // new.txt should now be in HEAD's tree.
577 git_tree* treeObj = nullptr;
578 git_commit_tree( &treeObj, commitAfter );
579
580 const git_tree_entry* entry = git_tree_entry_byname( treeObj, "new.txt" );
581 BOOST_CHECK( entry != nullptr );
582
583 git_tree_free( treeObj );
584 git_commit_free( commitAfter );
585 git_reference_free( headRefAfter );
586 git_repository_free( repo );
587}
588
589
594BOOST_AUTO_TEST_CASE( Amend_UnbornBranchReturnsError )
595{
596 BOOST_TEST_REQUIRE( ready() );
597
598 wxString unbornDir = repoPath() + wxT( "_unborn" );
599 wxFileName::Mkdir( unbornDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
600
601 git_repository* unbornRepo = nullptr;
602 BOOST_TEST_REQUIRE( git_repository_init( &unbornRepo, unbornDir.ToUTF8().data(), 0 ) == 0 );
603
604 GIT_COMMIT_HANDLER handler( unbornRepo );
605 CommitResult result = handler.PerformAmend( {}, wxT( "should fail" ), TEST_AUTHOR_NAME, TEST_AUTHOR_EMAIL );
606
607 BOOST_CHECK_EQUAL( static_cast<int>( result ), static_cast<int>( CommitResult::Error ) );
608 BOOST_CHECK( !handler.GetErrorString().IsEmpty() );
609
610 git_repository_free( unbornRepo );
611
612 if( wxFileName::DirExists( unbornDir ) )
613 wxFileName::Rmdir( unbornDir, wxPATH_RMDIR_RECURSIVE );
614}
615
616
621BOOST_AUTO_TEST_CASE( InitializeRepository_SeedsGitignoreWithKicadEntries )
622{
623 BOOST_TEST_REQUIRE( ready() );
624
625 wxString freshDir = repoPath() + wxT( "_fresh" );
626 wxFileName::Mkdir( freshDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
627
628 KIGIT_COMMON common( nullptr );
629 GIT_INIT_HANDLER handler( &common );
630
631 InitResult result = handler.InitializeRepository( freshDir );
632 BOOST_CHECK_EQUAL( static_cast<int>( result ), static_cast<int>( InitResult::Success ) );
633
634 wxFileName gitignoreFile( freshDir, wxT( ".gitignore" ) );
635 BOOST_TEST_REQUIRE( gitignoreFile.FileExists() );
636
637 wxString contents;
638 {
639 wxFFile f( gitignoreFile.GetFullPath(), wxT( "r" ) );
640 f.ReadAll( &contents );
641 }
642
643 BOOST_CHECK( contents.Contains( wxT( ".history/" ) ) );
644 BOOST_CHECK( contents.Contains( wxT( "*-backups/" ) ) );
645 BOOST_CHECK( contents.Contains( wxT( "_autosave-*" ) ) );
646 BOOST_CHECK( contents.Contains( wxT( "fp-info-cache" ) ) );
647 BOOST_CHECK( contents.Contains( wxT( "~*.lck" ) ) );
648
649 if( wxFileName::DirExists( freshDir ) )
650 wxFileName::Rmdir( freshDir, wxPATH_RMDIR_RECURSIVE );
651}
652
653
659BOOST_AUTO_TEST_CASE( InitializeRepository_GitignoreDoesNotDuplicateEntries )
660{
661 BOOST_TEST_REQUIRE( ready() );
662
663 wxString freshDir = repoPath() + wxT( "_dedup" );
664 wxFileName::Mkdir( freshDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
665
666 wxFileName gitignoreFile( freshDir, wxT( ".gitignore" ) );
667 {
668 wxFFile f( gitignoreFile.GetFullPath(), wxT( "w" ) );
669 f.Write( wxT( "# my custom ignores\n.history/\nbuild/\n" ) );
670 }
671
672 KIGIT_COMMON common( nullptr );
673 GIT_INIT_HANDLER handler( &common );
674
675 InitResult result = handler.InitializeRepository( freshDir );
676 BOOST_CHECK_EQUAL( static_cast<int>( result ), static_cast<int>( InitResult::Success ) );
677
678 wxTextFile tf;
679 BOOST_TEST_REQUIRE( tf.Open( gitignoreFile.GetFullPath() ) );
680
681 int historyCount = 0;
682 int buildCount = 0;
683 int fpInfoCount = 0;
684
685 for( size_t i = 0; i < tf.GetLineCount(); ++i )
686 {
687 wxString line = tf.GetLine( i );
688 line.Trim().Trim( false );
689
690 if( line == wxT( ".history/" ) )
691 historyCount++;
692 else if( line == wxT( "build/" ) )
693 buildCount++;
694 else if( line == wxT( "fp-info-cache" ) )
695 fpInfoCount++;
696 }
697
698 BOOST_CHECK_EQUAL( historyCount, 1 ); // was already present; not duplicated
699 BOOST_CHECK_EQUAL( buildCount, 1 ); // user's custom entry preserved
700 BOOST_CHECK_EQUAL( fpInfoCount, 1 ); // KiCad default that was missing got appended
701
702 if( wxFileName::DirExists( freshDir ) )
703 wxFileName::Rmdir( freshDir, wxPATH_RMDIR_RECURSIVE );
704}
705
706
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:56
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)
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")