github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libgit/autogit_node_wrappers_test.go (about)

     1  // Copyright 2018 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 libgit
     6  
     7  import (
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/keybase/client/go/kbfs/data"
    15  	"github.com/keybase/client/go/kbfs/libfs"
    16  	"github.com/keybase/client/go/kbfs/libkbfs"
    17  	"github.com/keybase/client/go/kbfs/tlf"
    18  	"github.com/keybase/client/go/kbfs/tlfhandle"
    19  	"github.com/keybase/client/go/protocol/keybase1"
    20  	"github.com/pkg/errors"
    21  	"github.com/stretchr/testify/require"
    22  	gogit "gopkg.in/src-d/go-git.v4"
    23  	"gopkg.in/src-d/go-git.v4/plumbing/object"
    24  )
    25  
    26  func TestAutogitNodeWrappersNoRepos(t *testing.T) {
    27  	ctx, config, cancel, tempdir := initConfigForAutogit(t)
    28  	defer cancel()
    29  	defer os.RemoveAll(tempdir)
    30  	defer libkbfs.CheckConfigAndShutdown(ctx, t, config)
    31  
    32  	shutdown := StartAutogit(config, 25)
    33  	defer shutdown()
    34  
    35  	h, err := tlfhandle.ParseHandle(
    36  		ctx, config.KBPKI(), config.MDOps(), nil, "user1", tlf.Private)
    37  	require.NoError(t, err)
    38  	rootFS, err := libfs.NewFS(
    39  		ctx, config, h, data.MasterBranch, "", "", keybase1.MDPriorityNormal)
    40  	require.NoError(t, err)
    41  
    42  	t.Log("Looking at user1's autogit directory should fail if no git repos")
    43  	_, err = rootFS.ReadDir(AutogitRoot)
    44  	require.Error(t, err)
    45  }
    46  
    47  func checkAutogitOneFile(t *testing.T, rootFS *libfs.FS) {
    48  	fis, err := rootFS.ReadDir(".kbfs_autogit/test")
    49  	require.NoError(t, err)
    50  	require.Len(t, fis, 1)
    51  	f, err := rootFS.Open(".kbfs_autogit/test/foo")
    52  	require.NoError(t, err)
    53  	defer f.Close()
    54  	data, err := io.ReadAll(f)
    55  	require.NoError(t, err)
    56  	require.Equal(t, "hello", string(data))
    57  }
    58  
    59  func checkAutogitTwoFiles(t *testing.T, rootFS *libfs.FS) {
    60  	fis, err := rootFS.ReadDir(".kbfs_autogit/test")
    61  	require.NoError(t, err)
    62  	require.Len(t, fis, 2) // foo and foo2
    63  	f, err := rootFS.Open(".kbfs_autogit/test/foo")
    64  	require.NoError(t, err)
    65  	defer f.Close()
    66  	data, err := io.ReadAll(f)
    67  	require.NoError(t, err)
    68  	require.Equal(t, "hello", string(data))
    69  	f2, err := rootFS.Open(".kbfs_autogit/test/foo2")
    70  	require.NoError(t, err)
    71  	defer f2.Close()
    72  	data2, err := io.ReadAll(f2)
    73  	require.NoError(t, err)
    74  	require.Equal(t, "hello2", string(data2))
    75  	// Make sure a non-existent file gives the right error.
    76  	_, err = rootFS.Open(".kbfs_autogit/test/missing")
    77  	require.True(t, os.IsNotExist(errors.Cause(err)))
    78  }
    79  
    80  func TestAutogitRepoNode(t *testing.T) {
    81  	ctx, config, cancel, tempdir := initConfigForAutogit(t)
    82  	defer cancel()
    83  	defer os.RemoveAll(tempdir)
    84  	defer libkbfs.CheckConfigAndShutdown(ctx, t, config)
    85  
    86  	am := NewAutogitManager(config, 25)
    87  	defer am.Shutdown()
    88  	rw := rootWrapper{am}
    89  	config.AddRootNodeWrapper(rw.wrap)
    90  
    91  	h, err := tlfhandle.ParseHandle(
    92  		ctx, config.KBPKI(), config.MDOps(), nil, "user1", tlf.Private)
    93  	require.NoError(t, err)
    94  	rootFS, err := libfs.NewFS(
    95  		ctx, config, h, data.MasterBranch, "", "", keybase1.MDPriorityNormal)
    96  	require.NoError(t, err)
    97  
    98  	t.Log("Init a new repo directly into KBFS.")
    99  	dotgitFS, _, err := GetOrCreateRepoAndID(ctx, config, h, "test", "")
   100  	require.NoError(t, err)
   101  	err = rootFS.MkdirAll("worktree", 0600)
   102  	require.NoError(t, err)
   103  	worktreeFS, err := rootFS.Chroot("worktree")
   104  	require.NoError(t, err)
   105  	dotgitStorage, err := NewGitConfigWithoutRemotesStorer(dotgitFS)
   106  	require.NoError(t, err)
   107  	repo, err := gogit.Init(dotgitStorage, worktreeFS)
   108  	require.NoError(t, err)
   109  	addFileToWorktreeAndCommit(
   110  		ctx, t, config, h, repo, worktreeFS, "foo", "hello")
   111  
   112  	t.Log("Use autogit to clone it using ReadDir")
   113  	checkAutogitOneFile(t, rootFS)
   114  
   115  	t.Log("Update the source repo and make sure the autogit repos update too")
   116  	addFileToWorktreeAndCommit(
   117  		ctx, t, config, h, repo, worktreeFS, "foo2", "hello2")
   118  
   119  	t.Log("Force the source repo to update for the user")
   120  	srcRootNode, _, err := config.KBFSOps().GetOrCreateRootNode(
   121  		ctx, h, data.MasterBranch)
   122  	require.NoError(t, err)
   123  	err = config.KBFSOps().SyncFromServer(
   124  		ctx, srcRootNode.GetFolderBranch(), nil)
   125  	require.NoError(t, err)
   126  
   127  	t.Log("Update the dest repo")
   128  	dstRootNode, _, err := config.KBFSOps().GetOrCreateRootNode(
   129  		ctx, h, data.MasterBranch)
   130  	require.NoError(t, err)
   131  	err = config.KBFSOps().SyncFromServer(
   132  		ctx, dstRootNode.GetFolderBranch(), nil)
   133  	require.NoError(t, err)
   134  
   135  	checkAutogitTwoFiles(t, rootFS)
   136  
   137  	t.Log("Switch to branch, check in more files.")
   138  	wt, err := repo.Worktree()
   139  	require.NoError(t, err)
   140  	err = wt.Checkout(&gogit.CheckoutOptions{
   141  		Branch: "refs/heads/dir/test-branch",
   142  		Create: true,
   143  	})
   144  	require.NoError(t, err)
   145  	addFileToWorktreeAndCommit(
   146  		ctx, t, config, h, repo, worktreeFS, "foo3", "hello3")
   147  	err = wt.Checkout(&gogit.CheckoutOptions{Branch: "refs/heads/master"})
   148  	require.NoError(t, err)
   149  	checkAutogitTwoFiles(t, rootFS)
   150  
   151  	t.Logf("Check the third file that's only on the branch")
   152  	f3, err := rootFS.Open(
   153  		".kbfs_autogit/test/.kbfs_autogit_branch_dir/" +
   154  			".kbfs_autogit_branch_test-branch/foo3")
   155  	require.NoError(t, err)
   156  	defer f3.Close()
   157  	data3, err := io.ReadAll(f3)
   158  	require.NoError(t, err)
   159  	require.Equal(t, "hello3", string(data3))
   160  
   161  	t.Logf("Use colons instead of slashes in the branch name")
   162  	f4, err := rootFS.Open(
   163  		".kbfs_autogit/test/.kbfs_autogit_branch_dir^test-branch/foo3")
   164  	require.NoError(t, err)
   165  	defer f4.Close()
   166  	data4, err := io.ReadAll(f4)
   167  	require.NoError(t, err)
   168  	require.Equal(t, "hello3", string(data4))
   169  
   170  	t.Logf("Check non-normalized repo name")
   171  	f5, err := rootFS.Open(".kbfs_autogit/test.git/foo")
   172  	require.NoError(t, err)
   173  	defer f5.Close()
   174  	data5, err := io.ReadAll(f5)
   175  	require.NoError(t, err)
   176  	require.Equal(t, "hello", string(data5))
   177  
   178  	err = dotgitFS.SyncAll()
   179  	require.NoError(t, err)
   180  }
   181  
   182  func TestAutogitRepoNodeReadonly(t *testing.T) {
   183  	ctx, config, cancel, tempdir := initConfigForAutogit(t)
   184  	defer cancel()
   185  	defer os.RemoveAll(tempdir)
   186  	defer libkbfs.CheckConfigAndShutdown(ctx, t, config)
   187  
   188  	am := NewAutogitManager(config, 25)
   189  	defer am.Shutdown()
   190  	rw := rootWrapper{am}
   191  	config.AddRootNodeWrapper(rw.wrap)
   192  
   193  	h, err := tlfhandle.ParseHandle(
   194  		ctx, config.KBPKI(), config.MDOps(), nil, "user1", tlf.Public)
   195  	require.NoError(t, err)
   196  	rootFS, err := libfs.NewFS(
   197  		ctx, config, h, data.MasterBranch, "", "", keybase1.MDPriorityNormal)
   198  	require.NoError(t, err)
   199  
   200  	t.Log("Init a new repo directly into KBFS.")
   201  	dotgitFS, _, err := GetOrCreateRepoAndID(ctx, config, h, "test", "")
   202  	require.NoError(t, err)
   203  	err = rootFS.MkdirAll("worktree", 0600)
   204  	require.NoError(t, err)
   205  	worktreeFS, err := rootFS.Chroot("worktree")
   206  	require.NoError(t, err)
   207  	dotgitStorage, err := NewGitConfigWithoutRemotesStorer(dotgitFS)
   208  	require.NoError(t, err)
   209  	repo, err := gogit.Init(dotgitStorage, worktreeFS)
   210  	require.NoError(t, err)
   211  	addFileToWorktreeAndCommit(
   212  		ctx, t, config, h, repo, worktreeFS, "foo", "hello")
   213  
   214  	t.Log("Use autogit to open it as another user.")
   215  	config2 := libkbfs.ConfigAsUser(config, "user2")
   216  	defer libkbfs.CheckConfigAndShutdown(ctx, t, config2)
   217  	am2 := NewAutogitManager(config2, 25)
   218  	defer am2.Shutdown()
   219  	rw2 := rootWrapper{am2}
   220  	config2.AddRootNodeWrapper(rw2.wrap)
   221  	rootFS2, err := libfs.NewFS(
   222  		ctx, config2, h, data.MasterBranch, "", "",
   223  		keybase1.MDPriorityNormal)
   224  	require.NoError(t, err)
   225  	checkAutogitOneFile(t, rootFS2)
   226  
   227  	addFileToWorktree(t, repo, worktreeFS, "foo2", "hello2")
   228  	t.Log("Repacking objects to more closely resemble a real kbfsgit push, " +
   229  		"which only creates packfiles")
   230  	err = repo.RepackObjects(&gogit.RepackConfig{})
   231  	require.NoError(t, err)
   232  	objFS, err := dotgitFS.Chroot("objects")
   233  	require.NoError(t, err)
   234  	fis, err := objFS.ReadDir("/")
   235  	require.NoError(t, err)
   236  	for _, fi := range fis {
   237  		if fi.Name() != "pack" {
   238  			err = libfs.RecursiveDelete(ctx, objFS.(*libfs.FS), fi)
   239  			require.NoError(t, err)
   240  		}
   241  	}
   242  	t.Log("Repacking done")
   243  	commitWorktree(ctx, t, config, h, worktreeFS)
   244  
   245  	t.Log("Force the source repo to update for the second user")
   246  	srcRootNode2, _, err := config2.KBFSOps().GetOrCreateRootNode(
   247  		ctx, h, data.MasterBranch)
   248  	require.NoError(t, err)
   249  	err = config2.KBFSOps().SyncFromServer(
   250  		ctx, srcRootNode2.GetFolderBranch(), nil)
   251  	require.NoError(t, err)
   252  
   253  	t.Log("Update the dest repo")
   254  	dstRootNode2, _, err := config2.KBFSOps().GetOrCreateRootNode(
   255  		ctx, h, data.MasterBranch)
   256  	require.NoError(t, err)
   257  	err = config2.KBFSOps().SyncFromServer(
   258  		ctx, dstRootNode2.GetFolderBranch(), nil)
   259  	require.NoError(t, err)
   260  
   261  	checkAutogitTwoFiles(t, rootFS2)
   262  }
   263  
   264  func TestAutogitCommitFile(t *testing.T) {
   265  	ctx, config, cancel, tempdir := initConfigForAutogit(t)
   266  	defer cancel()
   267  	defer os.RemoveAll(tempdir)
   268  	defer libkbfs.CheckConfigAndShutdown(ctx, t, config)
   269  
   270  	am := NewAutogitManager(config, 25)
   271  	defer am.Shutdown()
   272  	rw := rootWrapper{am}
   273  	config.AddRootNodeWrapper(rw.wrap)
   274  
   275  	h, err := tlfhandle.ParseHandle(
   276  		ctx, config.KBPKI(), config.MDOps(), nil, "user1", tlf.Private)
   277  	require.NoError(t, err)
   278  	rootFS, err := libfs.NewFS(
   279  		ctx, config, h, data.MasterBranch, "", "", keybase1.MDPriorityNormal)
   280  	require.NoError(t, err)
   281  
   282  	t.Log("Init a new repo directly into KBFS.")
   283  	dotgitFS, _, err := GetOrCreateRepoAndID(ctx, config, h, "test", "")
   284  	require.NoError(t, err)
   285  	err = rootFS.MkdirAll("worktree", 0600)
   286  	require.NoError(t, err)
   287  	worktreeFS, err := rootFS.Chroot("worktree")
   288  	require.NoError(t, err)
   289  	dotgitStorage, err := NewGitConfigWithoutRemotesStorer(dotgitFS)
   290  	require.NoError(t, err)
   291  	repo, err := gogit.Init(dotgitStorage, worktreeFS)
   292  	require.NoError(t, err)
   293  
   294  	msg1 := "commit1"
   295  	user1 := "user1"
   296  	email1 := "user1@keyba.se"
   297  	time1 := time.Now()
   298  	hash1 := addFileToWorktreeWithInfo(
   299  		t, repo, worktreeFS, "foo", "hello\n\nworld\n", msg1, user1, email1, time1)
   300  	commitWorktree(ctx, t, config, h, worktreeFS)
   301  
   302  	t.Log("Check the first commit -- no diff")
   303  	headerFormat := "commit %s\nAuthor: %s <%s>\nDate:   %s\n\n    %s\n"
   304  	expectedCommit1 := fmt.Sprintf(
   305  		headerFormat, hash1.String(), user1, email1,
   306  		time1.Format(object.DateFormat), msg1)
   307  
   308  	f1, err := rootFS.Open(
   309  		".kbfs_autogit/test.git/" + AutogitCommitPrefix + hash1.String())
   310  	require.NoError(t, err)
   311  	defer f1.Close()
   312  	data1, err := io.ReadAll(f1)
   313  	require.NoError(t, err)
   314  	require.Equal(t, expectedCommit1, string(data1))
   315  
   316  	t.Log("Make and check a new commit")
   317  	msg2 := "commit2"
   318  	user2 := "user2"
   319  	email2 := "user2@keyba.se"
   320  	time2 := time1.Add(1 * time.Minute)
   321  	hash2 := addFileToWorktreeWithInfo(
   322  		t, repo, worktreeFS, "foo", "hello\n\nworld\nhello world\n", msg2, user2, email2, time2)
   323  	commitWorktree(ctx, t, config, h, worktreeFS)
   324  
   325  	commit1, err := repo.CommitObject(hash1)
   326  	require.NoError(t, err)
   327  	tree1, err := commit1.Tree()
   328  	require.NoError(t, err)
   329  	commit2, err := repo.CommitObject(hash2)
   330  	require.NoError(t, err)
   331  	tree2, err := commit2.Tree()
   332  	require.NoError(t, err)
   333  
   334  	entry1, err := tree1.FindEntry("foo")
   335  	require.NoError(t, err)
   336  	entry2, err := tree2.FindEntry("foo")
   337  	require.NoError(t, err)
   338  
   339  	expectedCommit2 := fmt.Sprintf(
   340  		headerFormat, hash2.String(), user2, email2,
   341  		time2.Format(object.DateFormat), msg2) +
   342  		fmt.Sprintf(`diff --git a/foo b/foo
   343  index %s..%s 100644
   344  --- a/foo
   345  +++ b/foo
   346  @@ -1,3 +1,4 @@
   347   hello
   348   
   349   world
   350  +hello world
   351  `, entry1.Hash.String(), entry2.Hash.String())
   352  	f2, err := rootFS.Open(
   353  		".kbfs_autogit/test.git/" + AutogitCommitPrefix + hash2.String())
   354  	require.NoError(t, err)
   355  	defer f2.Close()
   356  	data2, err := io.ReadAll(f2)
   357  	require.NoError(t, err)
   358  	require.Equal(t, expectedCommit2, string(data2))
   359  }