github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/kbfsgit/runner_test.go (about) 1 // Copyright 2017 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package kbfsgit 6 7 import ( 8 "bytes" 9 "context" 10 "encoding/json" 11 "fmt" 12 "io" 13 "os" 14 "os/exec" 15 "path/filepath" 16 "runtime" 17 "strings" 18 "testing" 19 20 "github.com/keybase/client/go/kbfs/data" 21 "github.com/keybase/client/go/kbfs/libcontext" 22 "github.com/keybase/client/go/kbfs/libfs" 23 "github.com/keybase/client/go/kbfs/libgit" 24 "github.com/keybase/client/go/kbfs/libkbfs" 25 "github.com/keybase/client/go/kbfs/tlf" 26 "github.com/keybase/client/go/kbfs/tlfhandle" 27 "github.com/keybase/client/go/protocol/keybase1" 28 "github.com/stretchr/testify/require" 29 gogitcfg "gopkg.in/src-d/go-git.v4/config" 30 ) 31 32 type testErrput struct { 33 t *testing.T 34 } 35 36 func (te testErrput) Write(buf []byte) (int, error) { 37 te.t.Helper() 38 te.t.Log(string(buf)) 39 return 0, nil 40 } 41 42 func TestRunnerCapabilities(t *testing.T) { 43 ctx := libcontext.BackgroundContextWithCancellationDelayer() 44 config := libkbfs.MakeTestConfigOrBustLoggedInWithMode( 45 t, 0, libkbfs.InitSingleOp, "user1") 46 defer libkbfs.CheckConfigAndShutdown(ctx, t, config) 47 48 inputReader, inputWriter := io.Pipe() 49 defer inputWriter.Close() 50 go func() { 51 _, _ = inputWriter.Write([]byte("capabilities\n\n")) 52 }() 53 54 var output bytes.Buffer 55 r, err := newRunner(ctx, config, "origin", "keybase://private/user1/test", 56 "", inputReader, &output, testErrput{t}) 57 require.NoError(t, err) 58 err = r.processCommands(ctx) 59 require.NoError(t, err) 60 require.Equal(t, "fetch\npush\noption\n\n", output.String()) 61 } 62 63 func initConfigForRunner(t *testing.T) ( 64 ctx context.Context, config *libkbfs.ConfigLocal, tempdir string) { 65 ctx = libcontext.BackgroundContextWithCancellationDelayer() 66 config = libkbfs.MakeTestConfigOrBustLoggedInWithMode( 67 t, 0, libkbfs.InitSingleOp, "user1", "user2") 68 success := false 69 ctx = context.WithValue(ctx, libkbfs.CtxAllowNameKey, kbfsRepoDir) 70 71 tempdir, err := os.MkdirTemp(os.TempDir(), "journal_server") 72 require.NoError(t, err) 73 defer func() { 74 if !success { 75 os.RemoveAll(tempdir) 76 } 77 }() 78 79 err = config.EnableDiskLimiter(tempdir) 80 require.NoError(t, err) 81 err = config.EnableJournaling( 82 ctx, tempdir, libkbfs.TLFJournalSingleOpBackgroundWorkEnabled) 83 require.NoError(t, err) 84 85 success = true 86 return ctx, config, tempdir 87 } 88 89 func testRunnerInitRepo(t *testing.T, tlfType tlf.Type, typeString string) { 90 ctx, config, tempdir := initConfigForRunner(t) 91 defer libkbfs.CheckConfigAndShutdown(ctx, t, config) 92 defer os.RemoveAll(tempdir) 93 94 inputReader, inputWriter := io.Pipe() 95 defer inputWriter.Close() 96 go func() { 97 _, _ = inputWriter.Write([]byte("list\n\n")) 98 }() 99 100 h, err := tlfhandle.ParseHandle( 101 ctx, config.KBPKI(), config.MDOps(), config, "user1", tlfType) 102 require.NoError(t, err) 103 if tlfType != tlf.Public { 104 _, err = libgit.CreateRepoAndID(ctx, config, h, "test") 105 require.NoError(t, err) 106 } 107 108 var output bytes.Buffer 109 r, err := newRunner(ctx, config, "origin", 110 fmt.Sprintf("keybase://%s/user1/test", typeString), 111 "", inputReader, &output, testErrput{t}) 112 require.NoError(t, err) 113 err = r.processCommands(ctx) 114 require.NoError(t, err) 115 // No refs yet, including the HEAD symref. 116 require.Equal(t, output.String(), "\n") 117 118 // Now there should be a valid git repo stored in KBFS. Check the 119 // existence of the HEAD file to be sure. 120 fs, err := libfs.NewFS( 121 ctx, config, h, data.MasterBranch, ".kbfs_git/test", "", 122 keybase1.MDPriorityGit) 123 require.NoError(t, err) 124 head, err := fs.Open("HEAD") 125 require.NoError(t, err) 126 buf, err := io.ReadAll(head) 127 require.NoError(t, err) 128 require.Equal(t, "ref: refs/heads/master\n", string(buf)) 129 } 130 131 func TestRunnerInitRepoPrivate(t *testing.T) { 132 testRunnerInitRepo(t, tlf.Private, "private") 133 } 134 135 func TestRunnerInitRepoPublic(t *testing.T) { 136 testRunnerInitRepo(t, tlf.Public, "public") 137 } 138 139 func gitExec(t *testing.T, gitDir, workTree string, command ...string) { 140 cmd := exec.Command("git", 141 append([]string{"--git-dir", gitDir, "--work-tree", workTree}, 142 command...)...) 143 output, err := cmd.CombinedOutput() 144 require.NoError(t, err, string(output)) 145 } 146 147 func makeLocalRepoWithOneFileCustomCommitMsg(t *testing.T, 148 gitDir, filename, contents, branch, msg string) { 149 t.Logf("Make a new repo in %s with one file", gitDir) 150 err := os.WriteFile( 151 filepath.Join(gitDir, filename), []byte(contents), 0600) 152 require.NoError(t, err) 153 dotgit := filepath.Join(gitDir, ".git") 154 gitExec(t, dotgit, gitDir, "init") 155 156 if branch != "" { 157 gitExec(t, dotgit, gitDir, "checkout", "-b", branch) 158 } 159 160 gitExec(t, dotgit, gitDir, "add", filename) 161 gitExec(t, dotgit, gitDir, "-c", "user.name=Foo", 162 "-c", "user.email=foo@foo.com", "commit", "-a", "-m", msg) 163 } 164 165 func makeLocalRepoWithOneFile(t *testing.T, 166 gitDir, filename, contents, branch string) { 167 makeLocalRepoWithOneFileCustomCommitMsg( 168 t, gitDir, filename, contents, branch, "foo") 169 } 170 171 func addOneFileToRepoCustomCommitMsg(t *testing.T, gitDir, 172 filename, contents, msg string) { 173 t.Logf("Add a new file to %s", gitDir) 174 err := os.WriteFile( 175 filepath.Join(gitDir, filename), []byte(contents), 0600) 176 require.NoError(t, err) 177 dotgit := filepath.Join(gitDir, ".git") 178 179 gitExec(t, dotgit, gitDir, "add", filename) 180 gitExec(t, dotgit, gitDir, "-c", "user.name=Foo", 181 "-c", "user.email=foo@foo.com", "commit", "-a", "-m", msg) 182 } 183 184 func addOneFileToRepo(t *testing.T, gitDir, filename, contents string) { 185 addOneFileToRepoCustomCommitMsg( 186 t, gitDir, filename, contents, "foo") 187 } 188 189 func testPushWithTemplate(ctx context.Context, t *testing.T, 190 config libkbfs.Config, gitDir string, refspecs []string, 191 outputTemplate, tlfName string) { 192 // Use the runner to push the local data into the KBFS repo. 193 inputReader, inputWriter := io.Pipe() 194 defer inputWriter.Close() 195 go func() { 196 for _, refspec := range refspecs { 197 _, _ = inputWriter.Write([]byte(fmt.Sprintf("push %s\n", refspec))) 198 } 199 _, _ = inputWriter.Write([]byte("\n\n")) 200 }() 201 202 var output bytes.Buffer 203 r, err := newRunner(ctx, config, "origin", 204 fmt.Sprintf("keybase://private/%s/test", tlfName), 205 filepath.Join(gitDir, ".git"), inputReader, &output, testErrput{t}) 206 require.NoError(t, err) 207 err = r.processCommands(ctx) 208 require.NoError(t, err) 209 210 // The output can list refs in any order, so we need to compare 211 // maps rather than raw strings. 212 outputLines := strings.Split(output.String(), "\n") 213 outputMap := make(map[string]bool) 214 for _, line := range outputLines { 215 outputMap[line] = true 216 } 217 218 dsts := make([]interface{}, 0, len(refspecs)) 219 for _, refspec := range refspecs { 220 dsts = append(dsts, gogitcfg.RefSpec(refspec).Dst("")) 221 } 222 expectedOutput := fmt.Sprintf(outputTemplate, dsts...) 223 expectedOutputLines := strings.Split(expectedOutput, "\n") 224 expectedOutputMap := make(map[string]bool) 225 for _, line := range expectedOutputLines { 226 expectedOutputMap[line] = true 227 } 228 229 require.Equal(t, expectedOutputMap, outputMap) 230 } 231 232 func testPush(ctx context.Context, t *testing.T, config libkbfs.Config, 233 gitDir, refspec string) { 234 testPushWithTemplate(ctx, t, config, gitDir, []string{refspec}, 235 "ok %s\n\n", "user1") 236 } 237 238 func testListAndGetHeadsWithNameWithPush( 239 ctx context.Context, t *testing.T, config libkbfs.Config, gitDir string, 240 expectedRefs []string, tlfName string, forPush bool) (heads []string) { 241 inputReader, inputWriter := io.Pipe() 242 defer inputWriter.Close() 243 go func() { 244 p := "" 245 if forPush { 246 p = " for-push" 247 } 248 _, _ = inputWriter.Write([]byte(fmt.Sprintf("list%s\n\n", p))) 249 }() 250 251 var output bytes.Buffer 252 r, err := newRunner(ctx, config, "origin", 253 fmt.Sprintf("keybase://private/%s/test", tlfName), 254 filepath.Join(gitDir, ".git"), inputReader, &output, testErrput{t}) 255 require.NoError(t, err) 256 err = r.processCommands(ctx) 257 require.NoError(t, err) 258 listLines := strings.Split(output.String(), "\n") 259 t.Log(listLines) 260 require.Len(t, listLines, len(expectedRefs)+2 /* extra blank line */) 261 refs := make(map[string]string, len(expectedRefs)) 262 for _, line := range listLines { 263 if line == "" { 264 continue 265 } 266 refParts := strings.Split(line, " ") 267 require.Len(t, refParts, 2) 268 refs[refParts[1]] = refParts[0] 269 } 270 271 for _, expectedRef := range expectedRefs { 272 head, ok := refs[expectedRef] 273 require.True(t, ok) 274 heads = append(heads, head) 275 } 276 return heads 277 } 278 279 func testListAndGetHeadsWithName(ctx context.Context, t *testing.T, 280 config libkbfs.Config, gitDir string, expectedRefs []string, 281 tlfName string) (heads []string) { 282 return testListAndGetHeadsWithNameWithPush( 283 ctx, t, config, gitDir, expectedRefs, tlfName, false) 284 } 285 286 func testListAndGetHeads(ctx context.Context, t *testing.T, 287 config libkbfs.Config, gitDir string, expectedRefs []string) ( 288 heads []string) { 289 return testListAndGetHeadsWithName( 290 ctx, t, config, gitDir, expectedRefs, "user1") 291 } 292 293 // This tests pushing code to a bare repo stored in KBFS, and pulling 294 // code from that bare repo into a new working tree. This is a simple 295 // version of how the full KBFS Git system will work. Specifically, 296 // this test does the following: 297 // 298 // 1) Initializes a new repo on the local file system with one file. 299 // 2) Initializes a new bare repo in KBFS. 300 // 3) User pushes from that repo into the remote KBFS repo. 301 // 4) Initializes a second new repo on the local file system. 302 // 5) User pulls from the remote KBFS repo into the second repo. 303 func testRunnerPushFetch(t *testing.T, cloning bool, secondRepoHasBranch bool) { 304 ctx, config, tempdir := initConfigForRunner(t) 305 defer os.RemoveAll(tempdir) 306 defer libkbfs.CheckConfigAndShutdown(ctx, t, config) 307 308 git1, err := os.MkdirTemp(os.TempDir(), "kbfsgittest") 309 require.NoError(t, err) 310 defer os.RemoveAll(git1) 311 312 makeLocalRepoWithOneFile(t, git1, "foo", "hello", "") 313 314 h, err := tlfhandle.ParseHandle( 315 ctx, config.KBPKI(), config.MDOps(), config, "user1", tlf.Private) 316 require.NoError(t, err) 317 _, err = libgit.CreateRepoAndID(ctx, config, h, "test") 318 require.NoError(t, err) 319 320 testPush(ctx, t, config, git1, "refs/heads/master:refs/heads/master") 321 322 git2, err := os.MkdirTemp(os.TempDir(), "kbfsgittest") 323 require.NoError(t, err) 324 defer os.RemoveAll(git2) 325 326 t.Logf("Make a new repo in %s to clone from the KBFS repo", git2) 327 dotgit2 := filepath.Join(git2, ".git") 328 gitExec(t, dotgit2, git2, "init") 329 330 // Find out the head hash. 331 heads := testListAndGetHeads(ctx, t, config, git2, 332 []string{"refs/heads/master", "HEAD"}) 333 334 cloningStr := "" 335 cloningRetStr := "" 336 if cloning { 337 cloningStr = "option cloning true\n" 338 cloningRetStr = "ok\n" 339 } else if secondRepoHasBranch { 340 makeLocalRepoWithOneFile(t, git2, "foo2", "hello2", "b") 341 } 342 343 // Use the runner to fetch the KBFS data into the new git repo. 344 inputReader, inputWriter := io.Pipe() 345 defer inputWriter.Close() 346 go func() { 347 _, _ = inputWriter.Write([]byte(fmt.Sprintf( 348 "%sfetch %s refs/heads/master\n\n\n", cloningStr, heads[0]))) 349 }() 350 351 var output3 bytes.Buffer 352 r, err := newRunner(ctx, config, "origin", "keybase://private/user1/test", 353 dotgit2, inputReader, &output3, testErrput{t}) 354 require.NoError(t, err) 355 err = r.processCommands(ctx) 356 require.NoError(t, err) 357 // Just one symref, from HEAD to master (and master has no commits yet). 358 require.Equal(t, cloningRetStr+"\n", output3.String()) 359 360 // Checkout the head directly (fetching directly via the runner 361 // doesn't leave any refs, those would normally be created by the 362 // `git` process that invokes the runner). 363 gitExec(t, dotgit2, git2, "checkout", heads[0]) 364 365 data, err := os.ReadFile(filepath.Join(git2, "foo")) 366 require.NoError(t, err) 367 require.Equal(t, "hello", string(data)) 368 } 369 370 func TestRunnerPushFetch(t *testing.T) { 371 t.Skip("KBFS-3778: currently flaking") 372 testRunnerPushFetch(t, false, false) 373 } 374 375 func TestRunnerPushClone(t *testing.T) { 376 testRunnerPushFetch(t, true, false) 377 } 378 379 func TestRunnerPushFetchWithBranch(t *testing.T) { 380 t.Skip("KBFS-3589: currently flaking") 381 testRunnerPushFetch(t, false, true) 382 } 383 384 func TestRunnerListForPush(t *testing.T) { 385 ctx, config, tempdir := initConfigForRunner(t) 386 defer os.RemoveAll(tempdir) 387 defer libkbfs.CheckConfigAndShutdown(ctx, t, config) 388 389 git1, err := os.MkdirTemp(os.TempDir(), "kbfsgittest") 390 require.NoError(t, err) 391 defer os.RemoveAll(git1) 392 393 makeLocalRepoWithOneFile(t, git1, "foo", "hello", "") 394 395 h, err := tlfhandle.ParseHandle( 396 ctx, config.KBPKI(), config.MDOps(), config, "user1", tlf.Private) 397 require.NoError(t, err) 398 _, err = libgit.CreateRepoAndID(ctx, config, h, "test") 399 require.NoError(t, err) 400 401 testPush(ctx, t, config, git1, "refs/heads/master:refs/heads/master") 402 403 // Make sure we don't list symbolic references (see KBFS-1970). 404 _ = testListAndGetHeadsWithNameWithPush( 405 ctx, t, config, git1, []string{"refs/heads/master"}, "user1", true) 406 } 407 408 func TestRunnerDeleteBranch(t *testing.T) { 409 ctx, config, tempdir := initConfigForRunner(t) 410 defer os.RemoveAll(tempdir) 411 defer libkbfs.CheckConfigAndShutdown(ctx, t, config) 412 413 git, err := os.MkdirTemp(os.TempDir(), "kbfsgittest") 414 require.NoError(t, err) 415 defer os.RemoveAll(git) 416 417 makeLocalRepoWithOneFile(t, git, "foo", "hello", "") 418 419 h, err := tlfhandle.ParseHandle( 420 ctx, config.KBPKI(), config.MDOps(), config, "user1", tlf.Private) 421 require.NoError(t, err) 422 _, err = libgit.CreateRepoAndID(ctx, config, h, "test") 423 require.NoError(t, err) 424 425 testPush(ctx, t, config, git, "refs/heads/master:refs/heads/master") 426 testPush(ctx, t, config, git, "refs/heads/master:refs/heads/test") 427 428 // Make sure there are 2 remote branches. 429 testListAndGetHeads(ctx, t, config, git, 430 []string{"refs/heads/master", "refs/heads/test", "HEAD"}) 431 432 // Delete the test branch and make sure it goes away. 433 testPush(ctx, t, config, git, ":refs/heads/test") 434 testListAndGetHeads(ctx, t, config, git, 435 []string{"refs/heads/master", "HEAD"}) 436 } 437 438 func TestRunnerExitEarlyOnEOF(t *testing.T) { 439 ctx, config, tempdir := initConfigForRunner(t) 440 defer os.RemoveAll(tempdir) 441 defer libkbfs.CheckConfigAndShutdown(ctx, t, config) 442 443 git, err := os.MkdirTemp(os.TempDir(), "kbfsgittest") 444 require.NoError(t, err) 445 defer os.RemoveAll(git) 446 447 makeLocalRepoWithOneFile(t, git, "foo", "hello", "") 448 449 h, err := tlfhandle.ParseHandle( 450 ctx, config.KBPKI(), config.MDOps(), config, "user1", tlf.Private) 451 require.NoError(t, err) 452 rootNode, _, err := config.KBFSOps().GetOrCreateRootNode( 453 ctx, h, data.MasterBranch) 454 require.NoError(t, err) 455 _, err = libgit.CreateRepoAndID(ctx, config, h, "test") 456 require.NoError(t, err) 457 458 // Pause journal to force the processing to pause. 459 jManager, err := libkbfs.GetJournalManager(config) 460 require.NoError(t, err) 461 jManager.PauseBackgroundWork(ctx, rootNode.GetFolderBranch().Tlf) 462 463 // Input a full push batch, but let the reader EOF without giving 464 // the final \n. 465 input := bytes.NewBufferString( 466 "push refs/heads/master:refs/heads/master\n\n") 467 var output bytes.Buffer 468 r, err := newRunner(ctx, config, "origin", "keybase://private/user1/test", 469 filepath.Join(git, ".git"), input, &output, testErrput{t}) 470 require.NoError(t, err) 471 472 // Make sure we don't hang when EOF comes early. 473 err = r.processCommands(ctx) 474 require.NoError(t, err) 475 476 err = config.KBFSOps().SyncAll(ctx, rootNode.GetFolderBranch()) 477 require.NoError(t, err) 478 } 479 480 func TestForcePush(t *testing.T) { 481 ctx, config, tempdir := initConfigForRunner(t) 482 defer os.RemoveAll(tempdir) 483 defer libkbfs.CheckConfigAndShutdown(ctx, t, config) 484 485 git, err := os.MkdirTemp(os.TempDir(), "kbfsgittest") 486 require.NoError(t, err) 487 defer os.RemoveAll(git) 488 489 makeLocalRepoWithOneFile(t, git, "foo", "hello", "") 490 491 h, err := tlfhandle.ParseHandle( 492 ctx, config.KBPKI(), config.MDOps(), config, "user1", tlf.Private) 493 require.NoError(t, err) 494 _, err = libgit.CreateRepoAndID(ctx, config, h, "test") 495 require.NoError(t, err) 496 497 testPush(ctx, t, config, git, "refs/heads/master:refs/heads/master") 498 499 // Push a second file. 500 addOneFileToRepo(t, git, "foo2", "hello2") 501 testPush(ctx, t, config, git, "refs/heads/master:refs/heads/master") 502 503 // Now revert to the old commit and add a different file. 504 dotgit := filepath.Join(git, ".git") 505 gitExec(t, dotgit, git, "reset", "--hard", "HEAD~1") 506 507 addOneFileToRepo(t, git, "foo3", "hello3") 508 // A non-force push should fail. 509 testPushWithTemplate( 510 ctx, t, config, git, []string{"refs/heads/master:refs/heads/master"}, 511 "error %s some refs were not updated\n\n", "user1") 512 // But a force push should work 513 testPush(ctx, t, config, git, "+refs/heads/master:refs/heads/master") 514 } 515 516 func TestPushAllWithPackedRefs(t *testing.T) { 517 ctx, config, tempdir := initConfigForRunner(t) 518 defer os.RemoveAll(tempdir) 519 defer libkbfs.CheckConfigAndShutdown(ctx, t, config) 520 521 git, err := os.MkdirTemp(os.TempDir(), "kbfsgittest") 522 require.NoError(t, err) 523 defer os.RemoveAll(git) 524 525 makeLocalRepoWithOneFile(t, git, "foo", "hello", "") 526 527 dotgit := filepath.Join(git, ".git") 528 gitExec(t, dotgit, git, "pack-refs", "--all") 529 530 h, err := tlfhandle.ParseHandle( 531 ctx, config.KBPKI(), config.MDOps(), config, "user1", tlf.Private) 532 require.NoError(t, err) 533 _, err = libgit.CreateRepoAndID(ctx, config, h, "test") 534 require.NoError(t, err) 535 536 testPush(ctx, t, config, git, "refs/heads/master:refs/heads/master") 537 538 // Should be able to update the branch in a non-force way, even 539 // though it's a packed-ref. 540 addOneFileToRepo(t, git, "foo2", "hello2") 541 testPush(ctx, t, config, git, "refs/heads/master:refs/heads/master") 542 } 543 544 func TestPushSomeWithPackedRefs(t *testing.T) { 545 ctx, config, tempdir := initConfigForRunner(t) 546 defer os.RemoveAll(tempdir) 547 defer libkbfs.CheckConfigAndShutdown(ctx, t, config) 548 549 git, err := os.MkdirTemp(os.TempDir(), "kbfsgittest") 550 require.NoError(t, err) 551 defer os.RemoveAll(git) 552 553 h, err := tlfhandle.ParseHandle( 554 ctx, config.KBPKI(), config.MDOps(), config, "user1", tlf.Private) 555 require.NoError(t, err) 556 _, err = libgit.CreateRepoAndID(ctx, config, h, "test") 557 require.NoError(t, err) 558 559 makeLocalRepoWithOneFile(t, git, "foo", "hello", "") 560 561 // Make a non-branch ref (under refs/test). This ref would not be 562 // pushed as part of `git push --all`. 563 dotgit := filepath.Join(git, ".git") 564 gitExec(t, dotgit, git, "push", git, "HEAD:refs/test/ref") 565 566 addOneFileToRepo(t, git, "foo2", "hello2") 567 568 // Make a tag, and then another branch. 569 gitExec(t, dotgit, git, "tag", "v0") 570 gitExec(t, dotgit, git, "checkout", "-b", "test") 571 addOneFileToRepo(t, git, "foo3", "hello3") 572 573 // Simulate a `git push --all`, and make sure `refs/test/ref` 574 // isn't pushed. 575 testPushWithTemplate( 576 ctx, t, config, git, []string{ 577 "refs/heads/master:refs/heads/master", 578 "refs/heads/test:refs/heads/test", 579 "refs/tags/v0:refs/tags/v0", 580 }, 581 "ok %s\nok %s\nok %s\n\n", "user1") 582 testListAndGetHeads(ctx, t, config, git, 583 []string{ 584 "refs/heads/master", 585 "refs/heads/test", 586 "refs/tags/v0", 587 "HEAD", 588 }) 589 590 // Make sure we can push over a packed-refs ref. 591 addOneFileToRepo(t, git, "foo4", "hello4") 592 testPush(ctx, t, config, git, "refs/heads/test:refs/heads/test") 593 } 594 595 func testCloneIntoNewLocalRepo( 596 ctx context.Context, t *testing.T, config libkbfs.Config, 597 tlfName string) string { 598 git, err := os.MkdirTemp(os.TempDir(), "kbfsgittest") 599 require.NoError(t, err) 600 success := false 601 defer func() { 602 if !success { 603 os.RemoveAll(git) 604 } 605 }() 606 607 dotgit := filepath.Join(git, ".git") 608 gitExec(t, dotgit, git, "init") 609 610 heads := testListAndGetHeadsWithName(ctx, t, config, git, 611 []string{"refs/heads/master", "HEAD"}, tlfName) 612 613 inputReader, inputWriter := io.Pipe() 614 defer inputWriter.Close() 615 go func() { 616 _, _ = inputWriter.Write([]byte(fmt.Sprintf( 617 "option cloning true\n"+ 618 "fetch %s refs/heads/master\n\n\n", heads[0]))) 619 }() 620 621 var output bytes.Buffer 622 r2, err := newRunner(ctx, config, "origin", 623 fmt.Sprintf("keybase://private/%s/test", tlfName), 624 dotgit, inputReader, &output, testErrput{t}) 625 require.NoError(t, err) 626 err = r2.processCommands(ctx) 627 require.NoError(t, err) 628 // Just one symref, from HEAD to master (and master has no commits yet). 629 require.Equal(t, "ok\n\n", output.String()) 630 631 // Checkout the head directly (fetching directly via the runner 632 // doesn't leave any refs, those would normally be created by the 633 // `git` process that invokes the runner). 634 gitExec(t, dotgit, git, "checkout", heads[0]) 635 636 success = true 637 return git 638 } 639 640 func TestRunnerReaderClone(t *testing.T) { 641 ctx, config, tempdir := initConfigForRunner(t) 642 defer os.RemoveAll(tempdir) 643 defer libkbfs.CheckConfigAndShutdown(ctx, t, config) 644 645 git1, err := os.MkdirTemp(os.TempDir(), "kbfsgittest") 646 require.NoError(t, err) 647 defer os.RemoveAll(git1) 648 649 makeLocalRepoWithOneFile(t, git1, "foo", "hello", "") 650 testPushWithTemplate(ctx, t, config, git1, 651 []string{"refs/heads/master:refs/heads/master"}, 652 "ok %s\n\n", "user1#user2") 653 654 // Make sure the reader can clone it. 655 tempdir2, err := os.MkdirTemp(os.TempDir(), "journal_server") 656 require.NoError(t, err) 657 defer os.RemoveAll(tempdir2) 658 config2 := libkbfs.ConfigAsUser(config, "user2") 659 defer libkbfs.CheckConfigAndShutdown(ctx, t, config2) 660 err = config2.EnableDiskLimiter(tempdir2) 661 require.NoError(t, err) 662 err = config2.EnableJournaling( 663 ctx, tempdir2, libkbfs.TLFJournalSingleOpBackgroundWorkEnabled) 664 require.NoError(t, err) 665 666 git2 := testCloneIntoNewLocalRepo(ctx, t, config2, "user1#user2") 667 defer os.RemoveAll(git2) 668 669 data, err := os.ReadFile(filepath.Join(git2, "foo")) 670 require.NoError(t, err) 671 require.Equal(t, "hello", string(data)) 672 } 673 674 func TestRunnerDeletePackedRef(t *testing.T) { 675 ctx, config, tempdir := initConfigForRunner(t) 676 defer os.RemoveAll(tempdir) 677 defer libkbfs.CheckConfigAndShutdown(ctx, t, config) 678 679 git1, err := os.MkdirTemp(os.TempDir(), "kbfsgittest") 680 require.NoError(t, err) 681 defer os.RemoveAll(git1) 682 dotgit1 := filepath.Join(git1, ".git") 683 684 makeLocalRepoWithOneFile(t, git1, "foo", "hello", "b") 685 686 // Add a different file to master. 687 gitExec(t, dotgit1, git1, "checkout", "-b", "master") 688 addOneFileToRepo(t, git1, "foo2", "hello2") 689 690 gitExec(t, dotgit1, git1, "pack-refs", "--all") 691 692 h, err := tlfhandle.ParseHandle( 693 ctx, config.KBPKI(), config.MDOps(), config, "user1", tlf.Private) 694 require.NoError(t, err) 695 _, err = libgit.CreateRepoAndID(ctx, config, h, "test") 696 require.NoError(t, err) 697 698 testPushWithTemplate( 699 ctx, t, config, git1, []string{ 700 "refs/heads/master:refs/heads/master", 701 "refs/heads/b:refs/heads/b", 702 }, 703 "ok %s\nok %s\n\n", "user1") 704 705 testListAndGetHeadsWithName(ctx, t, config, git1, 706 []string{"refs/heads/master", "refs/heads/b", "HEAD"}, "user1") 707 708 // Add a new file to the branch and push, to create a loose ref. 709 gitExec(t, dotgit1, git1, "checkout", "b") 710 addOneFileToRepo(t, git1, "foo3", "hello3") 711 testPush(ctx, t, config, git1, "refs/heads/b:refs/heads/b") 712 713 // Now delete. 714 testPush(ctx, t, config, git1, ":refs/heads/b") 715 testListAndGetHeadsWithName(ctx, t, config, git1, 716 []string{"refs/heads/master", "HEAD"}, "user1") 717 } 718 719 func TestPushcertOptions(t *testing.T) { 720 ctx, config, tempdir := initConfigForRunner(t) 721 defer os.RemoveAll(tempdir) 722 defer libkbfs.CheckConfigAndShutdown(ctx, t, config) 723 724 git, err := os.MkdirTemp(os.TempDir(), "kbfsgittest") 725 require.NoError(t, err) 726 defer os.RemoveAll(git) 727 dotgit := filepath.Join(git, ".git") 728 729 checkPushcert := func(option, expected string) { 730 inputReader, inputWriter := io.Pipe() 731 defer inputWriter.Close() 732 go func() { 733 _, _ = inputWriter.Write([]byte(fmt.Sprintf( 734 "option pushcert %s\n\n", option))) 735 }() 736 737 var output bytes.Buffer 738 r, err := newRunner(ctx, config, "origin", 739 "keybase://private/user1/test", 740 dotgit, inputReader, &output, testErrput{t}) 741 require.NoError(t, err) 742 err = r.processCommands(ctx) 743 require.NoError(t, err) 744 // if-asked is supported, but signing will never be asked for. 745 require.Equal(t, fmt.Sprintf("%s\n", expected), output.String()) 746 } 747 748 checkPushcert("if-asked", "ok") 749 checkPushcert("true", "unsupported") 750 checkPushcert("false", "ok") 751 } 752 753 func TestPackRefsAndOverwritePackedRef(t *testing.T) { 754 ctx, config, tempdir := initConfigForRunner(t) 755 defer os.RemoveAll(tempdir) 756 defer libkbfs.CheckConfigAndShutdown(ctx, t, config) 757 758 git1, err := os.MkdirTemp(os.TempDir(), "kbfsgittest") 759 require.NoError(t, err) 760 defer os.RemoveAll(git1) 761 762 // Make shared repo with 2 branches. 763 makeLocalRepoWithOneFile(t, git1, "foo", "hello", "") 764 testPushWithTemplate(ctx, t, config, git1, 765 []string{"refs/heads/master:refs/heads/master"}, 766 "ok %s\n\n", "user1,user2") 767 testPushWithTemplate(ctx, t, config, git1, 768 []string{"refs/heads/master:refs/heads/test"}, 769 "ok %s\n\n", "user1,user2") 770 771 // Config for the second user. 772 tempdir2, err := os.MkdirTemp(os.TempDir(), "journal_server") 773 require.NoError(t, err) 774 defer os.RemoveAll(tempdir2) 775 config2 := libkbfs.ConfigAsUser(config, "user2") 776 defer libkbfs.CheckConfigAndShutdown(ctx, t, config2) 777 err = config2.EnableDiskLimiter(tempdir2) 778 require.NoError(t, err) 779 err = config2.EnableJournaling( 780 ctx, tempdir2, libkbfs.TLFJournalSingleOpBackgroundWorkEnabled) 781 require.NoError(t, err) 782 783 git2, err := os.MkdirTemp(os.TempDir(), "kbfsgittest") 784 require.NoError(t, err) 785 defer os.RemoveAll(git2) 786 787 heads := testListAndGetHeadsWithName(ctx, t, config2, git2, 788 []string{"refs/heads/master", "refs/heads/test", "HEAD"}, "user1,user2") 789 require.Equal(t, heads[0], heads[1]) 790 791 // Have the second user refpack, but stall it after it takes the lock. 792 packOnStalled, packUnstall, packCtx := libkbfs.StallMDOp( 793 ctx, config2, libkbfs.StallableMDAfterGetRange, 1) 794 packErrCh := make(chan error) 795 h, err := tlfhandle.ParseHandle( 796 ctx, config2.KBPKI(), config.MDOps(), config, "user1,user2", 797 tlf.Private) 798 require.NoError(t, err) 799 go func() { 800 packErrCh <- libgit.GCRepo( 801 packCtx, config2, h, "test", libgit.GCOptions{ 802 MaxLooseRefs: 0, 803 MaxObjectPacks: -1, 804 }) 805 }() 806 select { 807 case <-packOnStalled: 808 case <-ctx.Done(): 809 t.Fatal(ctx.Err()) 810 } 811 812 // While the second user is stalled, have the first user update 813 // one of the refs. 814 addOneFileToRepo(t, git1, "foo2", "hello2") 815 testPushWithTemplate(ctx, t, config, git1, 816 []string{"refs/heads/master:refs/heads/test"}, 817 "ok %s\n\n", "user1,user2") 818 819 close(packUnstall) 820 select { 821 case err := <-packErrCh: 822 require.NoError(t, err) 823 case <-ctx.Done(): 824 t.Fatal(ctx.Err()) 825 } 826 827 rootNode, _, err := config2.KBFSOps().GetOrCreateRootNode( 828 ctx, h, data.MasterBranch) 829 require.NoError(t, err) 830 err = config2.KBFSOps().SyncFromServer( 831 ctx, rootNode.GetFolderBranch(), nil) 832 require.NoError(t, err) 833 heads = testListAndGetHeadsWithName(ctx, t, config2, git2, 834 []string{"refs/heads/master", "refs/heads/test", "HEAD"}, "user1,user2") 835 require.NotEqual(t, heads[0], heads[1]) 836 } 837 838 func TestPackRefsAndDeletePackedRef(t *testing.T) { 839 ctx, config, tempdir := initConfigForRunner(t) 840 defer os.RemoveAll(tempdir) 841 defer libkbfs.CheckConfigAndShutdown(ctx, t, config) 842 843 git1, err := os.MkdirTemp(os.TempDir(), "kbfsgittest") 844 require.NoError(t, err) 845 defer os.RemoveAll(git1) 846 dotgit1 := filepath.Join(git1, ".git") 847 848 // Make shared repo with 2 branches. Make sure there's an initial 849 // pack-refs file. 850 makeLocalRepoWithOneFile(t, git1, "foo", "hello", "") 851 gitExec(t, dotgit1, git1, "pack-refs", "--all") 852 testPushWithTemplate(ctx, t, config, git1, 853 []string{"refs/heads/master:refs/heads/master"}, 854 "ok %s\n\n", "user1,user2") 855 testPushWithTemplate(ctx, t, config, git1, 856 []string{"refs/heads/master:refs/heads/test"}, 857 "ok %s\n\n", "user1,user2") 858 859 // Config for the second user. 860 tempdir2, err := os.MkdirTemp(os.TempDir(), "journal_server") 861 require.NoError(t, err) 862 defer os.RemoveAll(tempdir2) 863 config2 := libkbfs.ConfigAsUser(config, "user2") 864 defer libkbfs.CheckConfigAndShutdown(ctx, t, config2) 865 err = config2.EnableDiskLimiter(tempdir2) 866 require.NoError(t, err) 867 err = config2.EnableJournaling( 868 ctx, tempdir2, libkbfs.TLFJournalSingleOpBackgroundWorkEnabled) 869 require.NoError(t, err) 870 871 git2, err := os.MkdirTemp(os.TempDir(), "kbfsgittest") 872 require.NoError(t, err) 873 defer os.RemoveAll(git2) 874 875 heads := testListAndGetHeadsWithName(ctx, t, config2, git2, 876 []string{"refs/heads/master", "refs/heads/test", "HEAD"}, "user1,user2") 877 require.Equal(t, heads[0], heads[1]) 878 879 // Have the second user refpack, but stall it after it takes the lock. 880 packOnStalled, packUnstall, packCtx := libkbfs.StallMDOp( 881 ctx, config2, libkbfs.StallableMDAfterGetRange, 1) 882 packErrCh := make(chan error) 883 h, err := tlfhandle.ParseHandle( 884 ctx, config2.KBPKI(), config.MDOps(), config, "user1,user2", 885 tlf.Private) 886 require.NoError(t, err) 887 go func() { 888 packErrCh <- libgit.GCRepo( 889 packCtx, config2, h, "test", libgit.GCOptions{ 890 MaxLooseRefs: 0, 891 MaxObjectPacks: -1, 892 }) 893 }() 894 select { 895 case <-packOnStalled: 896 case <-ctx.Done(): 897 t.Fatal(ctx.Err()) 898 } 899 900 // While the second user is stalled, have the first user delete 901 // one of the refs. Wait until it tries to get the lock, and then 902 // release the pack-refs call. 903 deleteOnStalled, deleteUnstall, deleteCtx := libkbfs.StallMDOp( 904 ctx, config, libkbfs.StallableMDGetRange, 1) 905 inputReader, inputWriter := io.Pipe() 906 defer inputWriter.Close() 907 go func() { 908 _, _ = inputWriter.Write([]byte("push :refs/heads/test\n")) 909 _, _ = inputWriter.Write([]byte("\n\n")) 910 }() 911 912 var output bytes.Buffer 913 deleteRunner, err := newRunner(ctx, config, "origin", 914 "keybase://private/user1,user2/test", 915 dotgit1, inputReader, &output, testErrput{t}) 916 require.NoError(t, err) 917 deleteErrCh := make(chan error) 918 go func() { 919 deleteErrCh <- deleteRunner.processCommands(deleteCtx) 920 }() 921 select { 922 case <-deleteOnStalled: 923 case <-ctx.Done(): 924 t.Fatal(ctx.Err()) 925 } 926 // Release it, and it should block on getting the lock. 927 close(deleteUnstall) 928 929 // Now let the pack-refs finish. 930 close(packUnstall) 931 select { 932 case err := <-packErrCh: 933 require.NoError(t, err) 934 case <-ctx.Done(): 935 t.Fatal(ctx.Err()) 936 } 937 938 // And the delete should finish right after. 939 select { 940 case err := <-deleteErrCh: 941 require.NoError(t, err) 942 case <-ctx.Done(): 943 t.Fatal(ctx.Err()) 944 } 945 946 rootNode, _, err := config2.KBFSOps().GetOrCreateRootNode( 947 ctx, h, data.MasterBranch) 948 require.NoError(t, err) 949 err = config2.KBFSOps().SyncFromServer( 950 ctx, rootNode.GetFolderBranch(), nil) 951 require.NoError(t, err) 952 testListAndGetHeadsWithName(ctx, t, config2, git2, 953 []string{"refs/heads/master", "HEAD"}, "user1,user2") 954 } 955 956 func TestRepackObjects(t *testing.T) { 957 ctx, config, tempdir := initConfigForRunner(t) 958 defer os.RemoveAll(tempdir) 959 defer libkbfs.CheckConfigAndShutdown(ctx, t, config) 960 961 git, err := os.MkdirTemp(os.TempDir(), "kbfsgittest") 962 require.NoError(t, err) 963 defer os.RemoveAll(git) 964 965 h, err := tlfhandle.ParseHandle( 966 ctx, config.KBPKI(), config.MDOps(), config, "user1", tlf.Private) 967 require.NoError(t, err) 968 _, err = libgit.CreateRepoAndID(ctx, config, h, "test") 969 require.NoError(t, err) 970 971 // Make a few pushes to make a few object pack files. 972 makeLocalRepoWithOneFile(t, git, "foo", "hello", "") 973 testPush(ctx, t, config, git, "refs/heads/master:refs/heads/master") 974 addOneFileToRepo(t, git, "foo2", "hello2") 975 testPush(ctx, t, config, git, "refs/heads/master:refs/heads/master") 976 addOneFileToRepo(t, git, "foo3", "hello3") 977 testPush(ctx, t, config, git, "refs/heads/master:refs/heads/master") 978 addOneFileToRepo(t, git, "foo4", "hello4") 979 testPush(ctx, t, config, git, "refs/heads/master:refs/heads/master") 980 981 fs, _, err := libgit.GetRepoAndID(ctx, config, h, "test", "") 982 require.NoError(t, err) 983 984 storage, err := libgit.NewGitConfigWithoutRemotesStorer(fs) 985 require.NoError(t, err) 986 packs, err := storage.ObjectPacks() 987 require.NoError(t, err) 988 numObjectPacks := len(packs) 989 require.Equal(t, 3, numObjectPacks) 990 991 // Re-pack them all into one. 992 err = libgit.GCRepo( 993 ctx, config, h, "test", libgit.GCOptions{ 994 MaxLooseRefs: 100, 995 MaxObjectPacks: 0, 996 }) 997 require.NoError(t, err) 998 999 packs, err = storage.ObjectPacks() 1000 require.NoError(t, err) 1001 numObjectPacks = len(packs) 1002 require.Equal(t, 1, numObjectPacks) 1003 1004 // Check that a second clone looks correct. 1005 git2 := testCloneIntoNewLocalRepo(ctx, t, config, "user1") 1006 defer os.RemoveAll(git2) 1007 1008 checkFile := func(name, expectedData string) { 1009 data, err := os.ReadFile(filepath.Join(git2, name)) 1010 require.NoError(t, err) 1011 require.Equal(t, expectedData, string(data)) 1012 } 1013 checkFile("foo", "hello") 1014 checkFile("foo2", "hello2") 1015 checkFile("foo3", "hello3") 1016 checkFile("foo4", "hello4") 1017 } 1018 1019 func testHandlePushBatch(ctx context.Context, t *testing.T, 1020 config libkbfs.Config, git, refspec, tlfName string) libgit.RefDataByName { 1021 var input bytes.Buffer 1022 var output bytes.Buffer 1023 r, err := newRunner(ctx, config, "origin", 1024 fmt.Sprintf("keybase://private/%s/test", tlfName), 1025 filepath.Join(git, ".git"), &input, &output, testErrput{t}) 1026 require.NoError(t, err) 1027 1028 args := [][]string{{refspec}} 1029 commits, err := r.handlePushBatch(ctx, args) 1030 require.NoError(t, err) 1031 return commits 1032 } 1033 1034 func TestRunnerHandlePushBatch(t *testing.T) { 1035 t.Skip("KBFS-3836: currently flaking a lot") 1036 ctx, config, tempdir := initConfigForRunner(t) 1037 defer os.RemoveAll(tempdir) 1038 defer libkbfs.CheckConfigAndShutdown(ctx, t, config) 1039 1040 git, err := os.MkdirTemp(os.TempDir(), "kbfsgittest") 1041 require.NoError(t, err) 1042 defer os.RemoveAll(git) 1043 1044 t.Log("Setup the repository.") 1045 h, err := tlfhandle.ParseHandle( 1046 ctx, config.KBPKI(), config.MDOps(), config, "user1", tlf.Private) 1047 require.NoError(t, err) 1048 _, err = libgit.CreateRepoAndID(ctx, config, h, "test") 1049 require.NoError(t, err) 1050 1051 t.Log("Make a new local repo with one commit, and push it. " + 1052 "We expect this to return no commits, since it should push the " + 1053 "whole repository.") 1054 makeLocalRepoWithOneFileCustomCommitMsg(t, git, "foo", "hello", "", "one") 1055 refDataByName := testHandlePushBatch(ctx, t, config, git, 1056 "refs/heads/master:refs/heads/master", "user1") 1057 require.Len(t, refDataByName, 1) 1058 master := refDataByName["refs/heads/master"] 1059 require.False(t, master.IsDelete) 1060 commits := master.Commits 1061 require.Len(t, commits, 1) 1062 require.Equal(t, "one", strings.TrimSpace(commits[0].Message)) 1063 1064 t.Log("Add a commit and push it. We expect the push batch to return " + 1065 "one reference with one commit.") 1066 addOneFileToRepoCustomCommitMsg(t, git, "foo2", "hello2", "two") 1067 refDataByName = testHandlePushBatch(ctx, t, config, git, 1068 "refs/heads/master:refs/heads/master", "user1") 1069 require.Len(t, refDataByName, 1) 1070 master = refDataByName["refs/heads/master"] 1071 require.False(t, master.IsDelete) 1072 commits = master.Commits 1073 require.Len(t, commits, 1) 1074 require.Equal(t, "two", strings.TrimSpace(commits[0].Message)) 1075 1076 t.Log("Add three commits. We expect the push batch to return " + 1077 "one reference with three commits. The commits should be ordered " + 1078 "with the most recent first.") 1079 addOneFileToRepoCustomCommitMsg(t, git, "foo3", "hello3", "three") 1080 addOneFileToRepoCustomCommitMsg(t, git, "foo4", "hello4", "four") 1081 addOneFileToRepoCustomCommitMsg(t, git, "foo5", "hello5", "five") 1082 refDataByName = testHandlePushBatch(ctx, t, config, git, 1083 "refs/heads/master:refs/heads/master", "user1") 1084 require.Len(t, refDataByName, 1) 1085 master = refDataByName["refs/heads/master"] 1086 require.False(t, master.IsDelete) 1087 commits = master.Commits 1088 require.Len(t, commits, 3) 1089 require.Equal(t, "five", strings.TrimSpace(commits[0].Message)) 1090 require.Equal(t, "four", strings.TrimSpace(commits[1].Message)) 1091 require.Equal(t, "three", strings.TrimSpace(commits[2].Message)) 1092 1093 t.Log("Add more commits than the maximum to visit per ref. " + 1094 "Check that a sentinel value was added.") 1095 for i := 0; i < maxCommitsToVisitPerRef+1; i++ { 1096 filename := fmt.Sprintf("foo%d", i+6) 1097 content := fmt.Sprintf("hello%d", i+6) 1098 msg := fmt.Sprintf("commit message %d", i+6) 1099 addOneFileToRepoCustomCommitMsg(t, git, filename, content, msg) 1100 } 1101 refDataByName = testHandlePushBatch(ctx, t, config, git, 1102 "refs/heads/master:refs/heads/master", "user1") 1103 require.Len(t, refDataByName, 1) 1104 master = refDataByName["refs/heads/master"] 1105 require.False(t, master.IsDelete) 1106 commits = master.Commits 1107 require.Len(t, commits, maxCommitsToVisitPerRef) 1108 require.Equal(t, libgit.CommitSentinelValue, commits[maxCommitsToVisitPerRef-1]) 1109 1110 t.Log("Push a deletion.") 1111 refDataByName = testHandlePushBatch(ctx, t, config, git, 1112 ":refs/heads/master", "user1") 1113 require.Len(t, refDataByName, 1) 1114 master = refDataByName["refs/heads/master"] 1115 require.True(t, master.IsDelete) 1116 require.Len(t, master.Commits, 0) 1117 } 1118 1119 func TestRunnerSubmodule(t *testing.T) { 1120 if runtime.GOOS == "windows" { 1121 t.Skip("submodule add doesn't work well on Windows") 1122 } 1123 1124 ctx, config, tempdir := initConfigForRunner(t) 1125 defer os.RemoveAll(tempdir) 1126 defer libkbfs.CheckConfigAndShutdown(ctx, t, config) 1127 1128 shutdown := libgit.StartAutogit(config, 25) 1129 defer shutdown() 1130 1131 t.Log("Make a local repo that will become a KBFS repo") 1132 git1, err := os.MkdirTemp(os.TempDir(), "kbfsgittest") 1133 require.NoError(t, err) 1134 defer os.RemoveAll(git1) 1135 makeLocalRepoWithOneFile(t, git1, "foo", "hello", "") 1136 dotgit1 := filepath.Join(git1, ".git") 1137 1138 t.Log("Make a second local repo that will be a submodule") 1139 git2, err := os.MkdirTemp(os.TempDir(), "kbfsgittest2") 1140 require.NoError(t, err) 1141 defer os.RemoveAll(git2) 1142 makeLocalRepoWithOneFile(t, git2, "foo2", "hello2", "") 1143 dotgit2 := filepath.Join(git2, ".git") 1144 1145 t.Log("Add the submodule to the first local repo") 1146 // git-submodules requires a real working directory for some reason. 1147 err = os.Chdir(git1) 1148 require.NoError(t, err) 1149 gitExec(t, dotgit1, git1, "submodule", "add", "-f", dotgit2) 1150 gitExec(t, dotgit1, git1, "-c", "user.name=Foo", 1151 "-c", "user.email=foo@foo.com", "commit", "-a", "-m", "submodule") 1152 1153 t.Log("Push the first local repo into KBFS") 1154 h, err := tlfhandle.ParseHandle( 1155 ctx, config.KBPKI(), config.MDOps(), config, "user1", tlf.Private) 1156 require.NoError(t, err) 1157 _, err = libgit.CreateRepoAndID(ctx, config, h, "test") 1158 require.NoError(t, err) 1159 testPush(ctx, t, config, git1, "refs/heads/master:refs/heads/master") 1160 1161 t.Log("Use autogit to browse it") 1162 rootFS, err := libfs.NewFS( 1163 ctx, config, h, data.MasterBranch, "", "", keybase1.MDPriorityNormal) 1164 require.NoError(t, err) 1165 fis, err := rootFS.ReadDir(".kbfs_autogit/test") 1166 require.NoError(t, err) 1167 require.Len(t, fis, 3 /* foo, kbfsgittest2, and .gitmodules */) 1168 f, err := rootFS.Open(".kbfs_autogit/test/" + filepath.Base(git2)) 1169 require.NoError(t, err) 1170 defer f.Close() 1171 data, err := io.ReadAll(f) 1172 require.NoError(t, err) 1173 require.True(t, strings.HasPrefix(string(data), "git submodule")) 1174 } 1175 1176 func TestRunnerLFS(t *testing.T) { 1177 ctx, config, tempdir := initConfigForRunner(t) 1178 defer libkbfs.CheckConfigAndShutdown(ctx, t, config) 1179 defer os.RemoveAll(tempdir) 1180 1181 localFilePath := filepath.Join(tempdir, "local.txt") 1182 f, err := os.Create(localFilePath) 1183 require.NoError(t, err) 1184 doClose := true 1185 defer func() { 1186 if doClose { 1187 err := f.Close() 1188 require.NoError(t, err) 1189 } 1190 }() 1191 lfsData := []byte("hello") 1192 _, err = f.Write(lfsData) 1193 require.NoError(t, err) 1194 err = f.Close() 1195 require.NoError(t, err) 1196 doClose = false 1197 1198 inputReader, inputWriter := io.Pipe() 1199 defer inputWriter.Close() 1200 oid := "bf3e3e2af9366a3b704ae0c31de5afa64193ebabffde2091936ad2e7510bc03a" 1201 go func() { 1202 _, _ = inputWriter.Write([]byte("{\"event\": \"upload\", \"oid\": \"" + oid + "\", \"size\": 5, \"path\": \"" + filepath.ToSlash(localFilePath) + "\"}\n{\"event\": \"terminate\"}\n")) 1203 }() 1204 1205 h, err := tlfhandle.ParseHandle( 1206 ctx, config.KBPKI(), config.MDOps(), config, "user1", tlf.Private) 1207 require.NoError(t, err) 1208 _, err = libgit.CreateRepoAndID(ctx, config, h, "test") 1209 require.NoError(t, err) 1210 1211 t.Log("Send upload command and make sure it sends the right output") 1212 var output bytes.Buffer 1213 r, err := newRunnerWithType( 1214 ctx, config, "origin", "keybase://private/user1/test", "", inputReader, 1215 &output, testErrput{t}, processLFSNoProgress) 1216 require.NoError(t, err) 1217 err = r.processCommands(ctx) 1218 require.NoError(t, err) 1219 require.Equal( 1220 t, "{\"event\":\"complete\",\"oid\":\""+oid+"\"}\n", output.String()) 1221 1222 t.Log("Make sure the file has been fully uploaded") 1223 fs, err := libfs.NewFS( 1224 ctx, config, h, data.MasterBranch, 1225 fmt.Sprintf("%s/test/%s", kbfsRepoDir, libgit.LFSSubdir), "", 1226 keybase1.MDPriorityGit) 1227 require.NoError(t, err) 1228 oidF, err := fs.Open(oid) 1229 require.NoError(t, err) 1230 defer oidF.Close() 1231 buf, err := io.ReadAll(oidF) 1232 require.NoError(t, err) 1233 require.Equal(t, lfsData, buf) 1234 1235 t.Log("Download and check the file") 1236 inputReader2, inputWriter2 := io.Pipe() 1237 defer inputWriter2.Close() 1238 go func() { 1239 _, _ = inputWriter2.Write([]byte("{\"event\": \"download\", \"oid\": \"" + oid + "\"}\n{\"event\": \"terminate\"}\n")) 1240 }() 1241 oldWd, err := os.Getwd() 1242 oldWdExists := true 1243 if err != nil { 1244 if os.IsNotExist(err) { 1245 oldWdExists = false 1246 } else { 1247 require.NoError(t, err) 1248 } 1249 } 1250 err = os.Chdir(tempdir) 1251 require.NoError(t, err) 1252 if oldWdExists { 1253 defer func() { 1254 err = os.Chdir(oldWd) 1255 require.NoError(t, err) 1256 }() 1257 } 1258 var output2 bytes.Buffer 1259 r2, err := newRunnerWithType( 1260 ctx, config, "origin", "keybase://private/user1/test", "", inputReader2, 1261 &output2, testErrput{t}, processLFSNoProgress) 1262 require.NoError(t, err) 1263 err = r2.processCommands(ctx) 1264 require.NoError(t, err) 1265 outbuf := output2.Bytes() 1266 var resp lfsResponse 1267 err = json.Unmarshal(outbuf, &resp) 1268 require.NoError(t, err) 1269 p := resp.Path 1270 require.Equal( 1271 t, "{\"event\":\"complete\",\"oid\":\""+oid+"\",\"path\":\""+p+"\"}\n", 1272 output2.String()) 1273 1274 pF, err := os.Open(p) 1275 require.NoError(t, err) 1276 defer pF.Close() 1277 buf, err = io.ReadAll(pF) 1278 require.NoError(t, err) 1279 require.Equal(t, lfsData, buf) 1280 }