github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/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  	"errors"
    20  	"fmt"
    21  	"os"
    22  	"path/filepath"
    23  	"strings"
    24  	"time"
    25  
    26  	goerrors "gopkg.in/src-d/go-errors.v1"
    27  
    28  	"github.com/dolthub/dolt/go/cmd/dolt/errhand"
    29  	"github.com/dolthub/dolt/go/libraries/doltcore/creds"
    30  	"github.com/dolthub/dolt/go/libraries/doltcore/dbfactory"
    31  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    32  	"github.com/dolthub/dolt/go/libraries/doltcore/grpcendpoint"
    33  	"github.com/dolthub/dolt/go/libraries/doltcore/ref"
    34  	"github.com/dolthub/dolt/go/libraries/doltcore/table/editor"
    35  	"github.com/dolthub/dolt/go/libraries/utils/concurrentmap"
    36  	"github.com/dolthub/dolt/go/libraries/utils/config"
    37  	"github.com/dolthub/dolt/go/libraries/utils/filesys"
    38  	"github.com/dolthub/dolt/go/store/chunks"
    39  	"github.com/dolthub/dolt/go/store/datas"
    40  	"github.com/dolthub/dolt/go/store/hash"
    41  	"github.com/dolthub/dolt/go/store/types"
    42  )
    43  
    44  const (
    45  	DefaultInitBranch = "main"
    46  
    47  	DefaultLoginUrl = "https://dolthub.com/settings/credentials"
    48  
    49  	DefaultRemotesApiHost = "doltremoteapi.dolthub.com"
    50  	DefaultRemotesApiPort = "443"
    51  
    52  	tempTablesDir = "temptf"
    53  )
    54  
    55  var zeroHashStr = (hash.Hash{}).String()
    56  
    57  var ErrStateUpdate = errors.New("error updating local data repo state")
    58  var ErrMarshallingSchema = errors.New("error marshalling schema")
    59  var ErrInvalidCredsFile = errors.New("invalid creds file")
    60  var ErrRemoteAlreadyExists = errors.New("remote already exists")
    61  var ErrInvalidRemoteURL = errors.New("remote URL invalid")
    62  var ErrRemoteNotFound = errors.New("remote not found")
    63  var ErrInvalidRemoteName = errors.New("remote name invalid")
    64  var ErrBackupAlreadyExists = errors.New("backup already exists")
    65  var ErrInvalidBackupURL = errors.New("backup URL invalid")
    66  var ErrBackupNotFound = errors.New("backup not found")
    67  var ErrInvalidBackupName = errors.New("backup name invalid")
    68  var ErrFailedToDeleteBackup = errors.New("failed to delete backup")
    69  var ErrFailedToReadFromDb = errors.New("failed to read from db")
    70  var ErrFailedToDeleteRemote = errors.New("failed to delete remote")
    71  var ErrFailedToWriteRepoState = errors.New("failed to write repo state")
    72  var ErrRemoteAddressConflict = errors.New("address conflict with a remote")
    73  var ErrDoltRepositoryNotFound = errors.New("can no longer find .dolt dir on disk")
    74  var ErrFailedToAccessDB = goerrors.NewKind("failed to access '%s' database: can no longer find .dolt dir on disk")
    75  var ErrDatabaseIsLocked = errors.New("the database is locked by another dolt process")
    76  
    77  // DoltEnv holds the state of the current environment used by the cli.
    78  type DoltEnv struct {
    79  	Version string
    80  
    81  	Config     *DoltCliConfig
    82  	CfgLoadErr error
    83  
    84  	RepoState *RepoState
    85  	RSLoadErr error
    86  
    87  	DoltDB      *doltdb.DoltDB
    88  	DBLoadError error
    89  
    90  	FS     filesys.Filesys
    91  	urlStr string
    92  	hdp    HomeDirProvider
    93  
    94  	UserPassConfig *creds.DoltCredsForPass
    95  }
    96  
    97  func (dEnv *DoltEnv) GetRemoteDB(ctx context.Context, format *types.NomsBinFormat, r Remote, withCaching bool) (*doltdb.DoltDB, error) {
    98  	if withCaching {
    99  		return r.GetRemoteDB(ctx, format, dEnv)
   100  	} else {
   101  		return r.GetRemoteDBWithoutCaching(ctx, format, dEnv)
   102  	}
   103  }
   104  
   105  func (dEnv *DoltEnv) GetConfig() config.ReadableConfig {
   106  	return dEnv.Config
   107  }
   108  
   109  func (dEnv *DoltEnv) UrlStr() string {
   110  	return dEnv.urlStr
   111  }
   112  
   113  func createRepoState(fs filesys.Filesys) (*RepoState, error) {
   114  	repoState, rsErr := LoadRepoState(fs)
   115  
   116  	// deep copy remotes and backups ¯\_(ツ)_/¯ (see commit c59cbead)
   117  	if repoState != nil {
   118  		repoState.Remotes = repoState.Remotes.DeepCopy()
   119  		repoState.Backups = repoState.Backups.DeepCopy()
   120  	}
   121  
   122  	return repoState, rsErr
   123  }
   124  
   125  func (dEnv *DoltEnv) ReloadRepoState() error {
   126  	rs, err := createRepoState(dEnv.FS)
   127  	if err != nil {
   128  		return err
   129  	}
   130  	dEnv.RepoState = rs
   131  	return nil
   132  }
   133  
   134  func LoadWithoutDB(ctx context.Context, hdp HomeDirProvider, fs filesys.Filesys, version string) *DoltEnv {
   135  	cfg, cfgErr := LoadDoltCliConfig(hdp, fs)
   136  
   137  	repoState, rsErr := createRepoState(fs)
   138  
   139  	return &DoltEnv{
   140  		Version:    version,
   141  		Config:     cfg,
   142  		CfgLoadErr: cfgErr,
   143  		RepoState:  repoState,
   144  		RSLoadErr:  rsErr,
   145  		FS:         fs,
   146  		hdp:        hdp,
   147  	}
   148  }
   149  
   150  // Load loads the DoltEnv for the .dolt directory determined by resolving the specified urlStr with the specified Filesys.
   151  func Load(ctx context.Context, hdp HomeDirProvider, fs filesys.Filesys, urlStr string, version string) *DoltEnv {
   152  	dEnv := LoadWithoutDB(ctx, hdp, fs, version)
   153  
   154  	ddb, dbLoadErr := doltdb.LoadDoltDB(ctx, types.Format_Default, urlStr, fs)
   155  
   156  	dEnv.DoltDB = ddb
   157  	dEnv.DBLoadError = dbLoadErr
   158  	dEnv.urlStr = urlStr
   159  
   160  	if dbLoadErr == nil && dEnv.HasDoltDir() {
   161  		if !dEnv.HasDoltTempTableDir() {
   162  			tmpDir, err := dEnv.TempTableFilesDir()
   163  			if err != nil {
   164  				dEnv.DBLoadError = err
   165  			}
   166  			err = dEnv.FS.MkDirs(tmpDir)
   167  			dEnv.DBLoadError = err
   168  		} else {
   169  			// fire and forget cleanup routine.  Will delete as many old temp files as it can during the main commands execution.
   170  			// The process will not wait for this to finish so this may not always complete.
   171  			go func() {
   172  				// TODO dEnv.HasDoltTempTableDir() true but dEnv.TempTableFileDir() panics
   173  				tmpTableDir, err := dEnv.FS.Abs(filepath.Join(dEnv.urlStr, dbfactory.DoltDir, tempTablesDir))
   174  				if err != nil {
   175  					return
   176  				}
   177  				_ = fs.Iter(tmpTableDir, true, func(path string, size int64, isDir bool) (stop bool) {
   178  					if !isDir {
   179  						lm, exists := fs.LastModified(path)
   180  
   181  						if exists && time.Now().Sub(lm) > (time.Hour*24) {
   182  							_ = fs.DeleteFile(path)
   183  						}
   184  					}
   185  
   186  					return false
   187  				})
   188  			}()
   189  		}
   190  	}
   191  
   192  	if dEnv.RSLoadErr == nil && dbLoadErr == nil {
   193  		// If the working set isn't present in the DB, create it from the repo state. This step can be removed post 1.0.
   194  		_, err := dEnv.WorkingSet(ctx)
   195  		if errors.Is(err, doltdb.ErrWorkingSetNotFound) {
   196  			_ = dEnv.initWorkingSetFromRepoState(ctx)
   197  		} else if err != nil {
   198  			dEnv.RSLoadErr = err
   199  		}
   200  	}
   201  
   202  	return dEnv
   203  }
   204  
   205  func GetDefaultInitBranch(cfg config.ReadableConfig) string {
   206  	return GetStringOrDefault(cfg, config.InitBranchName, DefaultInitBranch)
   207  }
   208  
   209  // Valid returns whether this environment has been properly initialized. This is useful because although every command
   210  // gets a DoltEnv, not all of them require it, and we allow invalid dolt envs to be passed around for this reason.
   211  func (dEnv *DoltEnv) Valid() bool {
   212  	return dEnv != nil && dEnv.CfgLoadErr == nil && dEnv.DBLoadError == nil && dEnv.HasDoltDir() && dEnv.HasDoltDataDir()
   213  }
   214  
   215  // initWorkingSetFromRepoState sets the working set for the env's head to mirror the contents of the repo state file.
   216  // This is only necessary to migrate repos written before this method was introduced, and can be removed after 1.0
   217  func (dEnv *DoltEnv) initWorkingSetFromRepoState(ctx context.Context) error {
   218  	headRef, err := dEnv.RepoStateReader().CWBHeadRef()
   219  	if err != nil {
   220  		return err
   221  	}
   222  	wsRef, err := ref.WorkingSetRefForHead(headRef)
   223  	if err != nil {
   224  		return err
   225  	}
   226  
   227  	headRoot, err := dEnv.HeadRoot(ctx)
   228  	if err != nil {
   229  		return err
   230  	}
   231  
   232  	stagedRoot := headRoot
   233  	if len(dEnv.RepoState.staged) != 0 && dEnv.RepoState.staged != zeroHashStr {
   234  		stagedHash, ok := hash.MaybeParse(dEnv.RepoState.staged)
   235  		if !ok {
   236  			return fmt.Errorf("Corrupt repo, invalid staged hash %s", stagedHash)
   237  		}
   238  
   239  		stagedRoot, err = dEnv.DoltDB.ReadRootValue(ctx, stagedHash)
   240  		if err != nil {
   241  			return err
   242  		}
   243  	}
   244  
   245  	workingRoot := stagedRoot
   246  	if len(dEnv.RepoState.working) != 0 && dEnv.RepoState.working != zeroHashStr {
   247  		workingHash, ok := hash.MaybeParse(dEnv.RepoState.working)
   248  		if !ok {
   249  			return fmt.Errorf("Corrupt repo, invalid working hash %s", workingHash)
   250  		}
   251  
   252  		workingRoot, err = dEnv.DoltDB.ReadRootValue(ctx, workingHash)
   253  		if err != nil {
   254  			return err
   255  		}
   256  	}
   257  
   258  	mergeState, err := mergeStateToMergeState(ctx, dEnv.RepoState.merge, dEnv.DoltDB)
   259  	if err != nil {
   260  		return err
   261  	}
   262  
   263  	ws := doltdb.EmptyWorkingSet(wsRef).WithWorkingRoot(workingRoot).WithStagedRoot(stagedRoot).WithMergeState(mergeState)
   264  	return dEnv.UpdateWorkingSet(ctx, ws)
   265  }
   266  
   267  func mergeStateToMergeState(ctx context.Context, mergeState *mergeState, db *doltdb.DoltDB) (*doltdb.MergeState, error) {
   268  	if mergeState == nil {
   269  		return nil, nil
   270  	}
   271  
   272  	cs, err := doltdb.NewCommitSpec(mergeState.Commit)
   273  	if err != nil {
   274  		panic("Corrupted repostate. Active merge state is not valid.")
   275  	}
   276  
   277  	optCmt, err := db.Resolve(ctx, cs, nil)
   278  	if err != nil {
   279  		return nil, err
   280  	}
   281  
   282  	commit, ok := optCmt.ToCommit()
   283  	if !ok {
   284  		return nil, doltdb.ErrGhostCommitEncountered
   285  	}
   286  
   287  	pmwh := hash.Parse(mergeState.PreMergeWorking)
   288  	pmwr, err := db.ReadRootValue(ctx, pmwh)
   289  	if err != nil {
   290  		return nil, err
   291  	}
   292  
   293  	return doltdb.MergeStateFromCommitAndWorking(commit, pmwr), nil
   294  }
   295  
   296  // HasDoltDir returns true if the .dolt directory exists and is a valid directory
   297  func (dEnv *DoltEnv) HasDoltDir() bool {
   298  	return dEnv.hasDoltDir("./")
   299  }
   300  
   301  func (dEnv *DoltEnv) HasDoltDataDir() bool {
   302  	exists, isDir := dEnv.FS.Exists(dbfactory.DoltDataDir)
   303  	return exists && isDir
   304  }
   305  
   306  // HasDoltSqlServerInfo returns true if this Dolt environment has a sql-server.info file, indicating
   307  // that a sql-server is running from this Dolt environment.
   308  func (dEnv *DoltEnv) HasDoltSqlServerInfo() bool {
   309  	exists, _ := dEnv.FS.Exists(filepath.Join(dbfactory.DoltDir, "sql-server.info"))
   310  	return exists
   311  }
   312  
   313  func (dEnv *DoltEnv) HasDoltTempTableDir() bool {
   314  	tmpDir, err := dEnv.TempTableFilesDir()
   315  	if err != nil {
   316  		return false
   317  	}
   318  	ex, _ := dEnv.FS.Exists(tmpDir)
   319  
   320  	return ex
   321  }
   322  
   323  func mustAbs(dEnv *DoltEnv, path ...string) string {
   324  	absPath, err := dEnv.FS.Abs(filepath.Join(path...))
   325  
   326  	if err != nil {
   327  		panic(err)
   328  	}
   329  
   330  	return absPath
   331  }
   332  
   333  // GetDoltDir returns the path to the .dolt directory
   334  func (dEnv *DoltEnv) GetDoltDir() string {
   335  	if !dEnv.HasDoltDataDir() {
   336  		return ""
   337  	}
   338  
   339  	return mustAbs(dEnv, dbfactory.DoltDir)
   340  }
   341  
   342  func (dEnv *DoltEnv) hasDoltDir(path string) bool {
   343  	exists, isDir := dEnv.FS.Exists(mustAbs(dEnv, dbfactory.DoltDir))
   344  	return exists && isDir
   345  }
   346  
   347  // HasLocalConfig returns true if a repository local config file
   348  func (dEnv *DoltEnv) HasLocalConfig() bool {
   349  	_, ok := dEnv.Config.GetConfig(LocalConfig)
   350  
   351  	return ok
   352  }
   353  
   354  func (dEnv *DoltEnv) bestEffortDeleteAll(dir string) {
   355  	fileToIsDir := make(map[string]bool)
   356  	dEnv.FS.Iter(dir, false, func(path string, size int64, isDir bool) (stop bool) {
   357  		fileToIsDir[path] = isDir
   358  		return false
   359  	})
   360  
   361  	for path, isDir := range fileToIsDir {
   362  		if isDir {
   363  			dEnv.FS.Delete(path, true)
   364  		} else {
   365  			dEnv.FS.DeleteFile(path)
   366  		}
   367  	}
   368  }
   369  
   370  // InitRepo takes an empty directory and initializes it with a .dolt directory containing repo state, uncommitted license and readme, and creates a noms
   371  // database with dolt structure.
   372  func (dEnv *DoltEnv) InitRepo(ctx context.Context, nbf *types.NomsBinFormat, name, email, branchName string) error { // should remove name and email args
   373  	return dEnv.InitRepoWithTime(ctx, nbf, name, email, branchName, datas.CommitterDate())
   374  }
   375  
   376  func (dEnv *DoltEnv) InitRepoWithTime(ctx context.Context, nbf *types.NomsBinFormat, name, email, branchName string, t time.Time) error { // should remove name and email args
   377  	return dEnv.InitRepoWithCommitMetaGenerator(ctx, nbf, branchName, datas.MakeCommitMetaGenerator(name, email, t))
   378  }
   379  
   380  func (dEnv *DoltEnv) InitRepoWithCommitMetaGenerator(ctx context.Context, nbf *types.NomsBinFormat, branchName string, commitMeta datas.CommitMetaGenerator) error {
   381  	doltDir, err := dEnv.createDirectories(".")
   382  
   383  	if err != nil {
   384  		return err
   385  	}
   386  
   387  	err = dEnv.configureRepo(doltDir)
   388  
   389  	if err == nil {
   390  		err = dEnv.InitDBAndRepoStateWithCommitMetaGenerator(ctx, nbf, branchName, commitMeta)
   391  	}
   392  
   393  	if err != nil {
   394  		dEnv.bestEffortDeleteAll(dbfactory.DoltDir)
   395  	}
   396  
   397  	return err
   398  }
   399  
   400  func (dEnv *DoltEnv) InitRepoWithNoData(ctx context.Context, nbf *types.NomsBinFormat) error {
   401  	doltDir, err := dEnv.createDirectories(".")
   402  
   403  	if err != nil {
   404  		return err
   405  	}
   406  
   407  	err = dEnv.configureRepo(doltDir)
   408  
   409  	if err != nil {
   410  		dEnv.bestEffortDeleteAll(dbfactory.DoltDir)
   411  		return err
   412  	}
   413  
   414  	dEnv.DoltDB, err = doltdb.LoadDoltDB(ctx, nbf, dEnv.urlStr, dEnv.FS)
   415  
   416  	return err
   417  }
   418  
   419  func (dEnv *DoltEnv) createDirectories(dir string) (string, error) {
   420  	absPath, err := dEnv.FS.Abs(dir)
   421  
   422  	if err != nil {
   423  		return "", err
   424  	}
   425  
   426  	exists, isDir := dEnv.FS.Exists(absPath)
   427  
   428  	if !exists {
   429  		return "", fmt.Errorf("'%s' does not exist so could not create '%s", absPath, dbfactory.DoltDataDir)
   430  	} else if !isDir {
   431  		return "", fmt.Errorf("'%s' exists but it's a file not a directory", absPath)
   432  	}
   433  
   434  	if dEnv.hasDoltDir(dir) {
   435  		// Special case a completely empty directory. We can allow that.
   436  		dotDolt := mustAbs(dEnv, dbfactory.DoltDir)
   437  		entries, err := os.ReadDir(dotDolt)
   438  		if err != nil {
   439  			return "", err
   440  		}
   441  		if len(entries) != 0 {
   442  			return "", fmt.Errorf(".dolt directory already exists at '%s'", dir)
   443  		}
   444  	}
   445  
   446  	absDataDir := filepath.Join(absPath, dbfactory.DoltDataDir)
   447  	err = dEnv.FS.MkDirs(absDataDir)
   448  
   449  	if err != nil {
   450  		return "", fmt.Errorf("unable to make directory '%s', cause: %s", absDataDir, err.Error())
   451  	}
   452  
   453  	tmpDir, err := dEnv.TempTableFilesDir()
   454  	if err != nil {
   455  		return "", err
   456  	}
   457  
   458  	err = dEnv.FS.MkDirs(tmpDir)
   459  	if err != nil {
   460  		return "", fmt.Errorf("unable to make directory '%s', cause: %s", tmpDir, err.Error())
   461  	}
   462  
   463  	return filepath.Join(absPath, dbfactory.DoltDir), nil
   464  }
   465  
   466  func (dEnv *DoltEnv) configureRepo(doltDir string) error {
   467  	configDir, err := dEnv.FS.Abs(".")
   468  	if err != nil {
   469  		return fmt.Errorf("unable to resolve current path to create repo local config file: %s", err.Error())
   470  	}
   471  
   472  	err = dEnv.Config.CreateLocalConfig(configDir, map[string]string{})
   473  	if err != nil {
   474  		return fmt.Errorf("failed creating file %s", getLocalConfigPath())
   475  	}
   476  
   477  	return nil
   478  }
   479  
   480  // Inits the dolt DB of this environment with an empty commit at the time given and writes default docs to disk.
   481  // Writes new repo state with a main branch and current root hash.
   482  func (dEnv *DoltEnv) InitDBAndRepoState(ctx context.Context, nbf *types.NomsBinFormat, name, email, branchName string, t time.Time) error {
   483  	return dEnv.InitDBAndRepoStateWithCommitMetaGenerator(ctx, nbf, branchName, datas.MakeCommitMetaGenerator(name, email, t))
   484  }
   485  
   486  func (dEnv *DoltEnv) InitDBAndRepoStateWithCommitMetaGenerator(ctx context.Context, nbf *types.NomsBinFormat, branchName string, commitMeta datas.CommitMetaGenerator) error {
   487  	err := dEnv.InitDBWithCommitMetaGenerator(ctx, nbf, branchName, commitMeta)
   488  	if err != nil {
   489  		return err
   490  	}
   491  
   492  	return dEnv.InitializeRepoState(ctx, branchName)
   493  }
   494  
   495  // Inits the dolt DB of this environment with an empty commit at the time given and writes default docs to disk.
   496  // Does not update repo state.
   497  func (dEnv *DoltEnv) InitDBWithTime(ctx context.Context, nbf *types.NomsBinFormat, name, email, branchName string, t time.Time) error {
   498  	return dEnv.InitDBWithCommitMetaGenerator(ctx, nbf, branchName, datas.MakeCommitMetaGenerator(name, email, t))
   499  }
   500  
   501  func (dEnv *DoltEnv) InitDBWithCommitMetaGenerator(ctx context.Context, nbf *types.NomsBinFormat, branchName string, commitMeta datas.CommitMetaGenerator) error {
   502  	var err error
   503  	dEnv.DoltDB, err = doltdb.LoadDoltDB(ctx, nbf, dEnv.urlStr, dEnv.FS)
   504  	if err != nil {
   505  		return err
   506  	}
   507  
   508  	err = dEnv.DoltDB.WriteEmptyRepoWithCommitMetaGenerator(ctx, branchName, commitMeta)
   509  	if err != nil {
   510  		return fmt.Errorf("%w: %v", doltdb.ErrNomsIO, err)
   511  	}
   512  
   513  	return nil
   514  }
   515  
   516  // InitializeRepoState writes a default repo state to disk, consisting of a main branch and current root hash value.
   517  func (dEnv *DoltEnv) InitializeRepoState(ctx context.Context, branchName string) error {
   518  	commit, err := dEnv.DoltDB.ResolveCommitRef(ctx, ref.NewBranchRef(branchName))
   519  	if err != nil {
   520  		return err
   521  	}
   522  
   523  	root, err := commit.GetRootValue(ctx)
   524  	if err != nil {
   525  		return err
   526  	}
   527  
   528  	dEnv.RepoState, err = CreateRepoState(dEnv.FS, branchName)
   529  	if err != nil {
   530  		return ErrStateUpdate
   531  	}
   532  
   533  	// TODO: combine into one update
   534  	err = dEnv.UpdateWorkingRoot(ctx, root)
   535  	if err != nil {
   536  		return err
   537  	}
   538  
   539  	err = dEnv.UpdateStagedRoot(ctx, root)
   540  	if err != nil {
   541  		return err
   542  	}
   543  
   544  	dEnv.RSLoadErr = nil
   545  	return nil
   546  }
   547  
   548  type RootsProvider interface {
   549  	GetRoots(ctx context.Context) (doltdb.Roots, error)
   550  }
   551  
   552  // Roots returns the roots for this environment
   553  func (dEnv *DoltEnv) Roots(ctx context.Context) (doltdb.Roots, error) {
   554  	ws, err := dEnv.WorkingSet(ctx)
   555  	if err != nil {
   556  		return doltdb.Roots{}, err
   557  	}
   558  
   559  	headRoot, err := dEnv.HeadRoot(ctx)
   560  	if err != nil {
   561  		return doltdb.Roots{}, err
   562  	}
   563  
   564  	return doltdb.Roots{
   565  		Head:    headRoot,
   566  		Working: ws.WorkingRoot(),
   567  		Staged:  ws.StagedRoot(),
   568  	}, nil
   569  }
   570  
   571  // RecoveryRoots returns the roots for this environment in the case that the
   572  // currently checked out branch has been deleted or HEAD has been updated in a
   573  // non-principled way to point to a branch that does not exist. This is used by
   574  // `dolt checkout`, in particular, to go forward with a `dolt checkout` of an
   575  // existing branch in the degraded state where the current branch was deleted.
   576  func (dEnv *DoltEnv) RecoveryRoots(ctx context.Context) (doltdb.Roots, error) {
   577  	ws, err := dEnv.WorkingSet(ctx)
   578  	if err != nil {
   579  		return doltdb.Roots{}, err
   580  	}
   581  
   582  	headRoot, err := dEnv.HeadRoot(ctx)
   583  	if err == doltdb.ErrBranchNotFound {
   584  		headRoot = ws.StagedRoot()
   585  		err = nil
   586  	}
   587  	if err != nil {
   588  		return doltdb.Roots{}, err
   589  	}
   590  
   591  	return doltdb.Roots{
   592  		Head:    headRoot,
   593  		Working: ws.WorkingRoot(),
   594  		Staged:  ws.StagedRoot(),
   595  	}, nil
   596  }
   597  
   598  // UpdateRoots updates the working and staged roots for this environment
   599  func (dEnv *DoltEnv) UpdateRoots(ctx context.Context, roots doltdb.Roots) error {
   600  	ws, err := dEnv.WorkingSet(ctx)
   601  	if err == doltdb.ErrWorkingSetNotFound {
   602  		// first time updating roots
   603  		wsRef, err := ref.WorkingSetRefForHead(dEnv.RepoState.CWBHeadRef())
   604  		if err != nil {
   605  			return err
   606  		}
   607  		ws = doltdb.EmptyWorkingSet(wsRef)
   608  	} else if err != nil {
   609  		return err
   610  	}
   611  
   612  	return dEnv.UpdateWorkingSet(ctx, ws.WithWorkingRoot(roots.Working).WithStagedRoot(roots.Staged))
   613  }
   614  
   615  // WorkingRoot returns the working root for the current working branch
   616  func (dEnv *DoltEnv) WorkingRoot(ctx context.Context) (doltdb.RootValue, error) {
   617  	workingSet, err := dEnv.WorkingSet(ctx)
   618  	if err != nil {
   619  		return nil, err
   620  	}
   621  
   622  	return workingSet.WorkingRoot(), nil
   623  }
   624  
   625  func (dEnv *DoltEnv) WorkingSet(ctx context.Context) (*doltdb.WorkingSet, error) {
   626  	return WorkingSet(ctx, dEnv.DoltDB, dEnv.RepoStateReader())
   627  }
   628  
   629  func WorkingSet(ctx context.Context, ddb *doltdb.DoltDB, rsr RepoStateReader) (*doltdb.WorkingSet, error) {
   630  	headRef, err := rsr.CWBHeadRef()
   631  	if err != nil {
   632  		return nil, err
   633  	}
   634  	workingSetRef, err := ref.WorkingSetRefForHead(headRef)
   635  	if err != nil {
   636  		return nil, err
   637  	}
   638  
   639  	workingSet, err := ddb.ResolveWorkingSet(ctx, workingSetRef)
   640  	if err != nil {
   641  		return nil, err
   642  	}
   643  
   644  	return workingSet, nil
   645  }
   646  
   647  // UpdateWorkingRoot updates the working root for the current working branch to the root value given.
   648  // This method can fail if another client updates the working root at the same time.
   649  func (dEnv *DoltEnv) UpdateWorkingRoot(ctx context.Context, newRoot doltdb.RootValue) error {
   650  	var h hash.Hash
   651  	var wsRef ref.WorkingSetRef
   652  
   653  	ws, err := dEnv.WorkingSet(ctx)
   654  	if err == doltdb.ErrWorkingSetNotFound {
   655  		// first time updating root
   656  		wsRef, err = ref.WorkingSetRefForHead(dEnv.RepoState.CWBHeadRef())
   657  		if err != nil {
   658  			return err
   659  		}
   660  		ws = doltdb.EmptyWorkingSet(wsRef).WithWorkingRoot(newRoot).WithStagedRoot(newRoot)
   661  	} else if err != nil {
   662  		return err
   663  	} else {
   664  		h, err = ws.HashOf()
   665  		if err != nil {
   666  			return err
   667  		}
   668  
   669  		wsRef = ws.Ref()
   670  	}
   671  
   672  	return dEnv.DoltDB.UpdateWorkingSet(ctx, wsRef, ws.WithWorkingRoot(newRoot), h, dEnv.workingSetMeta(), nil)
   673  }
   674  
   675  // UpdateWorkingSet updates the working set for the current working branch to the value given.
   676  // This method can fail if another client updates the working set at the same time.
   677  func (dEnv *DoltEnv) UpdateWorkingSet(ctx context.Context, ws *doltdb.WorkingSet) error {
   678  	currentWs, err := dEnv.WorkingSet(ctx)
   679  	if err != doltdb.ErrWorkingSetNotFound && err != nil {
   680  		return err
   681  	}
   682  
   683  	var h hash.Hash
   684  	if currentWs != nil {
   685  		h, err = currentWs.HashOf()
   686  		if err != nil {
   687  			return err
   688  		}
   689  	}
   690  
   691  	return dEnv.DoltDB.UpdateWorkingSet(ctx, ws.Ref(), ws, h, dEnv.workingSetMeta(), nil)
   692  }
   693  
   694  type repoStateReader struct {
   695  	*DoltEnv
   696  }
   697  
   698  func (r *repoStateReader) CWBHeadRef() (ref.DoltRef, error) {
   699  	if r.RepoState == nil && r.RSLoadErr != nil {
   700  		return nil, r.RSLoadErr
   701  	}
   702  	return r.RepoState.CWBHeadRef(), nil
   703  }
   704  
   705  func (r *repoStateReader) CWBHeadSpec() (*doltdb.CommitSpec, error) {
   706  	if r.RepoState == nil && r.RSLoadErr != nil {
   707  		return nil, r.RSLoadErr
   708  	}
   709  	return r.RepoState.CWBHeadSpec(), nil
   710  }
   711  
   712  func (dEnv *DoltEnv) RepoStateReader() RepoStateReader {
   713  	return &repoStateReader{dEnv}
   714  }
   715  
   716  type repoStateWriter struct {
   717  	*DoltEnv
   718  }
   719  
   720  func (r *repoStateWriter) SetCWBHeadRef(ctx context.Context, marshalableRef ref.MarshalableRef) error {
   721  	if r.RepoState == nil && r.RSLoadErr != nil {
   722  		return r.RSLoadErr
   723  	}
   724  
   725  	r.RepoState.Head = marshalableRef
   726  	err := r.RepoState.Save(r.FS)
   727  
   728  	if err != nil {
   729  		return ErrStateUpdate
   730  	}
   731  
   732  	return nil
   733  }
   734  
   735  func (r *repoStateWriter) AddRemote(remote Remote) error {
   736  	return r.DoltEnv.AddRemote(remote)
   737  }
   738  
   739  func (r *repoStateWriter) AddBackup(remote Remote) error {
   740  	return r.DoltEnv.AddBackup(remote)
   741  }
   742  
   743  func (r *repoStateWriter) RemoveRemote(ctx context.Context, name string) error {
   744  	return r.DoltEnv.RemoveRemote(ctx, name)
   745  }
   746  
   747  func (r *repoStateWriter) RemoveBackup(ctx context.Context, name string) error {
   748  	return r.DoltEnv.RemoveBackup(ctx, name)
   749  }
   750  
   751  func (dEnv *DoltEnv) RepoStateWriter() RepoStateWriter {
   752  	return &repoStateWriter{dEnv}
   753  }
   754  
   755  func (dEnv *DoltEnv) HeadRoot(ctx context.Context) (doltdb.RootValue, error) {
   756  	commit, err := dEnv.HeadCommit(ctx)
   757  	if err != nil {
   758  		return nil, err
   759  	}
   760  
   761  	return commit.GetRootValue(ctx)
   762  }
   763  
   764  func (dEnv *DoltEnv) HeadCommit(ctx context.Context) (*doltdb.Commit, error) {
   765  	return dEnv.DoltDB.ResolveCommitRef(ctx, dEnv.RepoState.CWBHeadRef())
   766  }
   767  
   768  func (dEnv *DoltEnv) DbData() DbData {
   769  	return DbData{
   770  		Ddb: dEnv.DoltDB,
   771  		Rsw: dEnv.RepoStateWriter(),
   772  		Rsr: dEnv.RepoStateReader(),
   773  	}
   774  }
   775  
   776  // StagedRoot returns the staged root value in the current working set
   777  func (dEnv *DoltEnv) StagedRoot(ctx context.Context) (doltdb.RootValue, error) {
   778  	workingSet, err := dEnv.WorkingSet(ctx)
   779  	if err != nil {
   780  		return nil, err
   781  	}
   782  
   783  	return workingSet.StagedRoot(), nil
   784  }
   785  
   786  // UpdateStagedRoot updates the staged root for the current working branch. This can fail if multiple clients attempt
   787  // to update at the same time.
   788  func (dEnv *DoltEnv) UpdateStagedRoot(ctx context.Context, newRoot doltdb.RootValue) error {
   789  	var h hash.Hash
   790  	var wsRef ref.WorkingSetRef
   791  
   792  	ws, err := dEnv.WorkingSet(ctx)
   793  	if err == doltdb.ErrWorkingSetNotFound {
   794  		// first time updating root
   795  		wsRef, err = ref.WorkingSetRefForHead(dEnv.RepoState.CWBHeadRef())
   796  		if err != nil {
   797  			return err
   798  		}
   799  		ws = doltdb.EmptyWorkingSet(wsRef).WithWorkingRoot(newRoot).WithStagedRoot(newRoot)
   800  	} else if err != nil {
   801  		return err
   802  	} else {
   803  		h, err = ws.HashOf()
   804  		if err != nil {
   805  			return err
   806  		}
   807  
   808  		wsRef = ws.Ref()
   809  	}
   810  
   811  	return dEnv.DoltDB.UpdateWorkingSet(ctx, wsRef, ws.WithStagedRoot(newRoot), h, dEnv.workingSetMeta(), nil)
   812  }
   813  
   814  func (dEnv *DoltEnv) AbortMerge(ctx context.Context) error {
   815  	ws, err := dEnv.WorkingSet(ctx)
   816  	if err != nil {
   817  		return err
   818  	}
   819  
   820  	h, err := ws.HashOf()
   821  	if err != nil {
   822  		return err
   823  	}
   824  
   825  	return dEnv.DoltDB.UpdateWorkingSet(ctx, ws.Ref(), ws.AbortMerge(), h, dEnv.workingSetMeta(), nil)
   826  }
   827  
   828  func (dEnv *DoltEnv) workingSetMeta() *datas.WorkingSetMeta {
   829  	return dEnv.NewWorkingSetMeta("updated from dolt environment")
   830  }
   831  
   832  func (dEnv *DoltEnv) NewWorkingSetMeta(message string) *datas.WorkingSetMeta {
   833  	return &datas.WorkingSetMeta{
   834  		Name:        dEnv.Config.GetStringOrDefault(config.UserNameKey, ""),
   835  		Email:       dEnv.Config.GetStringOrDefault(config.UserEmailKey, ""),
   836  		Timestamp:   uint64(time.Now().Unix()),
   837  		Description: message,
   838  	}
   839  }
   840  
   841  func (dEnv *DoltEnv) CredsDir() (string, error) {
   842  	return getCredsDir(dEnv.hdp)
   843  }
   844  
   845  func (dEnv *DoltEnv) UserDoltCreds() (creds.DoltCreds, bool, error) {
   846  	kid, err := dEnv.Config.GetString(config.UserCreds)
   847  
   848  	if err == nil && kid != "" {
   849  		dir, err := dEnv.CredsDir()
   850  
   851  		if err != nil {
   852  			// not sure why you wouldn't be able to get the creds dir.
   853  			panic(err)
   854  		}
   855  
   856  		c, err := creds.JWKCredsReadFromFile(dEnv.FS, filepath.Join(dir, kid+".jwk"))
   857  		return c, c.IsPrivKeyValid() && c.IsPubKeyValid(), err
   858  	}
   859  
   860  	return creds.DoltCreds{}, false, nil
   861  }
   862  
   863  // GetGRPCDialParams implements dbfactory.GRPCDialProvider
   864  func (dEnv *DoltEnv) GetGRPCDialParams(config grpcendpoint.Config) (dbfactory.GRPCRemoteConfig, error) {
   865  	return NewGRPCDialProviderFromDoltEnv(dEnv).GetGRPCDialParams(config)
   866  }
   867  
   868  func (dEnv *DoltEnv) GetRemotes() (*concurrentmap.Map[string, Remote], error) {
   869  	if dEnv.RSLoadErr != nil {
   870  		return nil, dEnv.RSLoadErr
   871  	}
   872  
   873  	return dEnv.RepoState.Remotes, nil
   874  }
   875  
   876  // CheckRemoteAddressConflict checks whether any backups or remotes share the given URL. Returns the first remote if multiple match.
   877  // Returns NoRemote and false if none match.
   878  func CheckRemoteAddressConflict(absUrl string, remotes *concurrentmap.Map[string, Remote], backups *concurrentmap.Map[string, Remote]) (Remote, bool) {
   879  	if remotes != nil {
   880  		var rm *Remote
   881  		remotes.Iter(func(key string, value Remote) bool {
   882  			if value.Url == absUrl {
   883  				rm = &value
   884  				return false
   885  			}
   886  			return true
   887  		})
   888  		if rm != nil {
   889  			return *rm, true
   890  		}
   891  	}
   892  
   893  	if backups != nil {
   894  		var rm *Remote
   895  		backups.Iter(func(key string, value Remote) bool {
   896  			if value.Url == absUrl {
   897  				rm = &value
   898  				return false
   899  			}
   900  			return true
   901  		})
   902  		if rm != nil {
   903  			return *rm, true
   904  		}
   905  	}
   906  	return NoRemote, false
   907  }
   908  
   909  func (dEnv *DoltEnv) AddRemote(r Remote) error {
   910  	if _, ok := dEnv.RepoState.Remotes.Get(r.Name); ok {
   911  		return ErrRemoteAlreadyExists
   912  	}
   913  
   914  	if strings.IndexAny(r.Name, " \t\n\r./\\!@#$%^&*(){}[],.<>'\"?=+|") != -1 {
   915  		return ErrInvalidRemoteName
   916  	}
   917  
   918  	_, absRemoteUrl, err := GetAbsRemoteUrl(dEnv.FS, dEnv.Config, r.Url)
   919  	if err != nil {
   920  		return fmt.Errorf("%w; %s", ErrInvalidRemoteURL, err.Error())
   921  	}
   922  
   923  	// can have multiple remotes with the same address, but no conflicting backups
   924  	if rem, found := CheckRemoteAddressConflict(absRemoteUrl, nil, dEnv.RepoState.Backups); found {
   925  		return fmt.Errorf("%w: '%s' -> %s", ErrRemoteAddressConflict, rem.Name, rem.Url)
   926  	}
   927  
   928  	r.Url = absRemoteUrl
   929  	dEnv.RepoState.AddRemote(r)
   930  	return dEnv.RepoState.Save(dEnv.FS)
   931  }
   932  
   933  func (dEnv *DoltEnv) GetBackups() (*concurrentmap.Map[string, Remote], error) {
   934  	if dEnv.RSLoadErr != nil {
   935  		return nil, dEnv.RSLoadErr
   936  	}
   937  
   938  	return dEnv.RepoState.Backups, nil
   939  }
   940  
   941  func (dEnv *DoltEnv) AddBackup(r Remote) error {
   942  	if _, ok := dEnv.RepoState.Backups.Get(r.Name); ok {
   943  		return ErrBackupAlreadyExists
   944  	}
   945  
   946  	if strings.IndexAny(r.Name, " \t\n\r./\\!@#$%^&*(){}[],.<>'\"?=+|") != -1 {
   947  		return ErrInvalidBackupName
   948  	}
   949  
   950  	_, absRemoteUrl, err := GetAbsRemoteUrl(dEnv.FS, dEnv.Config, r.Url)
   951  	if err != nil {
   952  		return fmt.Errorf("%w; %s", ErrInvalidBackupURL, err.Error())
   953  	}
   954  
   955  	// no conflicting remote or backup addresses
   956  	if rem, found := CheckRemoteAddressConflict(absRemoteUrl, dEnv.RepoState.Remotes, dEnv.RepoState.Backups); found {
   957  		return fmt.Errorf("%w: '%s' -> %s", ErrRemoteAddressConflict, rem.Name, rem.Url)
   958  	}
   959  
   960  	r.Url = absRemoteUrl
   961  	dEnv.RepoState.AddBackup(r)
   962  	return dEnv.RepoState.Save(dEnv.FS)
   963  }
   964  
   965  func (dEnv *DoltEnv) RemoveRemote(ctx context.Context, name string) error {
   966  	remote, ok := dEnv.RepoState.Remotes.Get(name)
   967  	if !ok {
   968  		return ErrRemoteNotFound
   969  	}
   970  
   971  	ddb := dEnv.DoltDB
   972  	refs, err := ddb.GetRemoteRefs(ctx)
   973  	if err != nil {
   974  		return fmt.Errorf("%w: %s", ErrFailedToReadFromDb, err.Error())
   975  	}
   976  
   977  	for _, r := range refs {
   978  		rr := r.(ref.RemoteRef)
   979  
   980  		if rr.GetRemote() == remote.Name {
   981  			err = ddb.DeleteBranch(ctx, rr, nil)
   982  
   983  			if err != nil {
   984  				return fmt.Errorf("%w; failed to delete remote tracking ref '%s'; %s", ErrFailedToDeleteRemote, rr.String(), err.Error())
   985  			}
   986  		}
   987  	}
   988  
   989  	dEnv.RepoState.RemoveRemote(remote)
   990  	err = dEnv.RepoState.Save(dEnv.FS)
   991  	if err != nil {
   992  		return ErrFailedToWriteRepoState
   993  	}
   994  
   995  	return nil
   996  }
   997  
   998  func (dEnv *DoltEnv) RemoveBackup(ctx context.Context, name string) error {
   999  	backup, ok := dEnv.RepoState.Backups.Get(name)
  1000  	if !ok {
  1001  		return ErrBackupNotFound
  1002  	}
  1003  
  1004  	dEnv.RepoState.RemoveBackup(backup)
  1005  
  1006  	err := dEnv.RepoState.Save(dEnv.FS)
  1007  	if err != nil {
  1008  		return ErrFailedToWriteRepoState
  1009  	}
  1010  
  1011  	return nil
  1012  }
  1013  
  1014  func (dEnv *DoltEnv) GetBranches() (*concurrentmap.Map[string, BranchConfig], error) {
  1015  	if dEnv.RSLoadErr != nil {
  1016  		return nil, dEnv.RSLoadErr
  1017  	}
  1018  
  1019  	return dEnv.RepoState.Branches, nil
  1020  }
  1021  
  1022  func (dEnv *DoltEnv) UpdateBranch(name string, new BranchConfig) error {
  1023  	if dEnv.RSLoadErr != nil {
  1024  		return dEnv.RSLoadErr
  1025  	}
  1026  
  1027  	dEnv.RepoState.Branches.Set(name, new)
  1028  
  1029  	err := dEnv.RepoState.Save(dEnv.FS)
  1030  	if err != nil {
  1031  		return ErrFailedToWriteRepoState
  1032  	}
  1033  	return nil
  1034  }
  1035  
  1036  var ErrNotACred = errors.New("not a valid credential key id or public key")
  1037  
  1038  func (dEnv *DoltEnv) FindCreds(credsDir, pubKeyOrId string) (string, error) {
  1039  	if !creds.B32CredsByteSet.ContainsAll([]byte(pubKeyOrId)) {
  1040  		return "", creds.ErrBadB32CredsEncoding
  1041  	}
  1042  
  1043  	if len(pubKeyOrId) == creds.B32EncodedPubKeyLen {
  1044  		pubKeyOrId, _ = creds.PubKeyStrToKIDStr(pubKeyOrId)
  1045  	}
  1046  
  1047  	if len(pubKeyOrId) != creds.B32EncodedKeyIdLen {
  1048  		return "", ErrNotACred
  1049  	}
  1050  
  1051  	path := mustAbs(dEnv, credsDir, pubKeyOrId+creds.JWKFileExtension)
  1052  	exists, isDir := dEnv.FS.Exists(path)
  1053  
  1054  	if isDir {
  1055  		return path, filesys.ErrIsDir
  1056  	} else if !exists {
  1057  		return "", creds.ErrCredsNotFound
  1058  	} else {
  1059  		return path, nil
  1060  	}
  1061  }
  1062  
  1063  func (dEnv *DoltEnv) FindRef(ctx context.Context, refStr string) (ref.DoltRef, error) {
  1064  	localRef := ref.NewBranchRef(refStr)
  1065  	if hasRef, err := dEnv.DoltDB.HasRef(ctx, localRef); err != nil {
  1066  		return nil, err
  1067  	} else if hasRef {
  1068  		return localRef, nil
  1069  	} else {
  1070  		if strings.HasPrefix(refStr, "remotes/") {
  1071  			refStr = refStr[len("remotes/"):]
  1072  		}
  1073  
  1074  		slashIdx := strings.IndexRune(refStr, '/')
  1075  		if slashIdx > 0 {
  1076  			remoteName := refStr[:slashIdx]
  1077  			if _, ok := dEnv.RepoState.Remotes.Get(remoteName); ok {
  1078  				remoteRef, err := ref.NewRemoteRefFromPathStr(refStr)
  1079  
  1080  				if err != nil {
  1081  					return nil, err
  1082  				}
  1083  
  1084  				if hasRef, err = dEnv.DoltDB.HasRef(ctx, remoteRef); err != nil {
  1085  					return nil, err
  1086  				} else if hasRef {
  1087  					return remoteRef, nil
  1088  				}
  1089  			}
  1090  		}
  1091  	}
  1092  
  1093  	return nil, doltdb.ErrBranchNotFound
  1094  }
  1095  
  1096  // GetRefSpecs takes an optional remoteName and returns all refspecs associated with that remote.  If "" is passed as
  1097  // the remoteName then the default remote is used.
  1098  func GetRefSpecs(rsr RepoStateReader, remoteName string) ([]ref.RemoteRefSpec, error) {
  1099  	var remote Remote
  1100  	var err error
  1101  
  1102  	remotes, err := rsr.GetRemotes()
  1103  	if err != nil {
  1104  		return nil, err
  1105  	}
  1106  	if remoteName == "" {
  1107  		remote, err = GetDefaultRemote(rsr)
  1108  	} else if r, ok := remotes.Get(remoteName); ok {
  1109  		remote = r
  1110  	} else {
  1111  		err = ErrInvalidRepository.New(remoteName)
  1112  	}
  1113  
  1114  	if err != nil {
  1115  		return nil, err
  1116  	}
  1117  
  1118  	var refSpecs []ref.RemoteRefSpec
  1119  	for _, fs := range remote.FetchSpecs {
  1120  		rs, err := ref.ParseRefSpecForRemote(remote.Name, fs)
  1121  
  1122  		if err != nil {
  1123  			return nil, errhand.BuildDError("error: for '%s', '%s' is not a valid refspec.", remote.Name, fs).Build()
  1124  		}
  1125  
  1126  		if rrs, ok := rs.(ref.RemoteRefSpec); !ok {
  1127  			return nil, fmt.Errorf("%w; '%s' is not a valid refspec referring to a remote tracking branch", ref.ErrInvalidRefSpec, remote.Name)
  1128  		} else if rrs.GetRemote() != remote.Name {
  1129  			return nil, ErrInvalidRefSpecRemote
  1130  		} else {
  1131  			refSpecs = append(refSpecs, rrs)
  1132  		}
  1133  	}
  1134  
  1135  	return refSpecs, nil
  1136  }
  1137  
  1138  var ErrInvalidRefSpecRemote = errors.New("refspec refers to different remote")
  1139  var ErrNoRemote = errors.New("no remote")
  1140  var ErrUnknownRemote = errors.New("unknown remote")
  1141  var ErrCantDetermineDefault = errors.New("unable to determine the default remote")
  1142  
  1143  // GetDefaultRemote gets the default remote for the environment.  Not fully implemented yet.  Needs to support multiple
  1144  // repos and a configurable default.
  1145  func GetDefaultRemote(rsr RepoStateReader) (Remote, error) {
  1146  	remotes, err := rsr.GetRemotes()
  1147  	if err != nil {
  1148  		return NoRemote, err
  1149  	}
  1150  
  1151  	remotesLen := remotes.Len()
  1152  	if remotesLen == 0 {
  1153  		return NoRemote, ErrNoRemote
  1154  	} else if remotesLen == 1 {
  1155  		var remote *Remote
  1156  		remotes.Iter(func(key string, value Remote) bool {
  1157  			remote = &value
  1158  			return false
  1159  		})
  1160  		if remote != nil {
  1161  			return *remote, nil
  1162  		}
  1163  	}
  1164  
  1165  	if remote, ok := remotes.Get("origin"); ok {
  1166  		return remote, nil
  1167  	}
  1168  
  1169  	return NoRemote, ErrCantDetermineDefault
  1170  }
  1171  
  1172  // GetUserHomeDir returns the user's home dir
  1173  // based on current filesys
  1174  func (dEnv *DoltEnv) GetUserHomeDir() (string, error) {
  1175  	return getHomeDir(dEnv.hdp)
  1176  }
  1177  
  1178  func (dEnv *DoltEnv) TempTableFilesDir() (string, error) {
  1179  	doltDir := dEnv.GetDoltDir()
  1180  	if doltDir == "" {
  1181  		return "", ErrDoltRepositoryNotFound
  1182  	}
  1183  
  1184  	absPath, err := dEnv.FS.Abs(filepath.Join(doltDir, tempTablesDir))
  1185  	if err != nil {
  1186  		return "", err
  1187  	}
  1188  
  1189  	return absPath, nil
  1190  }
  1191  
  1192  func (dEnv *DoltEnv) DbEaFactory() editor.DbEaFactory {
  1193  	tmpDir, err := dEnv.TempTableFilesDir()
  1194  	if err != nil {
  1195  		return nil
  1196  	}
  1197  	return editor.NewDbEaFactory(tmpDir, dEnv.DoltDB.ValueReadWriter())
  1198  }
  1199  
  1200  func (dEnv *DoltEnv) BulkDbEaFactory() editor.DbEaFactory {
  1201  	tmpDir, err := dEnv.TempTableFilesDir()
  1202  	if err != nil {
  1203  		return nil
  1204  	}
  1205  	return editor.NewBulkImportTEAFactory(dEnv.DoltDB.ValueReadWriter(), tmpDir)
  1206  }
  1207  
  1208  func (dEnv *DoltEnv) IsAccessModeReadOnly() bool {
  1209  	return dEnv.DoltDB.AccessMode() == chunks.ExclusiveAccessMode_ReadOnly
  1210  }