42#include <wx/filename.h>
43#include <wx/textfile.h>
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 );
71 wxFileName::Mkdir(
m_repoPath, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
80 wxFileName::Rmdir(
m_tempBase, wxPATH_RMDIR_RECURSIVE );
96 git_repository* repo =
nullptr;
97 git_repository_open( &repo,
m_repoPath.ToUTF8().data() );
103 git_oid
createCommit( git_repository* aRepo,
const wxString& aFile,
const wxString& aContent,
104 const wxString& aMessage )
106 git_oid commitOid = {};
108 wxString filePath =
m_repoPath + wxFileName::GetPathSeparator() + aFile;
110 std::ofstream f( filePath.ToStdString() );
111 f << aContent.ToStdString();
114 git_index*
index =
nullptr;
116 if( git_repository_index( &
index, aRepo ) != 0 )
119 git_index_add_bypath(
index, aFile.ToUTF8().data() );
120 git_index_write(
index );
123 git_index_write_tree( &treeOid,
index );
124 git_index_free(
index );
126 git_tree* tree =
nullptr;
128 if( git_tree_lookup( &tree, aRepo, &treeOid ) != 0 )
131 git_signature* sig =
nullptr;
135 git_commit* parent =
nullptr;
136 git_reference* headRef =
nullptr;
138 if( git_repository_head( &headRef, aRepo ) == 0 )
140 git_reference_peel( (git_object**) &parent, headRef, GIT_OBJECT_COMMIT );
141 git_reference_free( headRef );
144 const git_commit* parents[1] = { parent };
145 const git_commit** parentsPtr = parent ? parents :
nullptr;
146 size_t parentsCount = parent ? 1 : 0;
148 git_commit_create( &commitOid, aRepo,
"HEAD", sig, sig,
nullptr, aMessage.ToUTF8().data(), tree, parentsCount,
152 git_commit_free( parent );
154 git_signature_free( sig );
155 git_tree_free( tree );
165 git_remote* remote =
nullptr;
166 int rc = git_remote_create( &remote, aRepo,
"origin", fileUrl.ToUTF8().data() );
169 git_remote_free( remote );
179 git_config* cfg =
nullptr;
181 if( git_repository_config( &cfg, aRepo ) != 0 )
184 wxString remoteKey = wxString::Format(
"branch.%s.remote", aBranch );
185 wxString mergeKey = wxString::Format(
"branch.%s.merge", aBranch );
187 git_config_delete_entry( cfg, remoteKey.ToUTF8().data() );
188 git_config_delete_entry( cfg, mergeKey.ToUTF8().data() );
190 git_config_free( cfg );
198 git_config* cfg =
nullptr;
200 if( git_repository_config( &cfg, aRepo ) != 0 )
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 );
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 );
215 wxString
readConfig( git_repository* aRepo,
const wxString& aKey )
217 git_config* cfg =
nullptr;
219 if( git_repository_config( &cfg, aRepo ) != 0 )
220 return wxEmptyString;
222 git_buf buf = {
nullptr, 0, 0 };
225 if( git_config_get_string_buf( &buf, cfg, aKey.ToUTF8().data() ) == 0 && buf.ptr )
226 result = wxString::FromUTF8( buf.ptr );
228 git_buf_dispose( &buf );
229 git_config_free( cfg );
237 git_repository* repo =
nullptr;
241 git_repository_init_options initOpts;
242 git_repository_init_options_init( &initOpts, GIT_REPOSITORY_INIT_OPTIONS_VERSION );
243 initOpts.initial_head =
"master";
245 if( git_repository_init_ext( &repo,
m_repoPath.ToUTF8().data(), &initOpts ) != 0 )
248 git_config* cfg =
nullptr;
250 if( git_repository_config( &cfg, repo ) == 0 )
254 git_config_free( cfg );
257 createCommit( repo, wxT(
"file.txt" ), wxT(
"initial\n" ), wxT(
"Initial commit" ) );
258 git_repository_free( repo );
265 git_clone_options opts;
266 git_clone_init_options( &opts, GIT_CLONE_OPTIONS_VERSION );
269 wxString sourceUrl = wxS(
"file://" ) +
m_repoPath;
271 git_repository* bare =
nullptr;
272 int rc = git_clone( &bare, sourceUrl.ToUTF8().data(),
m_remotePath.ToUTF8().data(), &opts );
275 git_repository_free( bare );
303 BOOST_TEST_REQUIRE( ready() );
305 git_repository* repo = openRepo();
306 BOOST_TEST_REQUIRE( repo );
310 clearUpstreamConfig( repo, wxT(
"master" ) );
315 git_repository_free( repo );
329 BOOST_TEST_REQUIRE( ready() );
331 git_repository* repo = openRepo();
332 BOOST_TEST_REQUIRE( repo );
335 git_oid c1 = createCommit( repo, wxT(
"file.txt" ), wxT(
"edited\n" ), wxT(
"Edit file" ) );
338 git_reference* trackingRef =
nullptr;
339 git_reference_create( &trackingRef, repo,
"refs/remotes/origin/master", &c1, 1,
nullptr );
342 git_reference_free( trackingRef );
345 setUpstreamConfig( repo, wxT(
"master" ) );
348 git_reference* headRef =
nullptr;
349 git_repository_head( &headRef, repo );
351 git_commit* headCommit =
nullptr;
352 git_reference_peel( (git_object**) &headCommit, headRef, GIT_OBJECT_COMMIT );
354 git_tree* tree =
nullptr;
355 git_commit_tree( &tree, headCommit );
357 git_signature* sig =
nullptr;
361 int rc = git_commit_amend( &amendedOid, headCommit,
"HEAD", sig, sig,
nullptr,
"Edit file (message-only amend)",
363 BOOST_TEST_REQUIRE( rc == 0 );
365 git_tree_free( tree );
366 git_commit_free( headCommit );
367 git_reference_free( headRef );
368 git_signature_free( sig );
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() ) );
381 git_repository_free( repo );
392 BOOST_TEST_REQUIRE( ready() );
394 git_repository* repo = openRepo();
395 BOOST_TEST_REQUIRE( repo );
397 git_oid c1 = createCommit( repo, wxT(
"file.txt" ), wxT(
"edited\n" ), wxT(
"Edit file" ) );
399 git_reference* trackingRef =
nullptr;
400 git_reference_create( &trackingRef, repo,
"refs/remotes/origin/master", &c1, 1,
nullptr );
403 git_reference_free( trackingRef );
406 setUpstreamConfig( repo, wxT(
"master" ) );
410 createCommit( repo, wxT(
"file.txt" ), wxT(
"edited again\n" ), wxT(
"Re-edit file" ) );
416 "AHEAD should contain file.txt when content differs from upstream" );
418 git_repository_free( repo );
431 BOOST_TEST_REQUIRE( ready() );
433 git_repository* repo = openRepo();
434 BOOST_TEST_REQUIRE( repo );
437 clearUpstreamConfig( repo, wxT(
"master" ) );
447 "Pull without upstream config should succeed via fallback; got "
448 + std::to_string(
static_cast<int>(
result ) ) +
", err='"
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" ) );
455 git_repository_free( repo );
466 BOOST_TEST_REQUIRE( ready() );
468 git_repository* repo = openRepo();
469 BOOST_TEST_REQUIRE( repo );
473 createCommit( repo, wxT(
"file.txt" ), wxT(
"second\n" ), wxT(
"Second commit" ) );
476 clearUpstreamConfig( repo, wxT(
"master" ) );
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" ) );
487 git_repository_free( repo );
497 BOOST_TEST_REQUIRE( ready() );
499 git_repository* repo = openRepo();
500 BOOST_TEST_REQUIRE( repo );
502 git_reference* headRefBefore =
nullptr;
503 git_repository_head( &headRefBefore, repo );
504 git_oid oidBefore = *git_reference_target( headRefBefore );
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 );
510 git_commit_free( commitBefore );
511 git_reference_free( headRefBefore );
517 git_reference* headRefAfter =
nullptr;
518 git_repository_head( &headRefAfter, repo );
519 git_oid oidAfter = *git_reference_target( headRefAfter );
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 );
525 BOOST_CHECK( !git_oid_equal( &oidBefore, &oidAfter ) );
526 BOOST_CHECK( git_oid_equal( &treeBefore, &treeAfter ) );
528 wxString amendedMsg = wxString::FromUTF8( git_commit_message( commitAfter ) );
531 git_commit_free( commitAfter );
532 git_reference_free( headRefAfter );
533 git_repository_free( repo );
542 BOOST_TEST_REQUIRE( ready() );
544 git_repository* repo = openRepo();
545 BOOST_TEST_REQUIRE( repo );
547 git_reference* headRefBefore =
nullptr;
548 git_repository_head( &headRefBefore, repo );
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 );
555 git_commit_free( commitBefore );
556 git_reference_free( headRefBefore );
559 wxString newFile = repoPath() + wxFileName::GetPathSeparator() + wxT(
"new.txt" );
561 std::ofstream f( newFile.ToStdString() );
562 f <<
"new content\n";
570 git_reference* headRefAfter =
nullptr;
571 git_repository_head( &headRefAfter, repo );
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 );
578 BOOST_CHECK( !git_oid_equal( &treeBefore, &treeAfter ) );
582 git_tree* treeObj =
nullptr;
583 git_commit_tree( &treeObj, commitAfter );
585 const git_tree_entry* entry = git_tree_entry_byname( treeObj,
"new.txt" );
586 BOOST_CHECK( entry !=
nullptr );
588 git_tree_free( treeObj );
589 git_commit_free( commitAfter );
590 git_reference_free( headRefAfter );
591 git_repository_free( repo );
601 BOOST_TEST_REQUIRE( ready() );
603 wxString unbornDir = repoPath() + wxT(
"_unborn" );
604 wxFileName::Mkdir( unbornDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
606 git_repository* unbornRepo =
nullptr;
607 BOOST_TEST_REQUIRE( git_repository_init( &unbornRepo, unbornDir.ToUTF8().data(), 0 ) == 0 );
615 git_repository_free( unbornRepo );
617 if( wxFileName::DirExists( unbornDir ) )
618 wxFileName::Rmdir( unbornDir, wxPATH_RMDIR_RECURSIVE );
628 BOOST_TEST_REQUIRE( ready() );
630 wxString freshDir = repoPath() + wxT(
"_fresh" );
631 wxFileName::Mkdir( freshDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
639 wxFileName gitignoreFile( freshDir, wxT(
".gitignore" ) );
640 BOOST_TEST_REQUIRE( gitignoreFile.FileExists() );
644 wxFFile f( gitignoreFile.GetFullPath(), wxT(
"r" ) );
645 f.ReadAll( &contents );
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" ) ) );
654 if( wxFileName::DirExists( freshDir ) )
655 wxFileName::Rmdir( freshDir, wxPATH_RMDIR_RECURSIVE );
666 BOOST_TEST_REQUIRE( ready() );
668 wxString freshDir = repoPath() + wxT(
"_dedup" );
669 wxFileName::Mkdir( freshDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
671 wxFileName gitignoreFile( freshDir, wxT(
".gitignore" ) );
673 wxFFile f( gitignoreFile.GetFullPath(), wxT(
"w" ) );
674 f.Write( wxT(
"# my custom ignores\n.history/\nbuild/\n" ) );
684 BOOST_TEST_REQUIRE( tf.Open( gitignoreFile.GetFullPath() ) );
686 int historyCount = 0;
690 for(
size_t i = 0; i < tf.GetLineCount(); ++i )
692 wxString line = tf.GetLine( i );
693 line.Trim().Trim(
false );
695 if( line == wxT(
".history/" ) )
697 else if( line == wxT(
"build/" ) )
699 else if( line == wxT(
"fp-info-cache" ) )
707 if( wxFileName::DirExists( freshDir ) )
708 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)
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")