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 }