43#include <wx/filename.h>
44#include <wx/textfile.h>
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 );
72 wxFileName::Mkdir(
m_repoPath, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
81 wxFileName::Rmdir(
m_tempBase, wxPATH_RMDIR_RECURSIVE );
97 git_repository* repo =
nullptr;
98 git_repository_open( &repo,
m_repoPath.ToUTF8().data() );
104 git_oid
createCommit( git_repository* aRepo,
const wxString& aFile,
const wxString& aContent,
105 const wxString& aMessage )
107 git_oid commitOid = {};
109 wxString filePath =
m_repoPath + wxFileName::GetPathSeparator() + aFile;
111 std::ofstream f( filePath.ToStdString() );
112 f << aContent.ToStdString();
115 git_index*
index =
nullptr;
117 if( git_repository_index( &
index, aRepo ) != 0 )
120 git_index_add_bypath(
index, aFile.ToUTF8().data() );
121 git_index_write(
index );
124 git_index_write_tree( &treeOid,
index );
125 git_index_free(
index );
127 git_tree* tree =
nullptr;
129 if( git_tree_lookup( &tree, aRepo, &treeOid ) != 0 )
132 git_signature* sig =
nullptr;
136 git_commit* parent =
nullptr;
137 git_reference* headRef =
nullptr;
139 if( git_repository_head( &headRef, aRepo ) == 0 )
141 git_reference_peel( (git_object**) &parent, headRef, GIT_OBJECT_COMMIT );
142 git_reference_free( headRef );
145 const git_commit* parents[1] = { parent };
146 const git_commit** parentsPtr = parent ? parents :
nullptr;
147 size_t parentsCount = parent ? 1 : 0;
149 git_commit_create( &commitOid, aRepo,
"HEAD", sig, sig,
nullptr, aMessage.ToUTF8().data(), tree, parentsCount,
153 git_commit_free( parent );
155 git_signature_free( sig );
156 git_tree_free( tree );
166 git_remote* remote =
nullptr;
167 int rc = git_remote_create( &remote, aRepo,
"origin", fileUrl.ToUTF8().data() );
170 git_remote_free( remote );
180 git_config* cfg =
nullptr;
182 if( git_repository_config( &cfg, aRepo ) != 0 )
185 wxString remoteKey = wxString::Format(
"branch.%s.remote", aBranch );
186 wxString mergeKey = wxString::Format(
"branch.%s.merge", aBranch );
188 git_config_delete_entry( cfg, remoteKey.ToUTF8().data() );
189 git_config_delete_entry( cfg, mergeKey.ToUTF8().data() );
191 git_config_free( cfg );
199 git_config* cfg =
nullptr;
201 if( git_repository_config( &cfg, aRepo ) != 0 )
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 );
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 );
216 wxString
readConfig( git_repository* aRepo,
const wxString& aKey )
218 git_config* cfg =
nullptr;
220 if( git_repository_config( &cfg, aRepo ) != 0 )
221 return wxEmptyString;
223 git_buf buf = {
nullptr, 0, 0 };
226 if( git_config_get_string_buf( &buf, cfg, aKey.ToUTF8().data() ) == 0 && buf.ptr )
227 result = wxString::FromUTF8( buf.ptr );
229 git_buf_dispose( &buf );
230 git_config_free( cfg );
238 git_repository* repo =
nullptr;
240 if( git_repository_init( &repo,
m_repoPath.ToUTF8().data(), 0 ) != 0 )
243 git_config* cfg =
nullptr;
245 if( git_repository_config( &cfg, repo ) == 0 )
249 git_config_free( cfg );
252 createCommit( repo, wxT(
"file.txt" ), wxT(
"initial\n" ), wxT(
"Initial commit" ) );
253 git_repository_free( repo );
260 git_clone_options opts;
261 git_clone_init_options( &opts, GIT_CLONE_OPTIONS_VERSION );
264 wxString sourceUrl = wxS(
"file://" ) +
m_repoPath;
266 git_repository* bare =
nullptr;
267 int rc = git_clone( &bare, sourceUrl.ToUTF8().data(),
m_remotePath.ToUTF8().data(), &opts );
270 git_repository_free( bare );
298 BOOST_TEST_REQUIRE( ready() );
300 git_repository* repo = openRepo();
301 BOOST_TEST_REQUIRE( repo );
305 clearUpstreamConfig( repo, wxT(
"master" ) );
310 git_repository_free( repo );
324 BOOST_TEST_REQUIRE( ready() );
326 git_repository* repo = openRepo();
327 BOOST_TEST_REQUIRE( repo );
330 git_oid c1 = createCommit( repo, wxT(
"file.txt" ), wxT(
"edited\n" ), wxT(
"Edit file" ) );
333 git_reference* trackingRef =
nullptr;
334 git_reference_create( &trackingRef, repo,
"refs/remotes/origin/master", &c1, 1,
nullptr );
337 git_reference_free( trackingRef );
340 setUpstreamConfig( repo, wxT(
"master" ) );
343 git_reference* headRef =
nullptr;
344 git_repository_head( &headRef, repo );
346 git_commit* headCommit =
nullptr;
347 git_reference_peel( (git_object**) &headCommit, headRef, GIT_OBJECT_COMMIT );
349 git_tree* tree =
nullptr;
350 git_commit_tree( &tree, headCommit );
352 git_signature* sig =
nullptr;
356 int rc = git_commit_amend( &amendedOid, headCommit,
"HEAD", sig, sig,
nullptr,
"Edit file (message-only amend)",
358 BOOST_TEST_REQUIRE( rc == 0 );
360 git_tree_free( tree );
361 git_commit_free( headCommit );
362 git_reference_free( headRef );
363 git_signature_free( sig );
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() ) );
376 git_repository_free( repo );
387 BOOST_TEST_REQUIRE( ready() );
389 git_repository* repo = openRepo();
390 BOOST_TEST_REQUIRE( repo );
392 git_oid c1 = createCommit( repo, wxT(
"file.txt" ), wxT(
"edited\n" ), wxT(
"Edit file" ) );
394 git_reference* trackingRef =
nullptr;
395 git_reference_create( &trackingRef, repo,
"refs/remotes/origin/master", &c1, 1,
nullptr );
398 git_reference_free( trackingRef );
401 setUpstreamConfig( repo, wxT(
"master" ) );
405 createCommit( repo, wxT(
"file.txt" ), wxT(
"edited again\n" ), wxT(
"Re-edit file" ) );
411 "AHEAD should contain file.txt when content differs from upstream" );
413 git_repository_free( repo );
426 BOOST_TEST_REQUIRE( ready() );
428 git_repository* repo = openRepo();
429 BOOST_TEST_REQUIRE( repo );
432 clearUpstreamConfig( repo, wxT(
"master" ) );
442 "Pull without upstream config should succeed via fallback; got "
443 + std::to_string(
static_cast<int>(
result ) ) +
", err='"
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" ) );
450 git_repository_free( repo );
461 BOOST_TEST_REQUIRE( ready() );
463 git_repository* repo = openRepo();
464 BOOST_TEST_REQUIRE( repo );
468 createCommit( repo, wxT(
"file.txt" ), wxT(
"second\n" ), wxT(
"Second commit" ) );
471 clearUpstreamConfig( repo, wxT(
"master" ) );
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" ) );
482 git_repository_free( repo );
492 BOOST_TEST_REQUIRE( ready() );
494 git_repository* repo = openRepo();
495 BOOST_TEST_REQUIRE( repo );
497 git_reference* headRefBefore =
nullptr;
498 git_repository_head( &headRefBefore, repo );
499 git_oid oidBefore = *git_reference_target( headRefBefore );
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 );
505 git_commit_free( commitBefore );
506 git_reference_free( headRefBefore );
512 git_reference* headRefAfter =
nullptr;
513 git_repository_head( &headRefAfter, repo );
514 git_oid oidAfter = *git_reference_target( headRefAfter );
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 );
520 BOOST_CHECK( !git_oid_equal( &oidBefore, &oidAfter ) );
521 BOOST_CHECK( git_oid_equal( &treeBefore, &treeAfter ) );
523 wxString amendedMsg = wxString::FromUTF8( git_commit_message( commitAfter ) );
526 git_commit_free( commitAfter );
527 git_reference_free( headRefAfter );
528 git_repository_free( repo );
537 BOOST_TEST_REQUIRE( ready() );
539 git_repository* repo = openRepo();
540 BOOST_TEST_REQUIRE( repo );
542 git_reference* headRefBefore =
nullptr;
543 git_repository_head( &headRefBefore, repo );
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 );
550 git_commit_free( commitBefore );
551 git_reference_free( headRefBefore );
554 wxString newFile = repoPath() + wxFileName::GetPathSeparator() + wxT(
"new.txt" );
556 std::ofstream f( newFile.ToStdString() );
557 f <<
"new content\n";
565 git_reference* headRefAfter =
nullptr;
566 git_repository_head( &headRefAfter, repo );
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 );
573 BOOST_CHECK( !git_oid_equal( &treeBefore, &treeAfter ) );
577 git_tree* treeObj =
nullptr;
578 git_commit_tree( &treeObj, commitAfter );
580 const git_tree_entry* entry = git_tree_entry_byname( treeObj,
"new.txt" );
581 BOOST_CHECK( entry !=
nullptr );
583 git_tree_free( treeObj );
584 git_commit_free( commitAfter );
585 git_reference_free( headRefAfter );
586 git_repository_free( repo );
596 BOOST_TEST_REQUIRE( ready() );
598 wxString unbornDir = repoPath() + wxT(
"_unborn" );
599 wxFileName::Mkdir( unbornDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
601 git_repository* unbornRepo =
nullptr;
602 BOOST_TEST_REQUIRE( git_repository_init( &unbornRepo, unbornDir.ToUTF8().data(), 0 ) == 0 );
610 git_repository_free( unbornRepo );
612 if( wxFileName::DirExists( unbornDir ) )
613 wxFileName::Rmdir( unbornDir, wxPATH_RMDIR_RECURSIVE );
623 BOOST_TEST_REQUIRE( ready() );
625 wxString freshDir = repoPath() + wxT(
"_fresh" );
626 wxFileName::Mkdir( freshDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
634 wxFileName gitignoreFile( freshDir, wxT(
".gitignore" ) );
635 BOOST_TEST_REQUIRE( gitignoreFile.FileExists() );
639 wxFFile f( gitignoreFile.GetFullPath(), wxT(
"r" ) );
640 f.ReadAll( &contents );
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" ) ) );
649 if( wxFileName::DirExists( freshDir ) )
650 wxFileName::Rmdir( freshDir, wxPATH_RMDIR_RECURSIVE );
661 BOOST_TEST_REQUIRE( ready() );
663 wxString freshDir = repoPath() + wxT(
"_dedup" );
664 wxFileName::Mkdir( freshDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
666 wxFileName gitignoreFile( freshDir, wxT(
".gitignore" ) );
668 wxFFile f( gitignoreFile.GetFullPath(), wxT(
"w" ) );
669 f.Write( wxT(
"# my custom ignores\n.history/\nbuild/\n" ) );
679 BOOST_TEST_REQUIRE( tf.Open( gitignoreFile.GetFullPath() ) );
681 int historyCount = 0;
685 for(
size_t i = 0; i < tf.GetLineCount(); ++i )
687 wxString line = tf.GetLine( i );
688 line.Trim().Trim(
false );
690 if( line == wxT(
".history/" ) )
692 else if( line == wxT(
"build/" ) )
694 else if( line == wxT(
"fp-info-cache" ) )
702 if( wxFileName::DirExists( freshDir ) )
703 wxFileName::Rmdir( freshDir, wxPATH_RMDIR_RECURSIVE );
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.
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)
Build a temp directory tree containing a local working repo with one commit and a bare "remote" repo ...
const wxString & repoPath() const
bool initRepoWithCommit()
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.
LIBGIT_BACKEND * m_backend
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.
bool initBareRemoteFromRepo()
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")