github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libgit/repo_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 libgit
     6  
     7  import (
     8  	"context"
     9  	"os"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/keybase/client/go/kbfs/data"
    14  	"github.com/keybase/client/go/kbfs/libcontext"
    15  	"github.com/keybase/client/go/kbfs/libkbfs"
    16  	"github.com/keybase/client/go/kbfs/test/clocktest"
    17  	"github.com/keybase/client/go/kbfs/tlf"
    18  	"github.com/keybase/client/go/kbfs/tlfhandle"
    19  	"github.com/keybase/client/go/libkb"
    20  	"github.com/keybase/client/go/protocol/keybase1"
    21  	"github.com/pkg/errors"
    22  	"github.com/stretchr/testify/require"
    23  )
    24  
    25  func initConfig(t *testing.T) (
    26  	ctx context.Context, cancel context.CancelFunc,
    27  	config *libkbfs.ConfigLocal, tempdir string) {
    28  	ctx = libcontext.BackgroundContextWithCancellationDelayer()
    29  	config = libkbfs.MakeTestConfigOrBustLoggedInWithMode(
    30  		t, 0, libkbfs.InitSingleOp, "user1", "user2")
    31  	success := false
    32  	ctx = context.WithValue(ctx, libkbfs.CtxAllowNameKey, kbfsRepoDir)
    33  
    34  	ctx, cancel = context.WithTimeout(ctx, 10*time.Second)
    35  
    36  	tempdir, err := os.MkdirTemp(os.TempDir(), "journal_server")
    37  	require.NoError(t, err)
    38  	defer func() {
    39  		if !success {
    40  			os.RemoveAll(tempdir)
    41  		}
    42  	}()
    43  
    44  	err = config.EnableDiskLimiter(tempdir)
    45  	require.NoError(t, err)
    46  	err = config.EnableJournaling(
    47  		ctx, tempdir, libkbfs.TLFJournalSingleOpBackgroundWorkEnabled)
    48  	require.NoError(t, err)
    49  
    50  	return ctx, cancel, config, tempdir
    51  }
    52  
    53  func TestGetOrCreateRepoAndID(t *testing.T) {
    54  	ctx, cancel, config, tempdir := initConfig(t)
    55  	defer cancel()
    56  	defer os.RemoveAll(tempdir)
    57  	defer libkbfs.CheckConfigAndShutdown(ctx, t, config)
    58  
    59  	h, err := tlfhandle.ParseHandle(
    60  		ctx, config.KBPKI(), config.MDOps(), nil, "user1", tlf.Private)
    61  	require.NoError(t, err)
    62  
    63  	fs, id1, err := GetOrCreateRepoAndID(ctx, config, h, "Repo1", "")
    64  	require.NoError(t, err)
    65  
    66  	// Another get should have the same ID.
    67  	_, id2, err := GetOrCreateRepoAndID(ctx, config, h, "Repo1", "")
    68  	require.NoError(t, err)
    69  	require.Equal(t, id1, id2)
    70  
    71  	// Now make sure case doesn't matter.
    72  	_, id3, err := GetOrCreateRepoAndID(ctx, config, h, "repo1", "")
    73  	require.NoError(t, err)
    74  	require.Equal(t, id1, id3)
    75  
    76  	// A trailing ".git" should be ignored.
    77  	_, id4, err := GetOrCreateRepoAndID(ctx, config, h, "repo1.git", "")
    78  	require.NoError(t, err)
    79  	require.Equal(t, id1, id4)
    80  
    81  	// A one letter repo name is ok.
    82  	_, id5, err := GetOrCreateRepoAndID(ctx, config, h, "r", "")
    83  	require.NoError(t, err)
    84  	require.NotEqual(t, id1, id5)
    85  
    86  	// Invalid names.
    87  	_, _, err = GetOrCreateRepoAndID(ctx, config, h, "", "")
    88  	require.IsType(t, libkb.InvalidRepoNameError{}, errors.Cause(err))
    89  	_, _, err = GetOrCreateRepoAndID(ctx, config, h, ".repo2", "")
    90  	require.IsType(t, libkb.InvalidRepoNameError{}, errors.Cause(err))
    91  	_, _, err = GetOrCreateRepoAndID(ctx, config, h, "repo3.ツ", "")
    92  	require.IsType(t, libkb.InvalidRepoNameError{}, errors.Cause(err))
    93  	_, _, err = GetOrCreateRepoAndID(ctx, config, h, "repo(4)", "")
    94  	require.IsType(t, libkb.InvalidRepoNameError{}, errors.Cause(err))
    95  
    96  	err = fs.SyncAll()
    97  	require.NoError(t, err)
    98  
    99  	rootNode, _, err := config.KBFSOps().GetOrCreateRootNode(
   100  		ctx, h, data.MasterBranch)
   101  	require.NoError(t, err)
   102  	jManager, err := libkbfs.GetJournalManager(config)
   103  	require.NoError(t, err)
   104  	err = jManager.FinishSingleOp(ctx, rootNode.GetFolderBranch().Tlf,
   105  		nil, keybase1.MDPriorityGit)
   106  	require.NoError(t, err)
   107  }
   108  
   109  func TestCreateRepoAndID(t *testing.T) {
   110  	ctx, cancel, config, tempdir := initConfig(t)
   111  	defer cancel()
   112  	defer os.RemoveAll(tempdir)
   113  	defer libkbfs.CheckConfigAndShutdown(ctx, t, config)
   114  
   115  	h, err := tlfhandle.ParseHandle(
   116  		ctx, config.KBPKI(), config.MDOps(), nil, "user1", tlf.Private)
   117  	require.NoError(t, err)
   118  
   119  	id1, err := CreateRepoAndID(ctx, config, h, "Repo1")
   120  	require.NoError(t, err)
   121  
   122  	id2, err := CreateRepoAndID(ctx, config, h, "Repo2")
   123  	require.NoError(t, err)
   124  	require.NotEqual(t, id1, id2)
   125  
   126  	_, err = CreateRepoAndID(ctx, config, h, "Repo1")
   127  	require.IsType(t, libkb.RepoAlreadyExistsError{}, err)
   128  
   129  	_, err = CreateRepoAndID(ctx, config, h, "rePo1")
   130  	require.IsType(t, libkb.RepoAlreadyExistsError{}, err)
   131  
   132  	_, err = CreateRepoAndID(ctx, config, h, "repo2")
   133  	require.IsType(t, libkb.RepoAlreadyExistsError{}, err)
   134  
   135  	rootNode, _, err := config.KBFSOps().GetOrCreateRootNode(
   136  		ctx, h, data.MasterBranch)
   137  	require.NoError(t, err)
   138  	jManager, err := libkbfs.GetJournalManager(config)
   139  	require.NoError(t, err)
   140  	err = jManager.FinishSingleOp(ctx, rootNode.GetFolderBranch().Tlf,
   141  		nil, keybase1.MDPriorityGit)
   142  	require.NoError(t, err)
   143  }
   144  
   145  func TestCreateDuplicateRepo(t *testing.T) {
   146  	ctx, cancel, config, tempdir := initConfig(t)
   147  	defer cancel()
   148  	defer os.RemoveAll(tempdir)
   149  	defer libkbfs.CheckConfigAndShutdown(ctx, t, config)
   150  
   151  	config2 := libkbfs.ConfigAsUser(config, "user2")
   152  	ctx2, cancel2 := context.WithCancel(context.Background())
   153  	defer cancel2()
   154  	tempdir, err := os.MkdirTemp(os.TempDir(), "journal_server")
   155  	require.NoError(t, err)
   156  	defer os.RemoveAll(tempdir)
   157  	err = config2.EnableDiskLimiter(tempdir)
   158  	require.NoError(t, err)
   159  	err = config2.EnableJournaling(
   160  		ctx2, tempdir, libkbfs.TLFJournalSingleOpBackgroundWorkEnabled)
   161  	require.NoError(t, err)
   162  	defer libkbfs.CheckConfigAndShutdown(ctx2, t, config2)
   163  
   164  	h, err := tlfhandle.ParseHandle(
   165  		ctx, config.KBPKI(), config.MDOps(), nil, "user1,user2", tlf.Private)
   166  	require.NoError(t, err)
   167  
   168  	rootNode, _, err := config.KBFSOps().GetOrCreateRootNode(
   169  		ctx, h, data.MasterBranch)
   170  	require.NoError(t, err)
   171  
   172  	t.Log("Start one create and wait for it to get the lock")
   173  	onStalled, unstall, getCtx := libkbfs.StallMDOp(
   174  		ctx, config, libkbfs.StallableMDAfterGetRange, 1)
   175  	err1ch := make(chan error, 1)
   176  	go func() {
   177  		_, err := CreateRepoAndID(getCtx, config, h, "Repo1")
   178  		err1ch <- err
   179  	}()
   180  
   181  	select {
   182  	case <-onStalled:
   183  	case <-ctx.Done():
   184  		t.Fatal(ctx.Err())
   185  	}
   186  
   187  	t.Log("Start 2nd create and wait for it to try to get the lock")
   188  	_, _, err = config2.KBFSOps().GetOrCreateRootNode(
   189  		ctx2, h, data.MasterBranch)
   190  	require.NoError(t, err)
   191  	onStalled2, unstall2, getCtx2 := libkbfs.StallMDOp(
   192  		ctx2, config2, libkbfs.StallableMDGetRange, 1)
   193  	err2ch := make(chan error, 1)
   194  	go func() {
   195  		_, err := CreateRepoAndID(getCtx2, config2, h, "Repo1")
   196  		err2ch <- err
   197  	}()
   198  
   199  	select {
   200  	case <-onStalled2:
   201  	case <-ctx.Done():
   202  		t.Fatal(ctx.Err())
   203  	}
   204  
   205  	close(unstall)
   206  	select {
   207  	case err := <-err1ch:
   208  		require.NoError(t, err)
   209  	case <-ctx.Done():
   210  		t.Fatal(ctx.Err())
   211  	}
   212  
   213  	close(unstall2)
   214  	select {
   215  	case err := <-err2ch:
   216  		require.IsType(t, libkb.RepoAlreadyExistsError{}, errors.Cause(err))
   217  	case <-ctx.Done():
   218  		t.Fatal(ctx.Err())
   219  	}
   220  
   221  	jManager, err := libkbfs.GetJournalManager(config)
   222  	require.NoError(t, err)
   223  	err = jManager.FinishSingleOp(ctx, rootNode.GetFolderBranch().Tlf,
   224  		nil, keybase1.MDPriorityGit)
   225  	require.NoError(t, err)
   226  }
   227  
   228  func TestGetRepoAndID(t *testing.T) {
   229  	ctx, cancel, config, tempdir := initConfig(t)
   230  	defer cancel()
   231  	defer os.RemoveAll(tempdir)
   232  	defer libkbfs.CheckConfigAndShutdown(ctx, t, config)
   233  
   234  	h, err := tlfhandle.ParseHandle(
   235  		ctx, config.KBPKI(), config.MDOps(), nil, "user1", tlf.Private)
   236  	require.NoError(t, err)
   237  
   238  	_, _, err = GetRepoAndID(ctx, config, h, "Repo1", "")
   239  	require.IsType(t, libkb.RepoDoesntExistError{}, errors.Cause(err))
   240  
   241  	id1, err := CreateRepoAndID(ctx, config, h, "Repo1")
   242  	require.NoError(t, err)
   243  
   244  	_, id2, err := GetRepoAndID(ctx, config, h, "Repo1", "")
   245  	require.NoError(t, err)
   246  	require.Equal(t, id1, id2)
   247  
   248  	_, id3, err := GetRepoAndID(ctx, config, h, "repo1", "")
   249  	require.NoError(t, err)
   250  	require.Equal(t, id1, id3)
   251  
   252  	rootNode, _, err := config.KBFSOps().GetOrCreateRootNode(
   253  		ctx, h, data.MasterBranch)
   254  	require.NoError(t, err)
   255  	jManager, err := libkbfs.GetJournalManager(config)
   256  	require.NoError(t, err)
   257  	err = jManager.FinishSingleOp(ctx, rootNode.GetFolderBranch().Tlf,
   258  		nil, keybase1.MDPriorityGit)
   259  	require.NoError(t, err)
   260  }
   261  
   262  func TestDeleteRepo(t *testing.T) {
   263  	ctx, cancel, config, tempdir := initConfig(t)
   264  	defer cancel()
   265  	defer os.RemoveAll(tempdir)
   266  	defer libkbfs.CheckConfigAndShutdown(ctx, t, config)
   267  	clock := &clocktest.TestClock{}
   268  	clock.Set(time.Now())
   269  	config.SetClock(clock)
   270  
   271  	h, err := tlfhandle.ParseHandle(
   272  		ctx, config.KBPKI(), config.MDOps(), nil, "user1", tlf.Private)
   273  	require.NoError(t, err)
   274  
   275  	_, err = CreateRepoAndID(ctx, config, h, "Repo1")
   276  	require.NoError(t, err)
   277  	rootNode, _, err := config.KBFSOps().GetOrCreateRootNode(
   278  		ctx, h, data.MasterBranch)
   279  	require.NoError(t, err)
   280  	jManager, err := libkbfs.GetJournalManager(config)
   281  	require.NoError(t, err)
   282  	err = jManager.FinishSingleOp(ctx, rootNode.GetFolderBranch().Tlf,
   283  		nil, keybase1.MDPriorityGit)
   284  	require.NoError(t, err)
   285  
   286  	err = DeleteRepo(ctx, config, h, "Repo1")
   287  	require.NoError(t, err)
   288  
   289  	gitNode, _, err := config.KBFSOps().Lookup(
   290  		ctx, rootNode, rootNode.ChildName(kbfsRepoDir))
   291  	require.NoError(t, err)
   292  	children, err := config.KBFSOps().GetDirChildren(ctx, gitNode)
   293  	require.NoError(t, err)
   294  	require.Len(t, children, 0) // .kbfs_deleted_repos is hidden
   295  
   296  	deletedReposNode, _, err := config.KBFSOps().Lookup(
   297  		ctx, gitNode, gitNode.ChildName(kbfsDeletedReposDir))
   298  	require.NoError(t, err)
   299  	children, err = config.KBFSOps().GetDirChildren(ctx, deletedReposNode)
   300  	require.NoError(t, err)
   301  	require.Len(t, children, 1)
   302  
   303  	// If cleanup happens too soon, it shouldn't clean the repo.
   304  	err = CleanOldDeletedRepos(ctx, config, h)
   305  	require.NoError(t, err)
   306  	children, err = config.KBFSOps().GetDirChildren(ctx, deletedReposNode)
   307  	require.NoError(t, err)
   308  	require.Len(t, children, 1)
   309  
   310  	// After a long time, cleanup should succeed.
   311  	clock.Add(minDeletedAgeForCleaning)
   312  	err = CleanOldDeletedRepos(ctx, config, h)
   313  	require.NoError(t, err)
   314  	children, err = config.KBFSOps().GetDirChildren(ctx, deletedReposNode)
   315  	require.NoError(t, err)
   316  	require.Len(t, children, 0)
   317  
   318  	err = jManager.FinishSingleOp(ctx, rootNode.GetFolderBranch().Tlf,
   319  		nil, keybase1.MDPriorityGit)
   320  	require.NoError(t, err)
   321  }
   322  
   323  func TestRepoRename(t *testing.T) {
   324  	ctx, cancel, config, tempdir := initConfig(t)
   325  	defer cancel()
   326  	defer os.RemoveAll(tempdir)
   327  	defer libkbfs.CheckConfigAndShutdown(ctx, t, config)
   328  
   329  	h, err := tlfhandle.ParseHandle(
   330  		ctx, config.KBPKI(), config.MDOps(), nil, "user1", tlf.Private)
   331  	require.NoError(t, err)
   332  
   333  	id1, err := CreateRepoAndID(ctx, config, h, "Repo1")
   334  	require.NoError(t, err)
   335  
   336  	err = RenameRepo(ctx, config, h, "Repo1", "Repo2")
   337  	require.NoError(t, err)
   338  
   339  	_, id2, err := GetRepoAndID(ctx, config, h, "Repo2", "")
   340  	require.NoError(t, err)
   341  	require.Equal(t, id1, id2)
   342  
   343  	_, id3, err := GetRepoAndID(ctx, config, h, "Repo1", "")
   344  	require.NoError(t, err)
   345  	require.Equal(t, id1, id3)
   346  
   347  	// Test a same-name repo rename.
   348  	err = RenameRepo(ctx, config, h, "Repo2", "repo2")
   349  	require.NoError(t, err)
   350  
   351  	_, id4, err := GetRepoAndID(ctx, config, h, "repo2", "")
   352  	require.NoError(t, err)
   353  	require.Equal(t, id1, id4)
   354  
   355  	// Can't rename onto existing repo.
   356  	id5, err := CreateRepoAndID(ctx, config, h, "Repo3")
   357  	require.NoError(t, err)
   358  	err = RenameRepo(ctx, config, h, "Repo2", "repo3")
   359  	require.IsType(t, libkb.RepoAlreadyExistsError{}, errors.Cause(err))
   360  
   361  	// Invalid new repo name.
   362  	err = RenameRepo(ctx, config, h, "Repo3", "")
   363  	require.IsType(t, libkb.InvalidRepoNameError{}, errors.Cause(err))
   364  
   365  	// Can create a new repo over the old symlink.
   366  	id6, err := CreateRepoAndID(ctx, config, h, "Repo1")
   367  	require.NoError(t, err)
   368  	require.NotEqual(t, id1, id6)
   369  
   370  	// Can rename onto a symlink.
   371  	err = RenameRepo(ctx, config, h, "repo2", "repo4")
   372  	require.NoError(t, err)
   373  	err = RenameRepo(ctx, config, h, "repo3", "repo2")
   374  	require.NoError(t, err)
   375  	_, id7, err := GetRepoAndID(ctx, config, h, "repo2", "")
   376  	require.NoError(t, err)
   377  	require.Equal(t, id5, id7)
   378  }