github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/env/environment.go (about) 1 // Copyright 2019-2020 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package env 16 17 import ( 18 "context" 19 "crypto/tls" 20 "errors" 21 "fmt" 22 "path/filepath" 23 "runtime" 24 "strings" 25 "time" 26 "unicode" 27 28 "google.golang.org/grpc" 29 "google.golang.org/grpc/credentials" 30 31 "github.com/dolthub/dolt/go/cmd/dolt/errhand" 32 "github.com/dolthub/dolt/go/libraries/doltcore/creds" 33 "github.com/dolthub/dolt/go/libraries/doltcore/dbfactory" 34 "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" 35 "github.com/dolthub/dolt/go/libraries/doltcore/doltdocs" 36 "github.com/dolthub/dolt/go/libraries/doltcore/grpcendpoint" 37 "github.com/dolthub/dolt/go/libraries/doltcore/ref" 38 "github.com/dolthub/dolt/go/libraries/doltcore/schema" 39 "github.com/dolthub/dolt/go/libraries/doltcore/schema/encoding" 40 "github.com/dolthub/dolt/go/libraries/utils/filesys" 41 "github.com/dolthub/dolt/go/store/hash" 42 "github.com/dolthub/dolt/go/store/types" 43 ) 44 45 const ( 46 DefaultLoginUrl = "https://dolthub.com/settings/credentials" 47 DefaultMetricsHost = "eventsapi.dolthub.com" 48 DefaultMetricsPort = "443" 49 DefaultRemotesApiHost = "doltremoteapi.dolthub.com" 50 DefaultRemotesApiPort = "443" 51 tempTablesDir = "temptf" 52 ) 53 54 var ErrPreexistingDoltDir = errors.New(".dolt dir already exists") 55 var ErrStateUpdate = errors.New("error updating local data repo state") 56 var ErrMarshallingSchema = errors.New("error marshalling schema") 57 var ErrInvalidCredsFile = errors.New("invalid creds file") 58 59 // DoltEnv holds the state of the current environment used by the cli. 60 type DoltEnv struct { 61 Version string 62 63 Config *DoltCliConfig 64 CfgLoadErr error 65 66 RepoState *RepoState 67 RSLoadErr error 68 69 Docs doltdocs.Docs 70 DocsLoadErr error 71 72 DoltDB *doltdb.DoltDB 73 DBLoadError error 74 75 FS filesys.Filesys 76 urlStr string 77 hdp HomeDirProvider 78 } 79 80 // Load loads the DoltEnv for the current directory of the cli 81 func Load(ctx context.Context, hdp HomeDirProvider, fs filesys.Filesys, urlStr, version string) *DoltEnv { 82 config, cfgErr := loadDoltCliConfig(hdp, fs) 83 repoState, rsErr := LoadRepoState(fs) 84 docs, docsErr := doltdocs.LoadDocs(fs) 85 ddb, dbLoadErr := doltdb.LoadDoltDB(ctx, types.Format_Default, urlStr) 86 87 dEnv := &DoltEnv{ 88 version, 89 config, 90 cfgErr, 91 repoState, 92 rsErr, 93 docs, 94 docsErr, 95 ddb, 96 dbLoadErr, 97 fs, 98 urlStr, 99 hdp, 100 } 101 102 if dbLoadErr == nil && dEnv.HasDoltDir() { 103 if !dEnv.HasDoltTempTableDir() { 104 err := dEnv.FS.MkDirs(dEnv.TempTableFilesDir()) 105 dEnv.DBLoadError = err 106 } else { 107 // fire and forget cleanup routine. Will delete as many old temp files as it can during the main commands execution. 108 // The process will not wait for this to finish so this may not always complete. 109 go func() { 110 _ = fs.Iter(dEnv.TempTableFilesDir(), true, func(path string, size int64, isDir bool) (stop bool) { 111 if !isDir { 112 lm, exists := fs.LastModified(path) 113 114 if exists && time.Now().Sub(lm) > (time.Hour*24) { 115 _ = fs.DeleteFile(path) 116 } 117 } 118 119 return false 120 }) 121 }() 122 } 123 } 124 125 dbfactory.InitializeFactories(dEnv) 126 127 return dEnv 128 } 129 130 // HasDoltDir returns true if the .dolt directory exists and is a valid directory 131 func (dEnv *DoltEnv) HasDoltDir() bool { 132 return dEnv.hasDoltDir("./") 133 } 134 135 func (dEnv *DoltEnv) HasDoltDataDir() bool { 136 exists, isDir := dEnv.FS.Exists(dbfactory.DoltDataDir) 137 return exists && isDir 138 } 139 140 func (dEnv *DoltEnv) HasDoltTempTableDir() bool { 141 ex, _ := dEnv.FS.Exists(dEnv.TempTableFilesDir()) 142 143 return ex 144 } 145 146 func mustAbs(dEnv *DoltEnv, path ...string) string { 147 absPath, err := dEnv.FS.Abs(filepath.Join(path...)) 148 149 if err != nil { 150 panic(err) 151 } 152 153 return absPath 154 } 155 156 // GetDoltDir returns the path to the .dolt directory 157 func (dEnv *DoltEnv) GetDoltDir() string { 158 if !dEnv.HasDoltDataDir() { 159 panic("No dolt dir") 160 } 161 162 return mustAbs(dEnv, dbfactory.DoltDir) 163 } 164 165 func (dEnv *DoltEnv) hasDoltDir(path string) bool { 166 exists, isDir := dEnv.FS.Exists(mustAbs(dEnv, dbfactory.DoltDir)) 167 return exists && isDir 168 } 169 170 // HasLocalConfig returns true if a repository local config file 171 func (dEnv *DoltEnv) HasLocalConfig() bool { 172 _, ok := dEnv.Config.GetConfig(LocalConfig) 173 174 return ok 175 } 176 177 func (dEnv *DoltEnv) bestEffortDeleteAll(dir string) { 178 fileToIsDir := make(map[string]bool) 179 dEnv.FS.Iter(dir, false, func(path string, size int64, isDir bool) (stop bool) { 180 fileToIsDir[path] = isDir 181 return false 182 }) 183 184 for path, isDir := range fileToIsDir { 185 if isDir { 186 dEnv.FS.Delete(path, true) 187 } else { 188 dEnv.FS.DeleteFile(path) 189 } 190 } 191 } 192 193 // InitRepo takes an empty directory and initializes it with a .dolt directory containing repo state, uncommitted license and readme, and creates a noms 194 // database with dolt structure. 195 func (dEnv *DoltEnv) InitRepo(ctx context.Context, nbf *types.NomsBinFormat, name, email string) error { // should remove name and email args 196 return dEnv.InitRepoWithTime(ctx, nbf, name, email, doltdb.CommitNowFunc()) 197 } 198 199 func (dEnv *DoltEnv) InitRepoWithTime(ctx context.Context, nbf *types.NomsBinFormat, name, email string, t time.Time) error { // should remove name and email args 200 doltDir, err := dEnv.createDirectories(".") 201 202 if err != nil { 203 return err 204 } 205 206 err = dEnv.configureRepo(doltDir) 207 208 if err == nil { 209 err = dEnv.InitDBAndRepoState(ctx, nbf, name, email, t) 210 } 211 212 if err != nil { 213 dEnv.bestEffortDeleteAll(dbfactory.DoltDir) 214 } 215 216 return err 217 } 218 219 func (dEnv *DoltEnv) InitRepoWithNoData(ctx context.Context, nbf *types.NomsBinFormat) error { 220 doltDir, err := dEnv.createDirectories(".") 221 222 if err != nil { 223 return err 224 } 225 226 err = dEnv.configureRepo(doltDir) 227 228 if err != nil { 229 dEnv.bestEffortDeleteAll(dbfactory.DoltDir) 230 return err 231 } 232 233 dEnv.DoltDB, err = doltdb.LoadDoltDB(ctx, nbf, dEnv.urlStr) 234 235 return err 236 } 237 238 func (dEnv *DoltEnv) createDirectories(dir string) (string, error) { 239 absPath, err := dEnv.FS.Abs(dir) 240 241 if err != nil { 242 return "", err 243 } 244 245 exists, isDir := dEnv.FS.Exists(absPath) 246 247 if !exists { 248 return "", fmt.Errorf("'%s' does not exist so could not create '%s", absPath, dbfactory.DoltDataDir) 249 } else if !isDir { 250 return "", fmt.Errorf("'%s' exists but it's a file not a directory", absPath) 251 } 252 253 if dEnv.hasDoltDir(dir) { 254 return "", ErrPreexistingDoltDir 255 } 256 257 absDataDir := filepath.Join(absPath, dbfactory.DoltDataDir) 258 err = dEnv.FS.MkDirs(absDataDir) 259 260 if err != nil { 261 return "", fmt.Errorf("unable to make directory '%s', cause: %s", absDataDir, err.Error()) 262 } 263 264 err = dEnv.FS.MkDirs(dEnv.TempTableFilesDir()) 265 266 if err != nil { 267 return "", fmt.Errorf("unable to make directory '%s', cause: %s", dEnv.TempTableFilesDir(), err.Error()) 268 } 269 270 return filepath.Join(absPath, dbfactory.DoltDir), nil 271 } 272 273 func (dEnv *DoltEnv) configureRepo(doltDir string) error { 274 err := dEnv.Config.CreateLocalConfig(map[string]string{}) 275 276 if err != nil { 277 return fmt.Errorf("failed creating file %s", getLocalConfigPath()) 278 } 279 280 return nil 281 } 282 283 // Inits the dolt DB of this environment with an empty commit at the time given and writes default docs to disk. 284 // Writes new repo state with a master branch and current root hash. 285 func (dEnv *DoltEnv) InitDBAndRepoState(ctx context.Context, nbf *types.NomsBinFormat, name, email string, t time.Time) error { 286 err := dEnv.InitDBWithTime(ctx, nbf, name, email, t) 287 if err != nil { 288 return err 289 } 290 291 return dEnv.InitializeRepoState(ctx) 292 } 293 294 // Inits the dolt DB of this environment with an empty commit at the time given and writes default docs to disk. 295 // Does not update repo state. 296 func (dEnv *DoltEnv) InitDBWithTime(ctx context.Context, nbf *types.NomsBinFormat, name, email string, t time.Time) error { 297 var err error 298 dEnv.DoltDB, err = doltdb.LoadDoltDB(ctx, nbf, dEnv.urlStr) 299 300 if err != nil { 301 return err 302 } 303 304 err = dEnv.DoltDB.WriteEmptyRepoWithCommitTime(ctx, name, email, t) 305 if err != nil { 306 return doltdb.ErrNomsIO 307 } 308 309 return nil 310 } 311 312 // InitializeRepoState writes a default repo state to disk, consisting of a master branch and current root hash value. 313 func (dEnv *DoltEnv) InitializeRepoState(ctx context.Context) error { 314 cs, _ := doltdb.NewCommitSpec(doltdb.MasterBranch) 315 commit, err := dEnv.DoltDB.Resolve(ctx, cs, nil) 316 if err != nil { 317 return err 318 } 319 320 root, err := commit.GetRootValue() 321 if err != nil { 322 return err 323 } 324 325 rootHash, err := root.HashOf() 326 if err != nil { 327 return err 328 } 329 330 dEnv.RepoState, err = CreateRepoState(dEnv.FS, doltdb.MasterBranch, rootHash) 331 if err != nil { 332 return ErrStateUpdate 333 } 334 335 dEnv.RSLoadErr = nil 336 return nil 337 } 338 339 func (dEnv *DoltEnv) WorkingRoot(ctx context.Context) (*doltdb.RootValue, error) { 340 return dEnv.DoltDB.ReadRootValue(ctx, dEnv.RepoState.WorkingHash()) 341 } 342 343 func (dEnv *DoltEnv) UpdateWorkingRoot(ctx context.Context, newRoot *doltdb.RootValue) error { 344 h, err := dEnv.DoltDB.WriteRootValue(ctx, newRoot) 345 346 if err != nil { 347 return doltdb.ErrNomsIO 348 } 349 350 return dEnv.RepoStateWriter().SetWorkingHash(ctx, h) 351 } 352 353 type repoStateReader struct { 354 dEnv *DoltEnv 355 } 356 357 func (r *repoStateReader) CWBHeadRef() ref.DoltRef { 358 return r.dEnv.RepoState.CWBHeadRef() 359 } 360 361 func (r *repoStateReader) CWBHeadSpec() *doltdb.CommitSpec { 362 return r.dEnv.RepoState.CWBHeadSpec() 363 } 364 365 func (r *repoStateReader) CWBHeadHash(ctx context.Context) (hash.Hash, error) { 366 ref := r.CWBHeadRef() 367 cm, err := r.dEnv.DoltDB.ResolveCommitRef(ctx, ref) 368 369 if err != nil { 370 return hash.Hash{}, err 371 } 372 373 return cm.HashOf() 374 } 375 376 func (r *repoStateReader) WorkingHash() hash.Hash { 377 return r.dEnv.RepoState.WorkingHash() 378 } 379 380 func (r *repoStateReader) StagedHash() hash.Hash { 381 return hash.Parse(r.dEnv.RepoState.Staged) 382 } 383 384 func (r *repoStateReader) IsMergeActive() bool { 385 return r.dEnv.RepoState.Merge != nil 386 } 387 388 func (r *repoStateReader) GetMergeCommit() string { 389 return r.dEnv.RepoState.Merge.Commit 390 } 391 392 func (r *repoStateReader) GetPreMergeWorking() string { 393 return r.dEnv.RepoState.Merge.PreMergeWorking 394 } 395 396 func (dEnv *DoltEnv) RepoStateReader() RepoStateReader { 397 return &repoStateReader{dEnv} 398 } 399 400 type repoStateWriter struct { 401 dEnv *DoltEnv 402 } 403 404 func (r *repoStateWriter) SetStagedHash(ctx context.Context, h hash.Hash) error { 405 r.dEnv.RepoState.Staged = h.String() 406 err := r.dEnv.RepoState.Save(r.dEnv.FS) 407 408 if err != nil { 409 return ErrStateUpdate 410 } 411 412 return nil 413 } 414 415 func (r *repoStateWriter) SetWorkingHash(ctx context.Context, h hash.Hash) error { 416 r.dEnv.RepoState.Working = h.String() 417 err := r.dEnv.RepoState.Save(r.dEnv.FS) 418 419 if err != nil { 420 return ErrStateUpdate 421 } 422 423 return nil 424 } 425 426 func (r *repoStateWriter) SetCWBHeadRef(ctx context.Context, marshalableRef ref.MarshalableRef) error { 427 r.dEnv.RepoState.Head = marshalableRef 428 err := r.dEnv.RepoState.Save(r.dEnv.FS) 429 430 if err != nil { 431 return ErrStateUpdate 432 } 433 434 return nil 435 } 436 437 func (r *repoStateWriter) AbortMerge() error { 438 return r.dEnv.RepoState.AbortMerge(r.dEnv.FS) 439 } 440 441 func (r *repoStateWriter) ClearMerge() error { 442 return r.dEnv.RepoState.ClearMerge(r.dEnv.FS) 443 } 444 445 func (r *repoStateWriter) StartMerge(commitStr string) error { 446 return r.dEnv.RepoState.StartMerge(commitStr, r.dEnv.FS) 447 } 448 449 func (dEnv *DoltEnv) RepoStateWriter() RepoStateWriter { 450 return &repoStateWriter{dEnv} 451 } 452 453 type docsReadWriter struct { 454 FS filesys.Filesys 455 } 456 457 // GetDocsOnDisk reads the filesystem and returns all docs. 458 func (d *docsReadWriter) GetDocsOnDisk(docNames ...string) (doltdocs.Docs, error) { 459 if docNames != nil { 460 ret := make(doltdocs.Docs, len(docNames)) 461 462 for i, name := range docNames { 463 doc, err := doltdocs.GetDoc(d.FS, name) 464 if err != nil { 465 return nil, err 466 } 467 ret[i] = doc 468 } 469 470 return ret, nil 471 } 472 473 return doltdocs.GetSupportedDocs(d.FS) 474 } 475 476 // WriteDocsToDisk creates or updates the dolt_docs table with docs. 477 func (d *docsReadWriter) WriteDocsToDisk(docs doltdocs.Docs) error { 478 return docs.Save(d.FS) 479 } 480 481 func (dEnv *DoltEnv) DocsReadWriter() DocsReadWriter { 482 return &docsReadWriter{dEnv.FS} 483 } 484 485 func (dEnv *DoltEnv) HeadRoot(ctx context.Context) (*doltdb.RootValue, error) { 486 commit, err := dEnv.DoltDB.ResolveCommitRef(ctx, dEnv.RepoState.CWBHeadRef()) 487 488 if err != nil { 489 return nil, err 490 } 491 492 return commit.GetRootValue() 493 } 494 495 func (dEnv *DoltEnv) DbData() DbData { 496 return DbData{ 497 Ddb: dEnv.DoltDB, 498 Rsw: dEnv.RepoStateWriter(), 499 Rsr: dEnv.RepoStateReader(), 500 Drw: dEnv.DocsReadWriter(), 501 } 502 } 503 504 func (dEnv *DoltEnv) StagedRoot(ctx context.Context) (*doltdb.RootValue, error) { 505 return dEnv.DoltDB.ReadRootValue(ctx, dEnv.RepoState.StagedHash()) 506 } 507 508 func (dEnv *DoltEnv) UpdateStagedRoot(ctx context.Context, newRoot *doltdb.RootValue) (hash.Hash, error) { 509 h, err := dEnv.DoltDB.WriteRootValue(ctx, newRoot) 510 511 if err != nil { 512 return hash.Hash{}, doltdb.ErrNomsIO 513 } 514 515 dEnv.RepoState.Staged = h.String() 516 err = dEnv.RepoState.Save(dEnv.FS) 517 518 if err != nil { 519 return hash.Hash{}, ErrStateUpdate 520 } 521 522 return h, nil 523 } 524 525 // todo: move this out of env to actions 526 func (dEnv *DoltEnv) PutTableToWorking(ctx context.Context, sch schema.Schema, rows types.Map, indexData types.Map, tableName string, autoVal types.Value) error { 527 root, err := dEnv.WorkingRoot(ctx) 528 if err != nil { 529 return doltdb.ErrNomsIO 530 } 531 532 vrw := dEnv.DoltDB.ValueReadWriter() 533 schVal, err := encoding.MarshalSchemaAsNomsValue(ctx, vrw, sch) 534 if err != nil { 535 return ErrMarshallingSchema 536 } 537 538 tbl, err := doltdb.NewTable(ctx, vrw, schVal, rows, indexData, autoVal) 539 if err != nil { 540 return err 541 } 542 543 newRoot, err := root.PutTable(ctx, tableName, tbl) 544 if err != nil { 545 return err 546 } 547 548 rootHash, err := root.HashOf() 549 if err != nil { 550 return err 551 } 552 553 newRootHash, err := newRoot.HashOf() 554 if err != nil { 555 return err 556 } 557 if rootHash == newRootHash { 558 return nil 559 } 560 561 return dEnv.UpdateWorkingRoot(ctx, newRoot) 562 } 563 564 func (dEnv *DoltEnv) IsMergeActive() bool { 565 return dEnv.RepoState.Merge != nil 566 } 567 568 func (dEnv *DoltEnv) GetTablesWithConflicts(ctx context.Context) ([]string, error) { 569 root, err := dEnv.WorkingRoot(ctx) 570 571 if err != nil { 572 return nil, err 573 } 574 575 return root.TablesInConflict(ctx) 576 } 577 578 func (dEnv *DoltEnv) CredsDir() (string, error) { 579 return getCredsDir(dEnv.hdp) 580 } 581 582 func (dEnv *DoltEnv) UserRPCCreds() (creds.DoltCreds, bool, error) { 583 kid, err := dEnv.Config.GetString(UserCreds) 584 585 if err == nil && kid != "" { 586 dir, err := dEnv.CredsDir() 587 588 if err != nil { 589 // not sure why you wouldn't be able to get the creds dir. 590 panic(err) 591 } 592 593 c, err := creds.JWKCredsReadFromFile(dEnv.FS, filepath.Join(dir, kid+".jwk")) 594 return c, c.IsPrivKeyValid() && c.IsPubKeyValid(), err 595 } 596 597 return creds.EmptyCreds, false, nil 598 } 599 600 func (dEnv *DoltEnv) getRPCCreds() (credentials.PerRPCCredentials, error) { 601 dCreds, valid, err := dEnv.UserRPCCreds() 602 if err != nil { 603 return nil, ErrInvalidCredsFile 604 } 605 if !valid { 606 return nil, nil 607 } 608 return dCreds, nil 609 } 610 611 func (dEnv *DoltEnv) getUserAgentString() string { 612 tokens := []string{ 613 "dolt_cli", 614 dEnv.Version, 615 runtime.GOOS, 616 runtime.GOARCH, 617 } 618 619 for i, t := range tokens { 620 tokens[i] = strings.Map(func(r rune) rune { 621 if unicode.IsSpace(r) { 622 return '_' 623 } 624 625 return r 626 }, strings.TrimSpace(t)) 627 } 628 629 return strings.Join(tokens, " ") 630 } 631 632 func (dEnv *DoltEnv) GetGRPCDialParams(config grpcendpoint.Config) (string, []grpc.DialOption, error) { 633 endpoint := config.Endpoint 634 if strings.IndexRune(endpoint, ':') == -1 { 635 if config.Insecure { 636 endpoint += ":80" 637 } else { 638 endpoint += ":443" 639 } 640 } 641 642 var opts []grpc.DialOption 643 if config.Insecure { 644 opts = append(opts, grpc.WithInsecure()) 645 } else { 646 tc := credentials.NewTLS(&tls.Config{}) 647 opts = append(opts, grpc.WithTransportCredentials(tc)) 648 } 649 650 opts = append(opts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(128*1024*1024))) 651 opts = append(opts, grpc.WithUserAgent(dEnv.getUserAgentString())) 652 653 if config.Creds != nil { 654 opts = append(opts, grpc.WithPerRPCCredentials(config.Creds)) 655 } else if config.WithEnvCreds { 656 rpcCreds, err := dEnv.getRPCCreds() 657 if err != nil { 658 return "", nil, err 659 } 660 if rpcCreds != nil { 661 opts = append(opts, grpc.WithPerRPCCredentials(rpcCreds)) 662 } 663 } 664 665 return endpoint, opts, nil 666 } 667 668 func (dEnv *DoltEnv) GetRemotes() (map[string]Remote, error) { 669 if dEnv.RSLoadErr != nil { 670 return nil, dEnv.RSLoadErr 671 } 672 673 return dEnv.RepoState.Remotes, nil 674 } 675 676 var ErrNotACred = errors.New("not a valid credential key id or public key") 677 678 func (dEnv *DoltEnv) FindCreds(credsDir, pubKeyOrId string) (string, error) { 679 if !creds.B32CredsByteSet.ContainsAll([]byte(pubKeyOrId)) { 680 return "", creds.ErrBadB32CredsEncoding 681 } 682 683 if len(pubKeyOrId) == creds.B32EncodedPubKeyLen { 684 pubKeyOrId, _ = creds.PubKeyStrToKIDStr(pubKeyOrId) 685 } 686 687 if len(pubKeyOrId) != creds.B32EncodedKeyIdLen { 688 return "", ErrNotACred 689 } 690 691 path := mustAbs(dEnv, credsDir, pubKeyOrId+creds.JWKFileExtension) 692 exists, isDir := dEnv.FS.Exists(path) 693 694 if isDir { 695 return path, filesys.ErrIsDir 696 } else if !exists { 697 return "", creds.ErrCredsNotFound 698 } else { 699 return path, nil 700 } 701 } 702 703 func (dEnv *DoltEnv) FindRef(ctx context.Context, refStr string) (ref.DoltRef, error) { 704 localRef := ref.NewBranchRef(refStr) 705 if hasRef, err := dEnv.DoltDB.HasRef(ctx, localRef); err != nil { 706 return nil, err 707 } else if hasRef { 708 return localRef, nil 709 } else { 710 if strings.HasPrefix(refStr, "remotes/") { 711 refStr = refStr[len("remotes/"):] 712 } 713 714 slashIdx := strings.IndexRune(refStr, '/') 715 if slashIdx > 0 { 716 remoteName := refStr[:slashIdx] 717 if _, ok := dEnv.RepoState.Remotes[remoteName]; ok { 718 remoteRef, err := ref.NewRemoteRefFromPathStr(refStr) 719 720 if err != nil { 721 return nil, err 722 } 723 724 if hasRef, err = dEnv.DoltDB.HasRef(ctx, remoteRef); err != nil { 725 return nil, err 726 } else if hasRef { 727 return remoteRef, nil 728 } 729 } 730 } 731 } 732 733 return nil, doltdb.ErrBranchNotFound 734 } 735 736 // GetRefSpecs takes an optional remoteName and returns all refspecs associated with that remote. If "" is passed as 737 // the remoteName then the default remote is used. 738 func (dEnv *DoltEnv) GetRefSpecs(remoteName string) ([]ref.RemoteRefSpec, errhand.VerboseError) { 739 var remote Remote 740 var verr errhand.VerboseError 741 742 if remoteName == "" { 743 remote, verr = dEnv.GetDefaultRemote() 744 } else if r, ok := dEnv.RepoState.Remotes[remoteName]; ok { 745 remote = r 746 } else { 747 verr = errhand.BuildDError("error: unknown remote '%s'", remoteName).Build() 748 } 749 750 if verr != nil { 751 return nil, verr 752 } 753 754 var refSpecs []ref.RemoteRefSpec 755 for _, fs := range remote.FetchSpecs { 756 rs, err := ref.ParseRefSpecForRemote(remote.Name, fs) 757 758 if err != nil { 759 return nil, errhand.BuildDError("error: for '%s', '%s' is not a valid refspec.", remote.Name, fs).Build() 760 } 761 762 if rrs, ok := rs.(ref.RemoteRefSpec); !ok { 763 return nil, errhand.BuildDError("error: '%s' is not a valid refspec referring to a remote tracking branch", remote.Name).Build() 764 } else if rrs.GetRemote() != remote.Name { 765 return nil, errhand.BuildDError("error: remote '%s' refers to remote '%s'", remote.Name, rrs.GetRemote()).Build() 766 } else { 767 refSpecs = append(refSpecs, rrs) 768 } 769 } 770 771 return refSpecs, nil 772 } 773 774 var ErrNoRemote = errhand.BuildDError("error: no remote.").Build() 775 var ErrCantDetermineDefault = errhand.BuildDError("error: unable to determine the default remote.").Build() 776 777 // GetDefaultRemote gets the default remote for the environment. Not fully implemented yet. Needs to support multiple 778 // repos and a configurable default. 779 func (dEnv *DoltEnv) GetDefaultRemote() (Remote, errhand.VerboseError) { 780 remotes := dEnv.RepoState.Remotes 781 782 if len(remotes) == 0 { 783 return NoRemote, ErrNoRemote 784 } else if len(remotes) == 1 { 785 for _, v := range remotes { 786 return v, nil 787 } 788 } 789 790 if remote, ok := dEnv.RepoState.Remotes["origin"]; ok { 791 return remote, nil 792 } 793 794 return NoRemote, ErrCantDetermineDefault 795 } 796 797 // GetUserHomeDir returns the user's home dir 798 // based on current filesys 799 func (dEnv *DoltEnv) GetUserHomeDir() (string, error) { 800 return getHomeDir(dEnv.hdp) 801 } 802 803 func (dEnv *DoltEnv) TempTableFilesDir() string { 804 return mustAbs(dEnv, dEnv.GetDoltDir(), tempTablesDir) 805 }