35#include <git2/sys/repository.h>
39#include <wx/filename.h>
42#include <wx/stdpaths.h>
64const char* c_opensshEncrypted =
"-----BEGIN OPENSSH PRIVATE KEY-----\n"
65 "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAxS2G8mY\n"
66 "+85LKkVcFtJi1GAAAAGAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIKB0oSTjiyvxC+qh\n"
67 "cJ2zEnPDzZWArHcl90v2Se1SkmoGAAAAkKKrIYipMtR+JUEsuoYSmnfD0b/ng57Fq2uj25\n"
68 "oHDRqrDyPKS0xqQJa9+IQvPeM+E3G1fAGvsOYTQXsLJzXuQDiLX3vceEuN9AmJkA9Naea4\n"
69 "qAPy8CTlBx/ktdd4YgnYXBETi6Ab1f/h7LAVlLJTOc9Ta9HilKzO8Vl5p3EUS1PWkfGGqe\n"
70 "QLo1/ryfJjC3ZZbA==\n"
71 "-----END OPENSSH PRIVATE KEY-----\n";
74const char* c_opensshPlain =
"-----BEGIN OPENSSH PRIVATE KEY-----\n"
75 "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n"
76 "QyNTUxOQAAACD4Xd9qfxjO+vpBXO94Otoa0kg/p5H8kMYhN6eFe7v8MgAAAIjYZhWO2GYV\n"
77 "jgAAAAtzc2gtZWQyNTUxOQAAACD4Xd9qfxjO+vpBXO94Otoa0kg/p5H8kMYhN6eFe7v8Mg\n"
78 "AAAEBNlyz61Kiufy1a3WO6i2I7vB+7W2iQc8R81H0qrBheSfhd32p/GM76+kFc73g62hrS\n"
79 "SD+nkfyQxiE3p4V7u/wyAAAAAXQBAgME\n"
80 "-----END OPENSSH PRIVATE KEY-----\n";
83const char* c_opensshTruncated =
"-----BEGIN OPENSSH PRIVATE KEY-----\n"
85 "-----END OPENSSH PRIVATE KEY-----\n";
88const char* c_pemEncrypted =
"-----BEGIN EC PRIVATE KEY-----\n"
89 "Proc-Type: 4,ENCRYPTED\n"
90 "DEK-Info: AES-128-CBC,10297828DB5031A5C22276D18E077B61\n"
92 "rHfBqMZtByvcsRb7zgNtkSjhFgnRY3jpyyagjM8jxNoEDRaSSHNSugON80psxytE\n"
93 "HBTGShcJbDXOC7EER7QZOTjM/d+3DB+FEL4EwnJxgnVfWN/5p9hP3CoJLgeItkQV\n"
94 "POqt3mQ1V8AnJibunyn13YrczHM39IFruBB9JvNKKG+i/7yf4Jy4jCB9nicH7TC+\n"
95 "hQCu/mOMde02ziwULJiWIU3EALSYo6JhDJxTRIqUr7TDGXEAPcxpQEvMDz+HAgzY\n"
96 "9zTU6LC/CSbHQNjHYSvgDY4zRDFWxeHgNeQVfqrlOPGGE8NC5HCeo5i1r+rSVPn+\n"
97 "kzqXcGpwN682lM/oJV4ix3her0jf59RnAPKYwyWXfhUwYFtzFwroOoquFi6+HHVT\n"
98 "LlzJacbVVj/GTBOdHBSsu8cfiVLymHhEEzQCyIYY05DeHFe8GiSwDDdvMCPppX38\n"
99 "/R3PnNaEYwOEUK4o50nsWEfPHQw8kZaUkiUcX7uIyV4=\n"
100 "-----END EC PRIVATE KEY-----\n";
103const char* c_pemPlain =
"-----BEGIN EC PRIVATE KEY-----\n"
104 "MIIBaAIBAQQgGuajOowPzHaTJKkj1W+RQS0p87K8bEkZbgbw5pmVArmggfowgfcC\n"
105 "AQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAAAAAA////////////////\n"
106 "MFsEIP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXYqjqT57Pr\n"
107 "vVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSdNgiG5wSTamZ44ROdJreBn36QBEEE\n"
108 "axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54W\n"
109 "K84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8\n"
110 "YyVRAgEBoUQDQgAELhPMgsc2xVYFBrKzqXq8CBRS8t9BBCnznM96FcZFv1MvdbIK\n"
111 "yn1nix57Bwq0VoyooErjaZ3SpXl47JlJpIsm4A==\n"
112 "-----END EC PRIVATE KEY-----\n";
115const char* c_pkcs8Encrypted =
"-----BEGIN ENCRYPTED PRIVATE KEY-----\n"
116 "MIIBzzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQImaRUWsA/SFECAggA\n"
117 "MB0GCWCGSAFlAwQBAgQQiywEwYp7En3NzOemv0ek9QSCAYAIh56t3GB2r1S/B7Cy\n"
118 "UFS4qn1DvGDuhubEl6cw3n8Y1PnWj6ryq6O9Pw/DKdnKonzVN+PVaofXg26ZPLdR\n"
119 "q+J7fmcMPIEC0DqV/6T+oGqWp2iaiTZ11BBOoyBkd8ev59c/TxngOWlbJvvmGhEF\n"
120 "mOiCwXKoKjzU8kSVbmL+vFe1thZjrCaihL79JsZE6Ws5bd7yAn6grPBPI8sBexNi\n"
121 "fERG81T9Nu2mxCEd7zkNAoK0cZBnUwFvxDama18wMvXRYzEHETCUCWevoNFdcwo/\n"
122 "IkGgLciMxtSBBOXyWiVvAzTloSGHa5wafptTsgyTc1HKZBTR0ZTNvBX37aziggWE\n"
123 "u4fMHcHlAJwP4UXgCACtgcfktVhjohgNQdBm5RB95qEUdNAQDQSe4Ro27HfT0YyR\n"
124 "AHZYNXziRjZHsO6wqGarcxdZkH1Cy8Ca9FJr3uRWWr+8CWIjE9nDEkFbXUyQGOPq\n"
125 "sTAC7FpnQHVczHcaa45jXYaPmU/iNFnPkO2DzxWADe/1xIg=\n"
126 "-----END ENCRYPTED PRIVATE KEY-----\n";
130git_repository* makeRepoWithCommit(
const wxString& aRepoPath,
const wxString& aFileName,
131 const std::string& aFileContents )
136 return repo.release();
141void commitWorkingFile(
git_repository* aRepo,
const wxString& aFileName,
const char* aMessage )
147std::string readWorkingFile(
const wxFileName& aFile )
149 std::ifstream in( aFile.GetFullPath().utf8_string() );
150 return std::string( ( std::istreambuf_iterator<char>( in ) ), std::istreambuf_iterator<char>() );
154void writeFile(
const wxString& aRepoPath,
const wxString& aFileName,
const char* aContent )
161bool waitForQuery(
const std::function<
bool()>& aFinished )
163 const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds( 5 );
165 while( !aFinished() && std::chrono::steady_clock::now() < deadline )
167 wxTheApp->ProcessPendingEvents();
168 std::this_thread::sleep_for( std::chrono::milliseconds( 1 ) );
177struct UI_CALLBACK_CAPTURE
179 explicit UI_CALLBACK_CAPTURE( std::shared_ptr<std::atomic<bool>> aWrongThread ) :
180 wrongThread( std::
move( aWrongThread ) ),
181 uiThread( std::this_thread::get_id() )
185 UI_CALLBACK_CAPTURE(
const UI_CALLBACK_CAPTURE& aOther ) :
186 wrongThread( aOther.wrongThread ),
187 uiThread( aOther.uiThread )
189 if( std::this_thread::get_id() != uiThread )
190 wrongThread->store(
true );
193 UI_CALLBACK_CAPTURE( UI_CALLBACK_CAPTURE&& ) =
default;
195 ~UI_CALLBACK_CAPTURE()
197 if( wrongThread && std::this_thread::get_id() != uiThread )
198 wrongThread->store(
true );
201 std::shared_ptr<std::atomic<bool>> wrongThread;
202 std::thread::id uiThread;
215 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"data\n" );
221 BOOST_CHECK( !root.IsEmpty() );
222 BOOST_CHECK_MESSAGE( !root.Contains( wxS(
"/.git" ) ) && !root.Contains( wxS(
"\\.git" ) ),
223 "GetGitRootDirectory should return the working directory, got: "
224 + root.ToStdString() );
228 rootFn.AssignDir( root );
229 rootFn.MakeAbsolute();
231 tmpFn.AssignDir( TempPath() );
232 tmpFn.MakeAbsolute();
233 BOOST_CHECK_EQUAL( rootFn.GetFullPath().ToStdString(), tmpFn.GetFullPath().ToStdString() );
240 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"data\n" );
248 git_remote* remote =
nullptr;
249 BOOST_REQUIRE_EQUAL( git_remote_create( &remote, repo,
"github",
260 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"data\n" );
263 git_remote* remote =
nullptr;
264 BOOST_REQUIRE_EQUAL( git_remote_create( &remote, repo,
"origin",
278 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"data\n" );
284 BOOST_CHECK_MESSAGE( local.empty(),
285 "Expected no AHEAD files when no upstream is configured, got "
286 + std::to_string( local.size() ) );
287 BOOST_CHECK_MESSAGE( remote.empty(),
288 "Expected no BEHIND files when no upstream is configured, got "
289 + std::to_string( remote.size() ) );
298 git_repository* repo = makeRepoWithCommit( TempPath(),
"untouched.txt",
"stable\n" );
303 wxFileName extra( TempPath(), wxS(
"second.txt" ) );
304 std::ofstream f( extra.GetFullPath().utf8_string() );
308 git_index*
index =
nullptr;
309 BOOST_REQUIRE_EQUAL( git_repository_index( &
index, repo ), GIT_OK );
311 BOOST_REQUIRE_EQUAL( git_index_add_bypath(
index,
"second.txt" ), GIT_OK );
312 BOOST_REQUIRE_EQUAL( git_index_write(
index ), GIT_OK );
315 BOOST_REQUIRE_EQUAL( git_index_write_tree( &newTreeOid,
index ), GIT_OK );
317 git_tree* newTree =
nullptr;
318 BOOST_REQUIRE_EQUAL( git_tree_lookup( &newTree, repo, &newTreeOid ), GIT_OK );
321 git_reference* head =
nullptr;
322 BOOST_REQUIRE_EQUAL( git_repository_head( &head, repo ), GIT_OK );
325 git_commit* parentCommit =
nullptr;
327 git_commit_lookup( &parentCommit, repo, git_reference_target( head ) ), GIT_OK );
330 git_signature* sig =
nullptr;
331 BOOST_REQUIRE_EQUAL( git_signature_now( &sig,
"QA Test",
"[email protected]" ), GIT_OK );
334 const git_commit* parents[1] = { parentCommit };
335 git_oid secondCommitOid;
336 BOOST_REQUIRE_EQUAL( git_commit_create( &secondCommitOid, repo,
"HEAD", sig, sig,
nullptr,
337 "second", newTree, 1, parents ),
341 git_treebuilder* tb =
nullptr;
342 BOOST_REQUIRE_EQUAL( git_treebuilder_new( &tb, repo,
nullptr ), GIT_OK );
345 const std::string upstreamData =
"upstream\n";
346 BOOST_REQUIRE_EQUAL( git_blob_create_from_buffer( &blobOid, repo, upstreamData.data(),
347 upstreamData.size() ),
349 BOOST_REQUIRE_EQUAL( git_treebuilder_insert(
nullptr, tb,
"remote_only.txt", &blobOid,
353 git_oid remoteTreeOid;
354 BOOST_REQUIRE_EQUAL( git_treebuilder_write( &remoteTreeOid, tb ), GIT_OK );
355 git_treebuilder_free( tb );
357 git_tree* remoteTree =
nullptr;
358 BOOST_REQUIRE_EQUAL( git_tree_lookup( &remoteTree, repo, &remoteTreeOid ), GIT_OK );
361 git_oid remoteCommitOid;
362 BOOST_REQUIRE_EQUAL( git_commit_create( &remoteCommitOid, repo,
nullptr, sig, sig,
nullptr,
363 "upstream root", remoteTree, 0,
nullptr ),
366 git_reference* remoteRef =
nullptr;
367 BOOST_REQUIRE_EQUAL( git_reference_create( &remoteRef, repo,
"refs/remotes/origin/main",
368 &remoteCommitOid,
true,
nullptr ),
372 git_config* cfg =
nullptr;
373 BOOST_REQUIRE_EQUAL( git_repository_config( &cfg, repo ), GIT_OK );
375 BOOST_REQUIRE_EQUAL( git_config_set_string( cfg,
"branch.main.remote",
"origin" ),
377 BOOST_REQUIRE_EQUAL( git_config_set_string( cfg,
"branch.main.merge",
"refs/heads/main" ),
387 BOOST_CHECK_MESSAGE( local.empty(),
"no files should be reported as AHEAD when the local and remote "
388 "histories share no ancestor" );
389 BOOST_CHECK_MESSAGE( remote.empty(),
"no files should be reported as BEHIND when the local and remote "
390 "histories share no ancestor" );
398 git_repository* repo = makeRepoWithCommit( TempPath(),
"untouched.txt",
"stable\n" );
402 wxFileName extra( TempPath(), wxS(
"touched.txt" ) );
403 std::ofstream f( extra.GetFullPath().utf8_string() );
407 git_index*
index =
nullptr;
408 BOOST_REQUIRE_EQUAL( git_repository_index( &
index, repo ), GIT_OK );
410 BOOST_REQUIRE_EQUAL( git_index_add_bypath(
index,
"touched.txt" ), GIT_OK );
411 BOOST_REQUIRE_EQUAL( git_index_write(
index ), GIT_OK );
414 BOOST_REQUIRE_EQUAL( git_index_write_tree( &treeOid,
index ), GIT_OK );
416 git_tree* tree =
nullptr;
417 BOOST_REQUIRE_EQUAL( git_tree_lookup( &tree, repo, &treeOid ), GIT_OK );
420 git_reference* head =
nullptr;
421 BOOST_REQUIRE_EQUAL( git_repository_head( &head, repo ), GIT_OK );
424 git_commit* parent =
nullptr;
425 BOOST_REQUIRE_EQUAL( git_commit_lookup( &parent, repo, git_reference_target( head ) ),
429 git_signature* sig =
nullptr;
430 BOOST_REQUIRE_EQUAL( git_signature_now( &sig,
"QA Test",
"[email protected]" ), GIT_OK );
433 const git_commit* parents[1] = { parent };
435 BOOST_REQUIRE_EQUAL( git_commit_create( &base_oid, repo,
"HEAD", sig, sig,
nullptr,
436 "add touched", tree, 1, parents ),
441 git_remote* origin =
nullptr;
442 BOOST_REQUIRE_EQUAL( git_remote_create( &origin, repo,
"origin",
447 git_reference* upstream_ref =
nullptr;
448 BOOST_REQUIRE_EQUAL( git_reference_create( &upstream_ref, repo,
"refs/remotes/origin/main",
449 &base_oid,
true,
nullptr ),
453 git_config* cfg =
nullptr;
454 BOOST_REQUIRE_EQUAL( git_repository_config( &cfg, repo ), GIT_OK );
456 BOOST_REQUIRE_EQUAL( git_config_set_string( cfg,
"branch.main.remote",
"origin" ),
458 BOOST_REQUIRE_EQUAL( git_config_set_string( cfg,
"branch.main.merge",
"refs/heads/main" ),
463 wxFileName extra( TempPath(), wxS(
"touched.txt" ) );
464 std::ofstream f( extra.GetFullPath().utf8_string() );
468 BOOST_REQUIRE_EQUAL( git_index_add_bypath(
index,
"touched.txt" ), GIT_OK );
469 BOOST_REQUIRE_EQUAL( git_index_write(
index ), GIT_OK );
472 BOOST_REQUIRE_EQUAL( git_index_write_tree( &newTreeOid,
index ), GIT_OK );
474 git_tree* newTree =
nullptr;
475 BOOST_REQUIRE_EQUAL( git_tree_lookup( &newTree, repo, &newTreeOid ), GIT_OK );
478 git_commit* baseCommit =
nullptr;
479 BOOST_REQUIRE_EQUAL( git_commit_lookup( &baseCommit, repo, &base_oid ), GIT_OK );
482 const git_commit* aheadParents[1] = { baseCommit };
484 BOOST_REQUIRE_EQUAL( git_commit_create( &aheadOid, repo,
"HEAD", sig, sig,
nullptr,
485 "modify touched", newTree, 1, aheadParents ),
491 BOOST_CHECK( remote.empty() );
492 BOOST_CHECK_MESSAGE( local.find( wxS(
"touched.txt" ) ) != local.end(),
493 "touched.txt should be reported as AHEAD" );
494 BOOST_CHECK_MESSAGE( local.find( wxS(
"untouched.txt" ) ) == local.end(),
495 "untouched.txt should NOT be reported as AHEAD - this was the "
496 "regression in issue 21576" );
503 "A passphrase-protected OpenSSH key must be reported as encrypted" );
506 "An OpenSSH key with cipher \"none\" must be reported as unencrypted" );
509 "An OpenSSH key with an unreadable body must not be reported as encrypted" );
516 "A PEM key with Proc-Type: 4,ENCRYPTED must be reported as encrypted" );
519 "A PEM key with no Proc-Type must be reported as unencrypted" );
526 "A PKCS#8 key headed ENCRYPTED PRIVATE KEY must be reported as encrypted" );
535 "A public key is not an encrypted private key" );
543 git_repository* repo = makeRepoWithCommit( TempPath(),
"untouched.txt",
"stable\n" );
546 git_reference* head =
nullptr;
547 BOOST_REQUIRE_EQUAL( git_repository_head( &head, repo ), GIT_OK );
550 git_oid baseOid = *git_reference_target( head );
552 git_commit* baseCommit =
nullptr;
553 BOOST_REQUIRE_EQUAL( git_commit_lookup( &baseCommit, repo, &baseOid ), GIT_OK );
559 wxFileName extra( TempPath(), wxS(
"remote_only.txt" ) );
560 std::ofstream f( extra.GetFullPath().utf8_string() );
561 f <<
"from remote\n";
564 git_index*
index =
nullptr;
565 BOOST_REQUIRE_EQUAL( git_repository_index( &
index, repo ), GIT_OK );
567 BOOST_REQUIRE_EQUAL( git_index_add_bypath(
index,
"remote_only.txt" ), GIT_OK );
568 BOOST_REQUIRE_EQUAL( git_index_write(
index ), GIT_OK );
571 BOOST_REQUIRE_EQUAL( git_index_write_tree( &treeOid,
index ), GIT_OK );
573 git_tree* tree =
nullptr;
574 BOOST_REQUIRE_EQUAL( git_tree_lookup( &tree, repo, &treeOid ), GIT_OK );
577 git_signature* sig =
nullptr;
578 BOOST_REQUIRE_EQUAL( git_signature_now( &sig,
"QA Test",
"[email protected]" ), GIT_OK );
581 const git_commit* parents[1] = { baseCommit };
584 git_commit_create( &remoteOid, repo,
nullptr, sig, sig,
nullptr,
"add remote_only", tree, 1, parents ),
587 git_remote* origin =
nullptr;
588 BOOST_REQUIRE_EQUAL( git_remote_create( &origin, repo,
"origin",
"[email protected]:repo.git" ), GIT_OK );
591 git_reference* upstream_ref =
nullptr;
593 git_reference_create( &upstream_ref, repo,
"refs/remotes/origin/main", &remoteOid,
true,
nullptr ),
597 git_config* cfg =
nullptr;
598 BOOST_REQUIRE_EQUAL( git_repository_config( &cfg, repo ), GIT_OK );
600 BOOST_REQUIRE_EQUAL( git_config_set_string( cfg,
"branch.main.remote",
"origin" ), GIT_OK );
601 BOOST_REQUIRE_EQUAL( git_config_set_string( cfg,
"branch.main.merge",
"refs/heads/main" ), GIT_OK );
606 BOOST_CHECK( local.empty() );
607 BOOST_CHECK_MESSAGE( remote.find( wxS(
"remote_only.txt" ) ) != remote.end(),
608 "remote_only.txt should be reported as BEHIND" );
751 token->store(
true );
767 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
772 wxFileName fn( TempPath(), wxS(
"file.txt" ) );
773 std::ofstream f( fn.GetFullPath().utf8_string() );
777 git_index*
index =
nullptr;
778 BOOST_REQUIRE_EQUAL( git_repository_index( &
index, repo ), GIT_OK );
780 BOOST_REQUIRE_EQUAL( git_index_add_bypath(
index,
"file.txt" ), GIT_OK );
781 BOOST_REQUIRE_EQUAL( git_index_write(
index ), GIT_OK );
784 BOOST_REQUIRE_EQUAL( git_index_write_tree( &treeOid,
index ), GIT_OK );
786 git_tree* tree =
nullptr;
787 BOOST_REQUIRE_EQUAL( git_tree_lookup( &tree, repo, &treeOid ), GIT_OK );
790 git_reference* head =
nullptr;
791 BOOST_REQUIRE_EQUAL( git_repository_head( &head, repo ), GIT_OK );
794 git_commit* parent =
nullptr;
795 BOOST_REQUIRE_EQUAL( git_commit_lookup( &parent, repo, git_reference_target( head ) ), GIT_OK );
798 git_signature* sig =
nullptr;
799 BOOST_REQUIRE_EQUAL( git_signature_now( &sig,
"QA Test",
"[email protected]" ), GIT_OK );
802 const git_commit* parents[1] = { parent };
804 BOOST_REQUIRE_EQUAL( git_commit_create( &secondOid, repo,
"HEAD", sig, sig,
nullptr,
"second", tree, 1, parents ),
810 BOOST_REQUIRE_EQUAL( commits.size(), 2u );
837 for(
const wxString& bad : { wxT(
"a@" ), wxT(
"@" ), wxT(
"@@" ), wxT(
"a@b" ), wxT(
"@b.c" ), wxT(
"a [email protected]" ),
881 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
886 BOOST_CHECK( !common.
CreateBranch( wxS(
"two words" ) ) );
888 BOOST_CHECK( common.
CreateBranch( wxS(
"feature/ok" ) ) );
895 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
898 git_reference* head =
nullptr;
899 BOOST_REQUIRE_EQUAL( git_repository_head( &head, repo ), GIT_OK );
902 git_commit* parent =
nullptr;
903 BOOST_REQUIRE_EQUAL( git_commit_lookup( &parent, repo, git_reference_target( head ) ), GIT_OK );
906 git_tree* tree =
nullptr;
907 BOOST_REQUIRE_EQUAL( git_commit_tree( &tree, parent ), GIT_OK );
911 const char* latin1Author =
"J\xf6rg";
912 const char* latin1Summary =
"Caf\xe9 fix";
914 git_signature* sig =
nullptr;
915 BOOST_REQUIRE_EQUAL( git_signature_now( &sig, latin1Author,
"[email protected]" ), GIT_OK );
918 const git_commit* parents[1] = { parent };
920 BOOST_REQUIRE_EQUAL( git_commit_create( &oid, repo,
"HEAD", sig, sig,
nullptr, latin1Summary, tree, 1, parents ),
926 BOOST_REQUIRE_EQUAL( commits.size(), 1u );
927 BOOST_CHECK_EQUAL( commits[0].summary.ToStdString(), wxString::FromUTF8(
"Caf\xc3\xa9 fix" ).ToStdString() );
928 BOOST_CHECK_EQUAL( commits[0].author.ToStdString(), wxString::FromUTF8(
"J\xc3\xb6rg" ).ToStdString() );
932 BOOST_CHECK( !
detail.message.IsEmpty() );
933 BOOST_CHECK( !
detail.author.IsEmpty() );
940 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
945 BOOST_REQUIRE_EQUAL( git_reference_name_to_id( &headOid, repo,
"HEAD" ), GIT_OK );
947 git_commit* headCommit =
nullptr;
948 BOOST_REQUIRE_EQUAL( git_commit_lookup( &headCommit, repo, &headOid ), GIT_OK );
951 git_reference* side =
nullptr;
952 BOOST_REQUIRE_EQUAL( git_branch_create( &side, repo,
"side", headCommit, 0 ), GIT_OK );
955 BOOST_REQUIRE_EQUAL( git_repository_set_head( repo,
"refs/heads/side" ), GIT_OK );
956 writeFile( TempPath(),
"side.txt",
"side\n" );
957 commitWorkingFile( repo,
"side.txt",
"side work" );
959 BOOST_REQUIRE_EQUAL( git_repository_set_head( repo,
"refs/heads/main" ), GIT_OK );
960 writeFile( TempPath(),
"file.txt",
"v2\n" );
961 commitWorkingFile( repo,
"file.txt",
"main work" );
965 auto summaries = [](
const std::vector<KIGIT_COMMIT_INFO>& aCommits )
967 std::vector<std::string> out;
970 out.push_back( c.summary.ToStdString() );
977 BOOST_REQUIRE_EQUAL( all.size(), 3u );
978 BOOST_CHECK( std::find( all.begin(), all.end(),
"side work" ) != all.end() );
979 BOOST_CHECK( std::find( all.begin(), all.end(),
"main work" ) != all.end() );
983 onMain.
branch = wxS(
"main" );
984 std::vector<std::string> mainOnly = summaries( common.
GetRecentCommits( 10, onMain ) );
985 BOOST_REQUIRE_EQUAL( mainOnly.size(), 2u );
991 search.
search = wxS(
"SIDE" );
992 std::vector<KIGIT_COMMIT_INFO> found = common.
GetRecentCommits( 10, search );
993 BOOST_REQUIRE_EQUAL( found.size(), 1u );
996 search.
search = found[0].hash.Left( 6 );
999 search.
search = wxS(
"nothing like this" );
1008 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
1012 BOOST_REQUIRE_EQUAL( git_reference_name_to_id( &firstOid, repo,
"HEAD" ), GIT_OK );
1014 git_object* first =
nullptr;
1015 BOOST_REQUIRE_EQUAL( git_object_lookup( &first, repo, &firstOid, GIT_OBJECT_COMMIT ), GIT_OK );
1019 BOOST_REQUIRE_EQUAL( git_tag_create_lightweight( &tagOid, repo,
"v1.0", first, 0 ), GIT_OK );
1021 writeFile( TempPath(),
"file.txt",
"v2\n" );
1022 commitWorkingFile( repo,
"file.txt",
"second" );
1027 BOOST_REQUIRE_EQUAL( commits.size(), 2u );
1030 BOOST_REQUIRE_EQUAL( commits[0].refs.size(), 2u );
1034 BOOST_REQUIRE_EQUAL( commits[1].refs.size(), 1u );
1045 git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
1046 opts.flags = GIT_REPOSITORY_INIT_MKPATH;
1047 opts.initial_head =
"main";
1049 BOOST_REQUIRE_EQUAL( git_repository_init_ext( &repo, TempPath().utf8_string().c_str(), &opts ), GIT_OK );
1072 git_reference* head =
nullptr;
1073 BOOST_REQUIRE_EQUAL( git_reference_lookup( &head, repo,
"HEAD" ), GIT_OK );
1076 BOOST_CHECK_EQUAL( std::string( git_reference_symbolic_target( head ) ),
"refs/heads/rev-a" );
1085 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
1088 writeFile( TempPath(),
"file.txt",
"v2\n" );
1089 commitWorkingFile( repo,
"file.txt",
"Route the power rails\n\nBody line one.\nBody line two.\n" );
1101 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"one\ntwo\nthree\n" );
1105 writeFile( TempPath(),
"file.txt",
"one\n2\nthree\n" );
1106 writeFile( TempPath(),
"new.txt",
"a\nb\n" );
1109 git_index*
index =
nullptr;
1110 BOOST_REQUIRE_EQUAL( git_repository_index( &
index, repo ), GIT_OK );
1112 BOOST_REQUIRE_EQUAL( git_index_add_bypath(
index,
"new.txt" ), GIT_OK );
1113 BOOST_REQUIRE_EQUAL( git_index_write(
index ), GIT_OK );
1116 commitWorkingFile( repo,
"file.txt",
"edit and add\n\nWith a body line." );
1121 BOOST_REQUIRE_EQUAL( commits.size(), 2u );
1129 BOOST_REQUIRE_EQUAL(
detail.parents.size(), 1u );
1133 BOOST_REQUIRE_EQUAL(
detail.files.size(), 2u );
1148 BOOST_CHECK( root.
parents.empty() );
1149 BOOST_REQUIRE_EQUAL( root.
files.size(), 1u );
1153 BOOST_CHECK( !common.
GetCommitDetail( wxS(
"0000000000ab" ) ).found );
1161 git_repository* repo = makeRepoWithCommit( TempPath(),
"readme.txt",
"root\n" );
1164 BOOST_REQUIRE( wxFileName::Mkdir( TempPath() + wxS(
"/mine" ) ) );
1165 BOOST_REQUIRE( wxFileName::Mkdir( TempPath() + wxS(
"/theirs" ) ) );
1166 writeFile( TempPath(), wxS(
"mine/board.txt" ),
"one\ntwo\n" );
1167 writeFile( TempPath(), wxS(
"theirs/board.txt" ),
"one\ntwo\n" );
1170 git_index*
index =
nullptr;
1171 BOOST_REQUIRE_EQUAL( git_repository_index( &
index, repo ), GIT_OK );
1173 BOOST_REQUIRE_EQUAL( git_index_add_bypath(
index,
"mine/board.txt" ), GIT_OK );
1174 BOOST_REQUIRE_EQUAL( git_index_add_bypath(
index,
"theirs/board.txt" ), GIT_OK );
1175 BOOST_REQUIRE_EQUAL( git_index_write(
index ), GIT_OK );
1178 commitWorkingFile( repo, wxS(
"readme.txt" ),
"two projects\n" );
1181 writeFile( TempPath(), wxS(
"mine/board.txt" ),
"one\ntwo\nthree\n" );
1182 writeFile( TempPath(), wxS(
"theirs/board.txt" ),
"one\ntwo\na\nb\nc\n" );
1183 writeFile( TempPath(), wxS(
"mine/~board.lck" ),
"locked\n" );
1204 wxString repoRoot = TempPath() + wxS(
"/real/repo" );
1205 BOOST_REQUIRE( wxFileName::Mkdir( repoRoot + wxS(
"/proj" ), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) );
1206 writeFile( repoRoot, wxS(
"proj/p.kicad_pro" ),
"{}\n" );
1208 wxString deepLink = TempPath() + wxS(
"/deep" );
1209 BOOST_REQUIRE_EQUAL( symlink( ( repoRoot + wxS(
"/proj" ) ).utf8_string().c_str(), deepLink.utf8_string().c_str() ),
1212 wxString rootLink = TempPath() + wxS(
"/top" );
1213 BOOST_REQUIRE_EQUAL( symlink( repoRoot.utf8_string().c_str(), rootLink.utf8_string().c_str() ), 0 );
1215 const wxString canonicalWorkDir = repoRoot + wxS(
"/" );
1219 wxString deepProject = deepLink + wxS(
"/" );
1229 wxString topProject = rootLink + wxS(
"/proj/" );
1242 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"hello world\n" );
1245 wxString dest = TempPath() + wxFileName::GetPathSeparator() + wxS(
"extracted.txt" );
1252 in.ReadAll( &content );
1264 writeFile( TempPath(),
"schematics/root.kicad_sch",
"root sheet\n" );
1265 commitWorkingFile( repo.get(),
"schematics/root.kicad_sch",
"add root sheet" );
1266 writeFile( TempPath(),
"schematics/child.kicad_sch",
"child sheet\n" );
1267 commitWorkingFile( repo.get(),
"schematics/child.kicad_sch",
"add child sheet" );
1272 wxString schematicFile;
1277 root = workspace.
Root();
1280 BOOST_CHECK_EQUAL( readWorkingFile( wxFileName( boardFile ) ),
"board version\n" );
1285 otherRoot = other.
Root();
1286 BOOST_CHECK( root != otherRoot );
1289 BOOST_CHECK( boardFile != otherBoard );
1290 BOOST_CHECK_EQUAL( readWorkingFile( wxFileName( otherBoard ) ),
"board version\n" );
1293 BOOST_CHECK( !wxFileName::DirExists( otherRoot ) );
1294 BOOST_CHECK_EQUAL( readWorkingFile( wxFileName( boardFile ) ),
"board version\n" );
1302 BOOST_CHECK_EQUAL( readWorkingFile( wxFileName( schematicFile ) ),
"root sheet\n" );
1303 wxFileName childFile( wxFileName( schematicFile ).GetPath(), wxS(
"child.kicad_sch" ) );
1307 BOOST_CHECK( !wxFileName::DirExists( root ) );
1308 BOOST_CHECK( !wxFileName::FileExists( boardFile ) );
1309 BOOST_CHECK( !wxFileName::FileExists( schematicFile ) );
1316 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
1320 BOOST_CHECK( emptyCommon.
GetTags().empty() );
1323 BOOST_CHECK( common.
GetTags().empty() );
1325 git_object* target =
nullptr;
1326 BOOST_REQUIRE_EQUAL( git_revparse_single( &target, repo,
"HEAD" ), GIT_OK );
1330 BOOST_REQUIRE_EQUAL( git_tag_create_lightweight( &tagOid, repo,
"v1.0", target, 0 ), GIT_OK );
1332 std::vector<KIGIT_TAG_INFO> tags = common.
GetTags();
1333 BOOST_REQUIRE_EQUAL( tags.size(), 1u );
1343 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
1351 wxFileName fn( TempPath(), wxS(
"file.txt" ) );
1352 std::ofstream f( fn.GetFullPath().utf8_string() );
1356 git_signature* sig =
nullptr;
1357 BOOST_REQUIRE_EQUAL( git_signature_now( &sig,
"QA Test",
"[email protected]" ), GIT_OK );
1361 BOOST_REQUIRE_EQUAL( git_stash_save( &stashOid, repo, sig,
"work in progress", GIT_STASH_DEFAULT ), GIT_OK );
1363 std::vector<KIGIT_STASH_INFO> stashes = common.
GetStashes();
1364 BOOST_REQUIRE_EQUAL( stashes.size(), 1u );
1366 BOOST_CHECK( stashes[0].
message.Contains(
"work in progress" ) );
1374 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
1379 auto dirtyAndStash = [&](
const char* aContent,
const wxString& aMessage )
1381 wxFileName fn( TempPath(), wxS(
"file.txt" ) );
1384 std::ofstream f( fn.GetFullPath().utf8_string() );
1391 dirtyAndStash(
"v2\n", wxS(
"first" ) );
1393 std::vector<KIGIT_STASH_INFO> before = common.
GetStashes();
1394 BOOST_REQUIRE_EQUAL( before.size(), 1u );
1396 wxString firstId = before[0].id;
1399 dirtyAndStash(
"v3\n", wxS(
"second" ) );
1401 std::vector<KIGIT_STASH_INFO> after = common.
GetStashes();
1402 BOOST_REQUIRE_EQUAL( after.size(), 2u );
1405 BOOST_CHECK( after[0].
id != firstId );
1415 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
1420 writeFile( TempPath(),
"file.txt",
"v2\n" );
1423 wxFileName fn( TempPath(), wxS(
"file.txt" ) );
1426 std::vector<KIGIT_STASH_INFO> stashes = common.
GetStashes();
1427 BOOST_REQUIRE_EQUAL( stashes.size(), 1u );
1428 BOOST_CHECK( stashes[0].
message.Contains(
"set aside for pull" ) );
1430 BOOST_CHECK( common.
StashPop( 0 ) );
1439 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
1444 std::vector<KIGIT_BRANCH_INFO> branches = common.
GetBranches();
1445 BOOST_REQUIRE_EQUAL( branches.size(), 1u );
1446 wxString original = branches[0].name;
1450 BOOST_REQUIRE_EQUAL( branches.size(), 2u );
1451 BOOST_CHECK( std::any_of( branches.begin(), branches.end(),
1454 return aBranch.name == wxS(
"feature" ) && !aBranch.isRemote;
1459 BOOST_REQUIRE_EQUAL( branches.size(), 1u );
1467 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
1472 BOOST_CHECK( common.
CreateTag(
"v1.0" ) );
1474 std::vector<KIGIT_TAG_INFO> tags = common.
GetTags();
1475 BOOST_REQUIRE_EQUAL( tags.size(), 1u );
1478 BOOST_CHECK( common.
DeleteTag(
"v1.0" ) );
1479 BOOST_CHECK( common.
GetTags().empty() );
1486 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
1493 git_config*
config =
nullptr;
1494 BOOST_REQUIRE_EQUAL( git_repository_config( &
config, repo ), GIT_OK );
1497 git_config_entry* nameEntry =
nullptr;
1498 BOOST_REQUIRE_EQUAL( git_config_get_entry( &nameEntry,
config,
"user.name" ), GIT_OK );
1499 BOOST_CHECK_EQUAL( wxString::FromUTF8( nameEntry->value ).ToStdString(),
"New Name" );
1500 git_config_entry_free( nameEntry );
1502 git_config_entry* emailEntry =
nullptr;
1503 BOOST_REQUIRE_EQUAL( git_config_get_entry( &emailEntry,
config,
"user.email" ), GIT_OK );
1505 git_config_entry_free( emailEntry );
1516 git_config*
config =
nullptr;
1517 BOOST_REQUIRE_EQUAL( git_repository_config( &
config, repo.get() ), GIT_OK );
1521 git_config* local =
nullptr;
1522 BOOST_REQUIRE_EQUAL( git_config_open_level( &local,
config, GIT_CONFIG_LEVEL_LOCAL ), GIT_OK );
1524 BOOST_REQUIRE_EQUAL( git_repository_set_config( repo.get(), local ), GIT_OK );
1528 preferences.authorName = wxS(
"Custom Author" );
1537 BOOST_REQUIRE_EQUAL( git_config_delete_entry( local,
"user.email" ), GIT_OK );
1544 BOOST_REQUIRE_EQUAL( git_config_delete_entry( local,
"user.name" ), GIT_OK );
1552 preferences.useDefaultAuthor =
true;
1557 preferences.useDefaultAuthor =
false;
1558 preferences.authorName.clear();
1568 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
1573 writeFile( TempPath(),
"file.txt",
"v2\n" );
1576 BOOST_REQUIRE_EQUAL( common.
GetStashes().size(), 1u );
1585 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
1590 writeFile( TempPath(),
"file.txt",
"v2\n" );
1592 BOOST_REQUIRE_EQUAL( common.
GetStashes().size(), 1u );
1603 BOOST_CHECK( common.
StashPop( 0 ) );
1611 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
1615 wxFileName fn( TempPath(), wxS(
"file.txt" ) );
1617 writeFile( TempPath(),
"file.txt",
"stashed\n" );
1619 BOOST_REQUIRE_EQUAL( common.
GetStashes().size(), 1u );
1622 writeFile( TempPath(),
"file.txt",
"committed\n" );
1623 commitWorkingFile( repo, wxS(
"file.txt" ),
"conflicting change" );
1625 BOOST_CHECK( !common.
StashPop( 0 ) );
1629 git_index*
index =
nullptr;
1630 BOOST_REQUIRE_EQUAL( git_repository_index( &
index, repo ), GIT_OK );
1632 BOOST_CHECK( !git_index_has_conflicts(
index ) );
1640 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
1648 auto setRebase = [repo](
const char* aValue )
1650 git_config* cfg =
nullptr;
1651 BOOST_REQUIRE_EQUAL( git_repository_config( &cfg, repo ), GIT_OK );
1653 BOOST_REQUIRE_EQUAL( git_config_set_string( cfg,
"pull.rebase", aValue ), GIT_OK );
1656 for(
const char* yes : {
"true",
"yes",
"on",
"1" } )
1662 for(
const char* no : {
"false",
"no",
"off",
"0" } )
1674 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
1699 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
1706 BOOST_REQUIRE_EQUAL( git_reference_name_to_id( &tagged, repo,
"HEAD" ), GIT_OK );
1709 writeFile( TempPath(),
"file.txt",
"v2\n" );
1710 commitWorkingFile( repo,
"file.txt",
"second" );
1717 BOOST_REQUIRE_EQUAL( git_reference_name_to_id( &headNow, repo,
"HEAD" ), GIT_OK );
1718 BOOST_CHECK( git_oid_equal( &headNow, &tagged ) );
1719 BOOST_CHECK_EQUAL( readWorkingFile( wxFileName( TempPath(),
"file.txt" ) ),
"v1\n" );
1725 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
1734 BOOST_REQUIRE_EQUAL( git_repository_set_head( repo,
"refs/heads/feature" ), GIT_OK );
1736 wxFileName fn( TempPath(), wxS(
"file.txt" ) );
1738 std::ofstream f( fn.GetFullPath().utf8_string() );
1742 commitWorkingFile( repo, wxS(
"file.txt" ),
"only on feature" );
1743 BOOST_REQUIRE_EQUAL( git_repository_set_head( repo,
"refs/heads/main" ), GIT_OK );
1758 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
1764 BOOST_CHECK( common.
AddRemote(
"origin",
"https://example.com/x.git" ) );
1766 std::vector<std::pair<wxString, wxString>> remotes = common.
GetRemotes();
1767 BOOST_REQUIRE_EQUAL( remotes.size(), 1u );
1769 BOOST_CHECK_EQUAL( remotes[0].second.ToStdString(),
"https://example.com/x.git" );
1784 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
1791 std::vector<KIGIT_BRANCH_INFO> branches = common.
GetBranches();
1792 BOOST_REQUIRE_EQUAL( branches.size(), 3u );
1793 std::set<wxString> names;
1797 names.insert( branch.name );
1798 BOOST_CHECK( !branch.isRemote );
1799 BOOST_CHECK( branch.when == branches.front().when );
1811 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
1817 writeFile( TempPath(),
"file.txt",
"v2\n" );
1818 const char*
message =
"second\n\nA full commit message.\n";
1819 commitWorkingFile( repo,
"file.txt",
message );
1822 BOOST_REQUIRE_EQUAL( commits.size(), 2u );
1824 auto hashOf = [&](
const char* aSummary )
1828 if( c.summary == aSummary )
1829 return c.hash.ToStdString();
1832 return std::string();
1835 std::vector<KIGIT_BRANCH_INFO> branches = common.
GetBranches();
1836 BOOST_REQUIRE_EQUAL( branches.size(), 2u );
1840 BOOST_CHECK( !branch.isRemote );
1841 BOOST_CHECK( branch.when.IsValid() );
1843 if( branch.name == wxS(
"main" ) )
1845 BOOST_CHECK( branch.isCurrent );
1852 BOOST_CHECK( !branch.isCurrent );
1867 BOOST_REQUIRE_EQUAL( git_reference_name_to_id( &headOid, repo.get(),
"HEAD" ), GIT_OK );
1868 git_reference* remote =
nullptr;
1869 BOOST_REQUIRE_EQUAL( git_reference_create( &remote, repo.get(),
"refs/remotes/origin/topic", &headOid, 0,
nullptr ),
1873 git_reference* alias =
nullptr;
1874 BOOST_REQUIRE_EQUAL( git_reference_symbolic_create( &alias, repo.get(),
"refs/remotes/origin/HEAD",
1875 "refs/remotes/origin/topic", 0,
nullptr ),
1879 std::vector<KIGIT_BRANCH_INFO> branches = common.
GetBranches();
1880 BOOST_REQUIRE_EQUAL( branches.size(), 3u );
1881 unsigned int localCount = 0;
1882 unsigned int remoteCount = 0;
1886 if( branch.name == wxS(
"origin/topic" ) )
1888 if( branch.isRemote )
1893 BOOST_CHECK( !branch.isCurrent );
1898 BOOST_CHECK( branch.isCurrent );
1899 BOOST_CHECK( !branch.isRemote );
1906 BOOST_REQUIRE_EQUAL( git_repository_set_head_detached( repo.get(), &headOid ), GIT_OK );
1908 BOOST_REQUIRE_EQUAL( branches.size(), 3u );
1909 BOOST_CHECK( std::none_of( branches.begin(), branches.end(),
1912 return aBranch.isCurrent;
1919 const wxDateTime older( 1, wxDateTime::Jan, 2020 );
1920 const wxDateTime newer( 2, wxDateTime::Jan, 2020 );
1922 auto branch = [](
const wxString& aName,
const wxDateTime& aWhen,
bool aRemote =
false,
bool aCurrent = false )
1927 info.isRemote = aRemote;
1928 info.isCurrent = aCurrent;
1932 std::vector<KIGIT_BRANCH_INFO> branches = {
1933 branch(
"origin/unknown", wxDateTime(),
true ), branch(
"b-tie", older ),
1934 branch(
"origin/z-new", newer,
true ), branch(
"a-undated", wxDateTime() ),
1935 branch(
"origin/a-old", older,
true ), branch(
"z-new", newer ),
1936 branch(
"main", wxDateTime(),
false,
true ), branch(
"a-tie", older )
1941 const std::vector<wxString>
expected = {
"main",
"z-new",
"a-tie",
"b-tie",
1942 "a-undated",
"origin/z-new",
"origin/a-old",
"origin/unknown" };
1944 BOOST_REQUIRE_EQUAL( branches.size(),
expected.size() );
1946 for(
size_t i = 0; i <
expected.size(); ++i )
1954 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
1960 writeFile( TempPath(),
"file.txt",
"v2\n" );
1961 commitWorkingFile( repo,
"file.txt",
"second\n\nWith a body." );
1964 git_reference*
main =
nullptr;
1965 BOOST_REQUIRE_EQUAL( git_branch_lookup( &
main, repo,
"main", GIT_BRANCH_LOCAL ), GIT_OK );
1967 BOOST_REQUIRE_EQUAL( git_branch_set_upstream(
main,
"base" ), GIT_OK );
1983 BOOST_CHECK( base.
upstream.IsEmpty() );
1998 auto commitOn = [&](
const char* aBranch,
const char* aMessage )
2000 wxString ref = wxS(
"refs/heads/" ) + wxString::FromUTF8( aBranch );
2001 BOOST_REQUIRE_EQUAL( git_repository_set_head( repo.get(), ref.utf8_str() ), GIT_OK );
2002 writeFile( TempPath(),
"file.txt", aMessage );
2003 commitWorkingFile( repo.get(),
"file.txt", aMessage );
2006 commitOn(
"selected",
"selected one" );
2008 commitOn(
"selected",
"selected two" );
2009 commitOn(
"selected-upstream",
"selected upstream one" );
2010 commitOn(
"main-upstream",
"main upstream one" );
2011 commitOn(
"main-upstream",
"main upstream two" );
2012 commitOn(
"main-upstream",
"main upstream three" );
2013 BOOST_REQUIRE_EQUAL( git_repository_set_head( repo.get(),
"refs/heads/main" ), GIT_OK );
2015 auto setUpstream = [&](
const char* aBranch,
const char* aUpstream )
2017 git_reference* ref =
nullptr;
2018 BOOST_REQUIRE_EQUAL( git_branch_lookup( &ref, repo.get(), aBranch, GIT_BRANCH_LOCAL ), GIT_OK );
2020 BOOST_REQUIRE_EQUAL( git_branch_set_upstream( ref, aUpstream ), GIT_OK );
2023 setUpstream(
"main",
"main-upstream" );
2024 setUpstream(
"selected",
"selected-upstream" );
2046 commitOn(
"main",
"local main one" );
2068 writeFile( TempPath(),
".git/refs/heads/broken",
"1111111111111111111111111111111111111111\n" );
2069 git_config*
config =
nullptr;
2070 BOOST_REQUIRE_EQUAL( git_repository_config( &
config, repo.get() ), GIT_OK );
2072 BOOST_REQUIRE_EQUAL( git_config_set_string(
config,
"branch.main.remote",
"." ), GIT_OK );
2073 BOOST_REQUIRE_EQUAL( git_config_set_string(
config,
"branch.main.merge",
"refs/heads/broken" ), GIT_OK );
2078 BOOST_CHECK( !
detail.divergence );
2088 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
2094 writeFile( TempPath(),
"file.txt",
"v2\n" );
2095 commitWorkingFile( repo,
"file.txt",
"second" );
2104 BOOST_CHECK( !common.
CreateBranch(
"orphan",
"nowhere" ) );
2112 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
2118 wxFileName fetchHead( wxString::FromUTF8( git_repository_path( repo ) ), wxS(
"FETCH_HEAD" ) );
2121 std::ofstream f( fetchHead.GetFullPath().utf8_string() );
2122 f <<
"0000000000000000000000000000000000000000\t\tbranch 'main' of https://example.com/x.git\n";
2126 wxDateTime stamp( 1, wxDateTime::Jan, 2020, 12, 0, 0 );
2127 BOOST_REQUIRE( fetchHead.SetTimes(
nullptr, &stamp,
nullptr ) );
2131 BOOST_CHECK( when.IsEqualTo( stamp ) );
2139 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"one\ntwo\nthree\n" );
2148 writeFile( TempPath(),
"file.txt",
"one\n2\nthree\n" );
2149 writeFile( TempPath(),
"new.txt",
"a\nb\n" );
2161 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
2167 BOOST_REQUIRE_EQUAL( commits.size(), 1u );
2169 writeFile( TempPath(),
"file.txt",
"v2\n" );
2170 writeFile( TempPath(),
"new.txt",
"n\n" );
2173 git_index*
index =
nullptr;
2174 BOOST_REQUIRE_EQUAL( git_repository_index( &
index, repo ), GIT_OK );
2176 BOOST_REQUIRE_EQUAL( git_index_add_bypath(
index,
"new.txt" ), GIT_OK );
2177 BOOST_REQUIRE_EQUAL( git_index_write(
index ), GIT_OK );
2182 std::vector<KIGIT_STASH_INFO> stashes = common.
GetStashes();
2183 BOOST_REQUIRE_EQUAL( stashes.size(), 1u );
2190 BOOST_CHECK(
detail.when.IsValid() );
2192 std::vector<std::string> files;
2194 for(
const wxString& f :
detail.files )
2195 files.push_back( f.ToStdString() );
2197 std::sort( files.begin(), files.end() );
2198 BOOST_REQUIRE_EQUAL( files.size(), 2u );
2202 BOOST_CHECK( !common.
GetStashDetail( wxS(
"0000000000000000000000000000000000000000" ) ).found );
2210 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
2213 wxFileName::Mkdir( TempPath() + wxS(
"/sub" ) );
2214 writeFile( TempPath(),
"sub/a.txt",
"a\n" );
2215 commitWorkingFile( repo,
"sub/a.txt",
"add a" );
2216 writeFile( TempPath(),
"file.txt",
"v2\n" );
2217 commitWorkingFile( repo,
"file.txt",
"edit file" );
2218 writeFile( TempPath(),
"sub/b.txt",
"b\n" );
2219 commitWorkingFile( repo,
"sub/b.txt",
"add b" );
2224 filter.path = wxS(
"file.txt" );
2226 BOOST_REQUIRE_EQUAL( commits.size(), 2 );
2230 filter.path = wxS(
"sub" );
2232 BOOST_REQUIRE_EQUAL( commits.size(), 2 );
2236 filter.path = wxS(
"sub/a.txt" );
2239 filter.path = wxS(
"nowhere.txt" );
2243 filter.path = wxS(
"sub" );
2244 filter.search = wxS(
"add b" );
2258 git_repository* repo = makeRepoWithCommit( TempPath(),
"board.kicad_pcb",
"(kicad_pcb)\n" );
2262 BOOST_CHECK( !PROJECT_GIT_UTILS::ProjectUsesGitLfs( common ) );
2264 writeFile( TempPath(),
".gitattributes",
"*.step filter=lfs diff=lfs merge=lfs -text\n" );
2265 commitWorkingFile( repo,
".gitattributes",
"route step files through lfs" );
2266 writeFile( TempPath(),
"case.step",
"ISO-10303-21;\n" );
2267 commitWorkingFile( repo,
"case.step",
"add the enclosure" );
2269 BOOST_CHECK( PROJECT_GIT_UTILS::PathUsesGitLfs( repo, wxS(
"case.step" ) ) );
2270 BOOST_CHECK( !PROJECT_GIT_UTILS::PathUsesGitLfs( repo, wxS(
"board.kicad_pcb" ) ) );
2273 BOOST_CHECK( PROJECT_GIT_UTILS::ProjectUsesGitLfs( common, &example ) );
2277 const wxString elsewhere = TempPath() + wxS(
"/elsewhere" );
2280 BOOST_CHECK( !PROJECT_GIT_UTILS::ProjectUsesGitLfs( otherProject ) );
2282 std::optional<wxString> prefix = PROJECT_GIT_UTILS::ProjectPrefix( wxS(
"/repo" ), wxS(
"/repo/proj" ) );
2285 prefix = PROJECT_GIT_UTILS::ProjectPrefix( wxS(
"/repo" ), wxS(
"/repo" ) );
2287 BOOST_CHECK( prefix->IsEmpty() );
2288 BOOST_CHECK( !PROJECT_GIT_UTILS::ProjectPrefix( wxS(
"/repo" ), wxS(
"/other/proj" ) ) );
2289 BOOST_CHECK( !PROJECT_GIT_UTILS::ProjectPrefix( wxS(
"/repo" ), wxS(
"/repo2/proj" ) ) );
2290 BOOST_CHECK( !PROJECT_GIT_UTILS::ProjectPrefix( wxS(
"/repo" ), wxEmptyString ) );
2295 prefix = PROJECT_GIT_UTILS::ProjectPrefix( wxS(
"C:\\repo" ), wxS(
"C:/repo/proj" ) );
2298 prefix = PROJECT_GIT_UTILS::ProjectPrefix( wxS(
"c:\\Repo\\" ), wxS(
"C:\\repo\\Proj" ) );
2301 BOOST_CHECK( !PROJECT_GIT_UTILS::ProjectPrefix( wxS(
"C:\\repo" ), wxS(
"D:\\repo\\proj" ) ) );
2311 writeFile( TempPath(),
".gitattributes",
"*.step filter=lfs diff=lfs merge=lfs -text\n" );
2312 commitWorkingFile( repo.get(),
".gitattributes",
"use lfs" );
2313 writeFile( TempPath(),
"projects/board/board.kicad_pcb",
"(kicad_pcb)\n" );
2314 commitWorkingFile( repo.get(),
"projects/board/board.kicad_pcb",
"add project" );
2315 writeFile( TempPath(),
"projects/board2/case.step",
"sibling enclosure\n" );
2316 commitWorkingFile( repo.get(),
"projects/board2/case.step",
"add sibling enclosure" );
2318 KIGIT_COMMON common( repo.get(), TempPath() + wxS(
"/projects/board" ) );
2320 BOOST_CHECK( !PROJECT_GIT_UTILS::ProjectUsesGitLfs( common, &example ) );
2321 BOOST_CHECK( PROJECT_GIT_UTILS::LfsRefusal( &common ).IsEmpty() );
2324 BOOST_CHECK( !PROJECT_GIT_UTILS::LfsRefusal( &unknownProject ).IsEmpty() );
2326 writeFile( TempPath(),
"projects/board/models/case.step",
"project enclosure\n" );
2327 commitWorkingFile( repo.get(),
"projects/board/models/case.step",
"add project enclosure" );
2329 BOOST_CHECK( PROJECT_GIT_UTILS::ProjectUsesGitLfs( common, &example ) );
2331 BOOST_CHECK( PROJECT_GIT_UTILS::LfsRefusal( &common ).Contains( example ) );
2339 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
2342 git_signature* authored =
nullptr;
2343 BOOST_REQUIRE_EQUAL( git_signature_new( &authored,
"QA Test",
"[email protected]", 1000, 0 ), GIT_OK );
2346 git_signature* committed =
nullptr;
2347 BOOST_REQUIRE_EQUAL( git_signature_new( &committed,
"QA Test",
"[email protected]", 2000, 0 ), GIT_OK );
2351 BOOST_REQUIRE_EQUAL( git_reference_name_to_id( &headOid, repo,
"HEAD" ), GIT_OK );
2353 git_commit* parent =
nullptr;
2354 BOOST_REQUIRE_EQUAL( git_commit_lookup( &parent, repo, &headOid ), GIT_OK );
2357 git_tree* tree =
nullptr;
2358 BOOST_REQUIRE_EQUAL( git_commit_tree( &tree, parent ), GIT_OK );
2361 const git_commit* parents[1] = { parent };
2363 BOOST_REQUIRE_EQUAL(
2364 git_commit_create( &commitOid, repo,
"HEAD", authored, committed,
nullptr,
"rebased", tree, 1, parents ),
2367 git_object* target =
nullptr;
2368 BOOST_REQUIRE_EQUAL( git_revparse_single( &target, repo,
"HEAD" ), GIT_OK );
2372 BOOST_REQUIRE_EQUAL( git_tag_create_lightweight( &tagOid, repo,
"rebased", target, 0 ), GIT_OK );
2385 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
2396 git_object* tagObject =
nullptr;
2397 BOOST_REQUIRE_EQUAL( git_revparse_single( &tagObject, repo,
"refs/tags/v1.0" ), GIT_OK );
2408 BOOST_CHECK( annotated.
when.IsValid() );
2416 BOOST_CHECK( lightweight.
when.IsValid() );
2418 BOOST_CHECK( !common.
GetTagDetail( wxS(
"nowhere" ) ).found );
2425 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
2433 git_object* commitObject =
nullptr;
2434 BOOST_REQUIRE_EQUAL( git_revparse_single( &commitObject, repo,
"HEAD" ), GIT_OK );
2437 git_signature* oldSig =
nullptr;
2438 BOOST_REQUIRE_EQUAL( git_signature_new( &oldSig,
"QA Test",
"[email protected]", 1000, 0 ), GIT_OK );
2442 BOOST_REQUIRE_EQUAL( git_tag_create( &oid, repo,
"old", commitObject, oldSig,
"old tag", 0 ), GIT_OK );
2444 git_object* treeObject =
nullptr;
2445 BOOST_REQUIRE_EQUAL( git_revparse_single( &treeObject, repo,
"HEAD^{tree}" ), GIT_OK );
2447 BOOST_REQUIRE_EQUAL( git_tag_create_lightweight( &oid, repo,
"tree-tag", treeObject, 0 ), GIT_OK );
2450 std::vector<KIGIT_TAG_INFO> tags = common.
GetTags();
2451 BOOST_REQUIRE_EQUAL( tags.size(), 4 );
2452 std::set<wxString> newest{ tags[0].name, tags[1].name };
2453 std::set<wxString> madeNow{ wxS(
"v1.0" ), wxS(
"beta-1" ) };
2454 BOOST_CHECK( newest == madeNow );
2457 BOOST_CHECK( tags[3].hash.IsEmpty() && !tags[3].when.IsValid() );
2465 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
2468 writeFile( TempPath(),
"file.txt",
"v2\n" );
2469 commitWorkingFile( repo,
"file.txt",
"Update BOM\n\nSecond pass.\n" );
2470 writeFile( TempPath(),
"file.txt",
"v3\n" );
2471 commitWorkingFile( repo,
"file.txt",
"Route power\n" );
2472 writeFile( TempPath(),
"file.txt",
"v4\n" );
2473 commitWorkingFile( repo,
"file.txt",
"Update BOM\n\nSecond pass.\n" );
2474 writeFile( TempPath(),
"file.txt",
"v5\n" );
2475 commitWorkingFile( repo,
"file.txt",
" \n\n" );
2481 BOOST_REQUIRE_EQUAL( messages.size(), 3 );
2492 BOOST_REQUIRE_EQUAL( git_repository_init( &unborn,
empty.PathStr().utf8_string().c_str(), 0 ), GIT_OK );
2505 wxS(
"Fix the thing" ) );
2507 wxS(
"Fix\n\nWhy it broke." ) );
2515 wxString signedOff = wxS(
"Why.\n\nSigned-off-by: QA Test <[email protected]>" );
2521 wxS(
"Fix\n\n" ) + signedOff );
2523 wxS(
"Fix\n\n" ) + signedOff + coauthor );
2525 wxS(
"Fix\n\n" ) + other + wxS(
"\n\nSigned-off-by: QA Test <[email protected]>" ) );
2527 wxString summary, description;
2535 BOOST_CHECK( description.IsEmpty() );
2553 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
2556 writeFile( TempPath(),
"file.txt",
"v2\n" );
2566 BOOST_CHECK_EQUAL( readWorkingFile( wxFileName( TempPath(),
"file.txt" ) ),
"v2\n" );
2607 const char* username;
2608 const char* hostname;
2611 const TEST_CASE cases[] = {
2612 {
"ssh://[email protected]:29022/srv/git/repos/l3-auth-30.git",
"runner",
"192.168.1.55" },
2613 {
"ssh://192.168.1.55:29022/srv/git/repos/l3-auth-30.git",
"",
"192.168.1.55" },
2617 {
"example.com:team/repo",
"",
"example.com" },
2618 {
"ssh://runner@[2001:db8::1]:2222/srv/repo",
"runner",
"2001:db8::1" },
2619 {
"runner@[2001:db8::1]:team/repo",
"runner",
"2001:db8::1" },
2622 for(
const TEST_CASE&
test : cases )
2631 BOOST_CHECK( parsed.
password.IsEmpty() );
2643 const char* address;
2644 const char* username;
2645 const char* password;
2649 const TEST_CASE cases[] = {
2652 {
"http://alice:[email protected]:8080/team/repo",
"http://example.com:8080/team/repo",
"alice",
"secret",
2660 for(
const TEST_CASE&
test : cases )
2678 const char* paths[] = {
2679 "/srv/git/repos/project.git",
"../repos/project.git",
2680 "./repos/project:backup.git",
"C:/repos/project.git",
2681 "C:\\repos\\project.git",
"C:project.git",
2682 "\\\\server\\repos\\project.git",
"file:///srv/git/repos/project.git",
2683 "file:///C:/repos/project.git",
2686 for(
const char*
path : paths )
2693 BOOST_CHECK( parsed.
username.IsEmpty() );
2694 BOOST_CHECK( parsed.
password.IsEmpty() );
2695 BOOST_CHECK( parsed.
hostname.IsEmpty() );
2705 const wxString sshURL = wxS(
"ssh://[email protected]:29022/srv/git/repos/l3-auth-30.git" );
2714 common.
SetRemote( wxS(
"/srv/git/repos/local.git" ) );
2724 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
2730 git_config* cfg =
nullptr;
2731 BOOST_REQUIRE_EQUAL( git_repository_config( &cfg, repo ), GIT_OK );
2733 BOOST_REQUIRE_EQUAL( git_config_set_string( cfg,
"branch.main.remote",
"origin" ), GIT_OK );
2734 BOOST_REQUIRE_EQUAL( git_config_set_string( cfg,
"branch.main.merge",
"refs/heads/main" ), GIT_OK );
2752 const wxString key = wxFileName( TempPath() + wxS(
"/keys/kicad" ) ).GetFullPath();
2772 PROJECT_GIT_UTILS::IsWithinProjectPath( wxS(
"C:/repo/proj/board.kicad_pcb" ), wxS(
"C:\\repo\\proj" ) ) );
2773 BOOST_CHECK( PROJECT_GIT_UTILS::IsWithinProjectPath( wxS(
"C:/repo/proj/sub/sheet.kicad_sch" ),
2774 wxS(
"C:\\repo\\proj\\" ) ) );
2775 BOOST_CHECK( !PROJECT_GIT_UTILS::IsWithinProjectPath( wxS(
"C:/repo/other/board.kicad_pcb" ),
2776 wxS(
"C:\\repo\\proj" ) ) );
2780 BOOST_CHECK( !PROJECT_GIT_UTILS::IsWithinProjectPath( wxS(
"/repo/project2/file" ), wxS(
"/repo/project" ) ) );
2781 BOOST_CHECK( PROJECT_GIT_UTILS::IsWithinProjectPath( wxS(
"/repo/project/file" ), wxS(
"/repo/project" ) ) );
2789 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"v1\n" );
2797 wxFileName fn( TempPath(), wxS(
"notes.txt" ) );
2798 std::ofstream f( fn.GetFullPath().utf8_string() );
2806 wxFileName fn( TempPath(), wxS(
"file.txt" ) );
2807 std::ofstream f( fn.GetFullPath().utf8_string() );
2820 status.
lfsMessage = wxS(
"Git LFS prevents this rewrite." );
2823 const std::vector<wxString> editors = { wxS(
"Schematic Editor" ), wxS(
"Footprint Editor" ) };
2826 BOOST_CHECK(
message.Contains(
"Schematic Editor" ) );
2827 BOOST_CHECK(
message.Contains(
"Footprint Editor" ) );
2828 BOOST_CHECK(
message.Contains(
"unsaved changes" ) );
2829 BOOST_CHECK(
message.Contains(
"Save or discard" ) );
2830 BOOST_CHECK( !
message.Contains(
"Git LFS" ) );
2831 BOOST_CHECK( !
message.Contains(
"other/board.kicad_pcb" ) );
2834 BOOST_CHECK( stashMessage.Contains(
"Schematic Editor" ) );
2835 BOOST_CHECK( stashMessage.Contains(
"Footprint Editor" ) );
2836 BOOST_CHECK( stashMessage.Contains(
"unsaved changes" ) );
2837 BOOST_CHECK( stashMessage.Contains(
"Save or discard" ) );
2838 BOOST_CHECK( stashMessage.Contains(
"before stashing" ) );
2849 auto unavailableLock = unavailable.
LockAction();
2854 BOOST_CHECK( !missing.
checked );
2855 BOOST_CHECK( missing.
RefusalMessage( {} ).Contains(
"Could not check" ) );
2856 BOOST_CHECK( !missing.
RefusalMessage( {} ).Contains(
"Stash Changes" ) );
2861 BOOST_REQUIRE_EQUAL( git_repository_init( &rawRepo, TempPath().utf8_str(), 1 ), GIT_OK );
2867 BOOST_CHECK( !failed.
checked );
2868 BOOST_CHECK( failed.
RefusalMessage( {} ).Contains(
"Could not check" ) );
2883 writeFile( TempPath(),
"notes.txt",
"untracked notes\n" );
2886 BOOST_CHECK( untracked.
checked );
2897 const wxString projectPath = TempPath() + wxS(
"/project" );
2898 BOOST_REQUIRE( wxFileName::Mkdir( projectPath, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) );
2899 BOOST_REQUIRE( wxFileName::Mkdir( TempPath() + wxS(
"/project2" ), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) );
2900 writeFile( TempPath(),
"project/board.kicad_pcb",
"project board\n" );
2901 commitWorkingFile( repo.get(),
"project/board.kicad_pcb",
"add project" );
2902 writeFile( TempPath(),
"project2/board.kicad_pcb",
"sibling board\n" );
2903 commitWorkingFile( repo.get(),
"project2/board.kicad_pcb",
"add sibling" );
2907 writeFile( TempPath(),
"project/board.kicad_pcb",
"edited project board\n" );
2910 BOOST_CHECK( inside.
checked );
2913 BOOST_CHECK( inside.
RefusalMessage( {} ).Contains(
"Stash Changes" ) );
2916 writeFile( TempPath(),
"project2/board.kicad_pcb",
"edited sibling board\n" );
2917 writeFile( TempPath(),
"project2/notes.txt",
"untracked sibling notes\n" );
2920 BOOST_CHECK( outside.
checked );
2926 BOOST_CHECK(
message.Contains(
"project2/board.kicad_pcb" ) );
2927 BOOST_CHECK( !
message.Contains(
"project/board.kicad_pcb" ) );
2928 BOOST_CHECK( !
message.Contains(
"notes.txt" ) );
2937 writeFile( TempPath(),
".gitattributes",
"*.step filter=lfs diff=lfs merge=lfs -text\n" );
2938 commitWorkingFile( repo.get(),
".gitattributes",
"route step files through lfs" );
2939 writeFile( TempPath(),
"case.step",
"ISO-10303-21;\n" );
2940 commitWorkingFile( repo.get(),
"case.step",
"add the enclosure" );
2949 BOOST_CHECK( clean.
RefusalMessage( {} ).Contains(
"case.step" ) );
2951 writeFile( TempPath(),
"board.kicad_pcb",
"edited board\n" );
2957 BOOST_CHECK( !dirty.
RefusalMessage( {} ).Contains(
"Stash Changes" ) );
2958 BOOST_CHECK( dirty.
RefusalMessage( {},
true ).Contains(
"Git LFS" ) );
2966 git_repository* repo = makeRepoWithCommit( TempPath(),
"file.txt",
"one\ntwo\nthree\n" );
2974 BOOST_REQUIRE_EQUAL( git_blob_create_from_buffer( &blobOid, repo,
"other\n", 6 ), GIT_OK );
2977 git_index*
index =
nullptr;
2978 BOOST_REQUIRE_EQUAL( git_repository_index( &
index, repo ), GIT_OK );
2981 git_index_entry stage{};
2982 stage.path =
"file.txt";
2983 stage.mode = GIT_FILEMODE_BLOB;
2984 git_oid_cpy( &stage.id, &blobOid );
2986 BOOST_REQUIRE_EQUAL( git_index_conflict_add(
index, &stage, &stage, &stage ), GIT_OK );
2987 BOOST_REQUIRE_EQUAL( git_index_write(
index ), GIT_OK );
2993 BOOST_REQUIRE_MESSAGE( common.
DiscardChanges( { wxS(
"file.txt" ) }, error ), error.ToStdString() );
2996 "discarding left the file still needing resolution" );
3006 BOOST_REQUIRE_EQUAL( git_reference_name_to_id( &head, repo.get(),
"HEAD" ), GIT_OK );
3007 const wxString confirmedHead = wxString::FromUTF8( git_oid_tostr_s( &head ) );
3010 auto forcePushError = [&common](
const wxString& aBranch,
const wxString& aHeadOid )
3020 const wxString noRemote = wxS(
"There is no remote named 'origin'." );
3021 BOOST_CHECK( forcePushError( confirmedBranch, confirmedHead ).StartsWith( noRemote ) );
3023 const wxString targetChanged = wxS(
"The branch or its last commit changed while the push was waiting." );
3027 BOOST_REQUIRE_EQUAL( git_repository_set_head( repo.get(),
"refs/heads/other" ), GIT_OK );
3028 BOOST_CHECK( forcePushError( confirmedBranch, confirmedHead ).Contains( targetChanged ) );
3031 BOOST_REQUIRE_EQUAL( git_repository_set_head( repo.get(),
"refs/heads/main" ), GIT_OK );
3032 writeFile( TempPath(), wxS(
"file.txt" ),
"two\n" );
3033 commitWorkingFile( repo.get(), wxS(
"file.txt" ),
"second" );
3034 BOOST_CHECK( forcePushError( confirmedBranch, confirmedHead ).Contains( targetChanged ) );
3036 BOOST_REQUIRE_EQUAL( git_reference_name_to_id( &head, repo.get(),
"HEAD" ), GIT_OK );
3038 forcePushError( confirmedBranch, wxString::FromUTF8( git_oid_tostr_s( &head ) ) ).StartsWith( noRemote ) );
3053 std::promise<void> entered;
3054 auto ready = entered.get_future();
3055 auto result = std::async( std::launch::async,
3056 [&handler, &entered]()
3058 entered.set_value();
3062 const bool started = ready.wait_for( std::chrono::seconds( 5 ) ) == std::future_status::ready;
3063 const bool waited =
result.wait_for( std::chrono::milliseconds( 50 ) ) == std::future_status::timeout;
3064 token->store(
true );
3067 BOOST_CHECK( started );
3068 BOOST_CHECK( waited );
3069 BOOST_REQUIRE(
result.wait_for( std::chrono::seconds( 5 ) ) == std::future_status::ready );
3082 auto wrongThread = std::make_shared<std::atomic<bool>>( false );
3083 UI_CALLBACK_CAPTURE capture( wrongThread );
3084 bool completed =
false;
3089 "completion capture",
3093 [capture, &completed]()
3101 return !transfer.
IsBusy();
3105 BOOST_CHECK( completed );
3106 BOOST_CHECK( !wrongThread->load() );
3108 auto entered = std::make_shared<std::promise<void>>();
3109 std::promise<void> release;
3110 auto ready = entered->get_future();
3111 auto proceed = release.get_future().share();
3112 auto transfer = std::make_unique<KIGIT_TRANSFER>();
3116 "destroyed completion owner",
3119 entered->set_value();
3122 [capture, &completed]()
3127 const bool started = ready.wait_for( std::chrono::seconds( 5 ) ) == std::future_status::ready;
3129 release.set_value();
3131 BOOST_CHECK( started );
3132 BOOST_REQUIRE_EQUAL( Backend()->OrphanRegistry().JoinAll( std::chrono::seconds( 5 ) ), 0u );
3133 wxTheApp->ProcessPendingEvents();
3134 BOOST_CHECK( !completed );
3135 BOOST_CHECK( !wrongThread->load() );
3142 auto session = std::make_shared<KIGIT_COMMON>( repo.get() );
3145 auto entered = std::make_shared<std::promise<void>>();
3146 std::promise<void> release;
3147 auto ready = entered->get_future();
3148 auto proceed = release.get_future().share();
3149 auto worked = std::make_shared<std::atomic<unsigned>>( 0 );
3150 auto workerThread = std::make_shared<std::thread::id>();
3151 unsigned completed = 0;
3155 [worked, workerThread, proceed, entered](
KIGIT_COMMON& )
3157 *workerThread = std::this_thread::get_id();
3158 worked->fetch_or( 1 );
3159 entered->set_value();
3167 BOOST_REQUIRE( ready.wait_for( std::chrono::seconds( 5 ) ) == std::future_status::ready );
3168 BOOST_CHECK( *workerThread != std::this_thread::get_id() );
3169 BOOST_CHECK( !session->TryLockAction().owns_lock() );
3175 worked->fetch_or( 2 );
3185 worked->fetch_or( 4 );
3191 release.set_value();
3206 auto session = std::make_shared<KIGIT_COMMON>( repo.get() );
3208 bool completed =
false;
3209 auto worked = std::make_shared<std::atomic<unsigned>>( 0 );
3210 std::shared_ptr<int> workerLifetime = std::make_shared<int>( 0 );
3211 std::weak_ptr<int> workerWatch = workerLifetime;
3217 worked->fetch_add( 1 );
3223 workerLifetime.reset();
3226 const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds( 5 );
3228 while( !workerWatch.expired() && std::chrono::steady_clock::now() < deadline )
3229 std::this_thread::sleep_for( std::chrono::milliseconds( 1 ) );
3236 worked->fetch_add( 1 );
3250 BOOST_CHECK( !completed );
3257 auto session = std::make_shared<KIGIT_COMMON>( repo.get() );
3258 std::weak_ptr<KIGIT_COMMON> watched = session;
3259 auto query = std::make_unique<KIGIT_QUERY>();
3261 auto entered = std::make_shared<std::promise<void>>();
3262 std::promise<void> release;
3263 auto ready = entered->get_future();
3264 auto proceed = release.get_future().share();
3265 bool completed =
false;
3271 entered->set_value();
3279 BOOST_REQUIRE( ready.wait_for( std::chrono::seconds( 5 ) ) == std::future_status::ready );
3281 const auto start = std::chrono::steady_clock::now();
3283 BOOST_CHECK( std::chrono::steady_clock::now() - start < std::chrono::seconds( 1 ) );
3284 BOOST_CHECK( !watched.expired() );
3285 release.set_value();
3290 return watched.expired();
3292 wxTheApp->ProcessPendingEvents();
3293 BOOST_CHECK( !completed );
3301 auto worked = std::make_shared<std::atomic<bool>>( false );
3302 std::thread::id completionThread;
3306 worked->store(
true );
3308 auto done = [&completionThread]()
3310 completionThread = std::this_thread::get_id();
3313 query.
Run(
nullptr, work, done );
3319 BOOST_CHECK( completionThread == std::this_thread::get_id() );
3321 auto session = std::make_shared<KIGIT_COMMON>(
nullptr );
3322 completionThread = {};
3323 query.
Run( session, work, done );
3329 BOOST_CHECK( completionThread == std::this_thread::get_id() );
3331 session->SetRepo( repo.get() );
3332 session->SetShuttingDown(
true );
3333 completionThread = {};
3334 query.
Run( session, work, done );
3340 BOOST_CHECK( completionThread == std::this_thread::get_id() );
3341 BOOST_CHECK( !worked->load() );
3343 Backend()->OrphanRegistry().JoinAll( std::chrono::seconds( 1 ) );
3344 completionThread = {};
3345 query.
Run( session, work, done );
3346 BOOST_CHECK( !query.
IsBusy() );
3347 BOOST_CHECK( completionThread == std::this_thread::get_id() );
3354 auto session = std::make_shared<KIGIT_COMMON>( repo.get() );
3355 auto query = std::make_unique<KIGIT_QUERY>();
3362 throw std::runtime_error(
"query failed" );
3374 BOOST_CHECK( session->TryLockAction().owns_lock() );
3382 auto session = std::make_shared<KIGIT_COMMON>( repo.get() );
3386 auto entered = std::make_shared<std::promise<void>>();
3387 std::promise<void> release;
3388 auto ready = entered->get_future();
3389 auto proceed = release.get_future().share();
3390 auto worked = std::make_shared<std::atomic<bool>>( false );
3391 unsigned completed = 0;
3397 entered->set_value();
3406 BOOST_REQUIRE( ready.wait_for( std::chrono::seconds( 5 ) ) == std::future_status::ready );
3411 worked->store(
true );
3417 release.set_value();
3424 BOOST_CHECK( worked->load() );
BranchResult SwitchToBranch(const wxString &aBranchName)
Switch to the specified branch.
A private scratch directory for extracting committed file versions to diff, removed when this object ...
bool IsOk() const
True when the scratch directory was created.
const wxString & Root() const
The scratch directory, for callers that lay out their own files (e.g. footprint libraries).
wxString Materialize(git_repository *aRepo, const wxString &aRef, const wxString &aRelPath, KICAD_DIFF::DOC_KIND aKind)
Extract aRelPath at aRef into a uniquely named slot and return the path to load, or an empty string w...
void SetExpectedTarget(const wxString &aBranch, const wxString &aHeadOid)
Pin the branch and full HEAD commit ID confirmed by the caller, so a queued push cannot publish a dif...
PushResult PerformPush(bool aForce=false)
Utility class with helper functions for project level git operations.
static bool WriteBlobToFile(git_repository *aRepo, const wxString &aRef, const wxString &aPath, const wxString &aDestPath)
Write a file as it exists at a git ref to a path on disk, so a committed version can be diffed.
static wxString ComputeSymlinkPreservingWorkDir(const wxString &aUserProjectPath, const wxString &aCanonicalWorkDir)
Compute a working directory path that preserves symlinks from the user's project path.
static bool IsWithinProjectPath(const wxString &aAbsPath, const wxString &aProjectPath)
Test whether an absolute file path lives inside the current project directory.
static std::optional< wxString > ProjectPrefix(const wxString &aWorkDir, const wxString &aProjectPath)
The project's folder inside the repository, ending in a slash.
Publishes a token for as long as its operation runs.
The project's git session.
std::pair< int, int > GetWorkingTreeLineStats(const std::set< wxString > &aPaths) const
std::shared_ptr< std::atomic< bool > > CANCEL_TOKEN
One token per operation.
GIT_CONN_TYPE GetConnType() const
std::vector< KIGIT_COMMIT_INFO > GetRecentCommits(size_t aMax=50, const KIGIT_HISTORY_FILTER &aFilter={}) const
bool HasUncommittedTrackedChanges(bool *aReadOk=nullptr) const
bool StashPop(size_t aIndex)
wxString GetRemote() const
wxString GetCurrentBranchName() const
wxString GetGitRootDirectory() const
KIGIT_TAG_DETAIL GetTagDetail(const wxString &aName) const
void SetSSHKey(const wxString &aSSHKey)
std::unique_lock< std::mutex > LockAction()
bool IsPullRebaseConfigured() const
True when the repository is set to rebase rather than merge when getting changes.
KIGIT_COMMIT_DETAIL GetCommitDetail(const wxString &aHash) const
bool HasUpstreamCommits() const
True when the upstream branch has commits the current branch does not.
bool CheckoutTag(const wxString &aName)
void SetUsername(const wxString &aUsername)
bool DeleteTag(const wxString &aName)
std::vector< wxString > GetProjectDirs()
Return a vector of project files in the repository.
void NotifyRefreshListeners()
std::vector< wxString > GetRecentCommitMessages(size_t aMax) const
bool RemoveRemote(const wxString &aName)
std::optional< size_t > GetUnmergedCommitCount(const wxString &aBranch) const
Commits in aBranch that HEAD does not contain. No value when either cannot be read.
bool CreateBranch(const wxString &aName, const wxString &aFrom=wxEmptyString)
wxString GetNextPublicKey()
By value, so the caller never holds a pointer into the vector.
bool AddRemote(const wxString &aName, const wxString &aUrl)
static CANCEL_TOKEN NewCancelToken()
void AddRefreshListener(void *aOwner, std::function< void()> aCallback)
std::vector< KIGIT_STASH_INFO > GetStashes() const
bool StartUnbornBranch(const wxString &aName)
Point HEAD at a branch that does not exist yet, so the first commit creates it.
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 ...
bool IsHeadUnborn() const
True while the repository has no commits, so HEAD names a branch that does not exist yet.
KIGIT_BRANCH_DETAIL GetBranchDetail(const wxString &aBranch) const
bool HasPushAndPullRemote() const
bool StashApply(size_t aIndex)
bool DiscardChanges(const std::vector< wxString > &aPaths, wxString &aError)
Put the given working directory relative paths back to their committed state.
wxString GetUsername() const
bool SetUserConfig(const wxString &aName, const wxString &aEmail)
KIGIT_STASH_DETAIL GetStashDetail(const wxString &aId) const
std::vector< std::pair< wxString, wxString > > GetRemotes() const
Returns every remote as a (name, URL) pair.
bool CreateTag(const wxString &aName, const wxString &aMessage=wxEmptyString)
void UpdateCurrentBranchInfo()
void RemoveRefreshListener(void *aOwner)
bool StashDrop(size_t aIndex)
bool HasLocalCommits() const
True when HEAD has commits its upstream does not, or has no upstream yet.
bool StashSave(const wxString &aMessage)
Set the uncommitted tracked changes aside as a new stash, as git stash push would.
wxDateTime GetLastFetchTime() const
static REMOTE_URL ParseRemoteURL(const wxString &aURL)
std::vector< KIGIT_TAG_INFO > GetTags() const
std::vector< KIGIT_BRANCH_INFO > GetBranches() const
Local and remote branches with their tip commits.
bool DeleteBranch(const wxString &aName)
bool IsPathConflicted(const wxString &aPath) const
True while aPath is one of the files a merge or rebase stopped on.
wxString GetRemotename() const
bool IsHeadDetached() const
True when HEAD points at a commit rather than a branch, as after checking out a tag.
void SetRemote(const wxString &aRemote)
bool SetRemoteUrl(const wxString &aName, const wxString &aUrl)
const std::vector< wxString > & GetErrorStrings() const
wxString GetErrorString()
Read repository state without blocking the UI.
bool IsBusy() const
Remains true until the running worker returns, including after Cancel().
void Cancel()
Suppress the running completion and discard pending work. Does not wait for a read.
void Run(std::shared_ptr< KIGIT_COMMON > aSession, std::function< void(KIGIT_COMMON &)> aWork, std::function< void()> aDone)
Run aWork under the session's action lock on a worker, then aDone on the UI thread.
void SetCancelToken(const KIGIT_COMMON::CANCEL_TOKEN &aToken)
Callbacks read this token rather than the session, so one operation's cancel cannot reach another.
bool IsThisOperationCancelled() const
void SetWaitForActionLock(bool aWait)
Worker transfers may wait for a background read to finish.
bool IsBusy() const
True while a transfer started here is still running.
bool Start(const std::string &aLabel, const std::function< void(const KIGIT_COMMON::CANCEL_TOKEN &)> &aWork, const std::function< void()> &aDone)
Run aWork on a background thread, then aDone on the UI thread.
Give each test a backend and a temporary directory.
static bool empty(const wxTextEntryBase *aCtrl)
static std::string ToStdString(const wxString &aStr)
GIT_WORKTREE_STATUS ReadGitWorktreeStatus(KIGIT_COMMON &aCommon, const wxString &aProjectPath)
Read tracked changes and Git LFS using a captured project path.
std::unique_ptr< git_tree, decltype([](git_tree *aTree) { git_tree_free(aTree); })> GitTreePtr
A unique pointer for git_tree objects with automatic cleanup.
void SortBranchesForDisplay(std::vector< KIGIT_BRANCH_INFO > &aBranches)
Put the current branch first, then local and remote branches with the newest tips first.
bool IsPrivateKeyEncrypted(const wxString &aKeyText)
Report whether an SSH private key is protected by a passphrase.
void SplitCommitMessage(const wxString &aMessage, wxString &aSummary, wxString &aDescription)
Split a commit message into its first line and the description after the first blank line.
wxString BranchNameError(const wxString &aName)
Empty when aName is a branch name git would accept, otherwise why it would not, in the words to show ...
std::unique_ptr< git_repository, decltype([](git_repository *aRepo) { git_repository_free(aRepo); })> GitRepositoryPtr
A unique pointer for git_repository objects with automatic cleanup.
GitUserConfig ResolveGitIdentity(const GIT_IDENTITY &aIdentity, const COMMON_SETTINGS::GIT &aPreferences, const wxString &aUserName)
Fill missing values from custom preferences, then use the OS user name if needed.
std::unique_ptr< git_commit, decltype([](git_commit *aCommit) { git_commit_free(aCommit); })> GitCommitPtr
A unique pointer for git_commit objects with automatic cleanup.
GIT_IDENTITY ReadGitIdentity(KIGIT_COMMON &aCommon)
Read on a worker while holding the session's action lock.
std::unique_ptr< git_config, decltype([](git_config *aConfig) { git_config_free(aConfig); })> GitConfigPtr
A unique pointer for git_config objects with automatic cleanup.
std::unique_ptr< git_reference, decltype([](git_reference *aRef) { git_reference_free(aRef); })> GitReferencePtr
A unique pointer for git_reference objects with automatic cleanup.
wxString ComposeCommitMessage(const wxString &aSummary, const wxString &aDescription, const wxString &aSignOff)
Join a summary line, an optional description and an optional sign-off into one commit message.
bool ValidateIdentity(const wxString &aName, const wxString &aEmail, wxString &aError)
Check a name and email for recording commits. False leaves the reason in aError.
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_object, decltype([](git_object *aObject) { git_object_free(aObject); })> GitObjectPtr
A unique pointer for git_object objects with automatic cleanup.
std::unique_ptr< git_remote, decltype([](git_remote *aRemote) { git_remote_free(aRemote); })> GitRemotePtr
A unique pointer for git_remote objects with automatic cleanup.
KIGIT::GitRepositoryPtr InitGitRepository(const wxString &aPath, const char *aBranch="main", const char *aAuthorName="QA Test", const char *aAuthorEmail="
[email protected]")
Pin the branch and author so repository setup does not depend on the user's Git config.
void WriteGitFile(const wxString &aRepoPath, const wxString &aRelativePath, const std::string &aContents)
Write test data without staging it. Create parent directories when needed.
git_oid CommitGitFile(git_repository *aRepo, const wxString &aPath, const wxString &aMessage)
Stage one file and commit the index. Other staged changes are kept.
Repository state used before an operation that requires a clean working tree.
std::vector< wxString > outsideProject
wxString RefusalMessage(const std::vector< wxString > &aUnsavedEditors, bool aAllowTrackedChanges=false) const
Empty when rewriting is allowed.
Repository values and whether Git can use them to record a commit.
std::optional< KIGIT_AHEAD_BEHIND > divergence
Comparison with upstream, if it could be read.
wxString upstream
The branch this one tracks, or empty.
std::vector< wxString > parents
Short hashes.
std::vector< KIGIT_COMMIT_FILE > files
A transport address with any embedded credentials extracted.
wxString url
Keeps the SSH username, scheme and port, but never a password.
wxString search
Keep only commits whose summary, author or hash contains this.
wxString branch
One branch, local or remote, or empty for every branch and HEAD.
wxString message
The tag's own message, or the commit's for a lightweight tag.
wxString email
Their email.
wxString name
The tag name.
wxString hash
Short hash of the tagged commit.
wxDateTime when
When the tag was made, or the commit for a lightweight tag.
wxString author
Who made the tag, or the commit for a lightweight tag.
bool found
False when no tag of that name points at a commit.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_CASE(GitRootDirectoryReturnsWorkdir)
VECTOR3I expected(15, 30, 45)
BOOST_TEST_CONTEXT("Test Clearance")
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")