github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/env/repo_state.go (about)

     1  // Copyright 2019 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  	"encoding/json"
    20  	"os"
    21  
    22  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    23  	"github.com/dolthub/dolt/go/libraries/doltcore/ref"
    24  	"github.com/dolthub/dolt/go/libraries/utils/concurrentmap"
    25  	"github.com/dolthub/dolt/go/libraries/utils/filesys"
    26  	"github.com/dolthub/dolt/go/store/hash"
    27  	"github.com/dolthub/dolt/go/store/types"
    28  )
    29  
    30  // TODO: change name to ClientStateReader, move out of env package
    31  type RepoStateReader interface {
    32  	CWBHeadRef() (ref.DoltRef, error)
    33  	CWBHeadSpec() (*doltdb.CommitSpec, error)
    34  	GetRemotes() (*concurrentmap.Map[string, Remote], error)
    35  	GetBackups() (*concurrentmap.Map[string, Remote], error)
    36  	GetBranches() (*concurrentmap.Map[string, BranchConfig], error)
    37  }
    38  
    39  type RepoStateWriter interface {
    40  	// TODO: kill this
    41  	SetCWBHeadRef(context.Context, ref.MarshalableRef) error
    42  	AddRemote(r Remote) error
    43  	AddBackup(r Remote) error
    44  	RemoveRemote(ctx context.Context, name string) error
    45  	RemoveBackup(ctx context.Context, name string) error
    46  	TempTableFilesDir() (string, error)
    47  	UpdateBranch(name string, new BranchConfig) error
    48  }
    49  
    50  type RepoStateReadWriter interface {
    51  	RepoStateReader
    52  	RepoStateWriter
    53  }
    54  
    55  // RemoteDbProvider is an interface for getting a database from a remote
    56  type RemoteDbProvider interface {
    57  	GetRemoteDB(ctx context.Context, format *types.NomsBinFormat, r Remote, withCaching bool) (*doltdb.DoltDB, error)
    58  }
    59  
    60  type DbData struct {
    61  	Ddb *doltdb.DoltDB
    62  	Rsw RepoStateWriter
    63  	Rsr RepoStateReader
    64  }
    65  
    66  type BranchConfig struct {
    67  	Merge  ref.MarshalableRef `json:"head"`
    68  	Remote string             `json:"remote"`
    69  }
    70  
    71  type RepoState struct {
    72  	Head     ref.MarshalableRef                       `json:"head"`
    73  	Remotes  *concurrentmap.Map[string, Remote]       `json:"remotes"`
    74  	Backups  *concurrentmap.Map[string, Remote]       `json:"backups"`
    75  	Branches *concurrentmap.Map[string, BranchConfig] `json:"branches"`
    76  	// |staged|, |working|, and |merge| are legacy fields left over from when Dolt repos stored this info in the repo
    77  	// state file, not in the DB directly. They're still here so that we can migrate existing repositories forward to the
    78  	// new storage format, but they should be used only for this purpose and are no longer written.
    79  	staged  string
    80  	working string
    81  	merge   *mergeState
    82  }
    83  
    84  // repoStateLegacy only exists to unmarshall legacy repo state files, since the JSON marshaller can't work with
    85  // unexported fields
    86  type repoStateLegacy struct {
    87  	Head     ref.MarshalableRef                       `json:"head"`
    88  	Remotes  *concurrentmap.Map[string, Remote]       `json:"remotes"`
    89  	Backups  *concurrentmap.Map[string, Remote]       `json:"backups"`
    90  	Branches *concurrentmap.Map[string, BranchConfig] `json:"branches"`
    91  	Staged   string                                   `json:"staged,omitempty"`
    92  	Working  string                                   `json:"working,omitempty"`
    93  	Merge    *mergeState                              `json:"merge,omitempty"`
    94  }
    95  
    96  // repoStateLegacyFromRepoState creates a new repoStateLegacy from a RepoState file. Only for testing.
    97  func repoStateLegacyFromRepoState(rs *RepoState) *repoStateLegacy {
    98  	return &repoStateLegacy{
    99  		Head:     rs.Head,
   100  		Remotes:  rs.Remotes,
   101  		Backups:  rs.Backups,
   102  		Branches: rs.Branches,
   103  		Staged:   rs.staged,
   104  		Working:  rs.working,
   105  		Merge:    rs.merge,
   106  	}
   107  }
   108  
   109  type mergeState struct {
   110  	Commit          string `json:"commit"`
   111  	PreMergeWorking string `json:"working_pre_merge"`
   112  }
   113  
   114  func (rs *repoStateLegacy) toRepoState() *RepoState {
   115  	newRS := &RepoState{
   116  		Head:     rs.Head,
   117  		Remotes:  rs.Remotes,
   118  		Backups:  rs.Backups,
   119  		Branches: rs.Branches,
   120  		staged:   rs.Staged,
   121  		working:  rs.Working,
   122  		merge:    rs.Merge,
   123  	}
   124  
   125  	if newRS.Remotes == nil {
   126  		newRS.Remotes = concurrentmap.New[string, Remote]()
   127  	}
   128  	if newRS.Backups == nil {
   129  		newRS.Backups = concurrentmap.New[string, Remote]()
   130  	}
   131  	if newRS.Branches == nil {
   132  		newRS.Branches = concurrentmap.New[string, BranchConfig]()
   133  	}
   134  
   135  	return newRS
   136  }
   137  
   138  func (rs *repoStateLegacy) save(fs filesys.ReadWriteFS) error {
   139  	data, err := json.MarshalIndent(rs, "", "  ")
   140  	if err != nil {
   141  		return err
   142  	}
   143  
   144  	return fs.WriteFile(getRepoStateFile(), data, os.ModePerm)
   145  }
   146  
   147  // LoadRepoState parses the repo state file from the file system given
   148  func LoadRepoState(fs filesys.ReadWriteFS) (*RepoState, error) {
   149  	path := getRepoStateFile()
   150  	data, err := fs.ReadFile(path)
   151  
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	var repoState repoStateLegacy
   157  	err = json.Unmarshal(data, &repoState)
   158  
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  
   163  	return repoState.toRepoState(), nil
   164  }
   165  
   166  func CloneRepoState(fs filesys.ReadWriteFS, r Remote) (*RepoState, error) {
   167  	init := ref.NewBranchRef(DefaultInitBranch) // best effort
   168  	hashStr := hash.Hash{}.String()
   169  	remotes := concurrentmap.New[string, Remote]()
   170  	remotes.Set(r.Name, r)
   171  	rs := &RepoState{
   172  		Head:     ref.MarshalableRef{Ref: init},
   173  		staged:   hashStr,
   174  		working:  hashStr,
   175  		Remotes:  remotes,
   176  		Branches: concurrentmap.New[string, BranchConfig](),
   177  		Backups:  concurrentmap.New[string, Remote](),
   178  	}
   179  
   180  	err := rs.Save(fs)
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  
   185  	return rs, nil
   186  }
   187  
   188  func CreateRepoState(fs filesys.ReadWriteFS, br string) (*RepoState, error) {
   189  	headRef, err := ref.Parse(br)
   190  
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	rs := &RepoState{
   196  		Head:     ref.MarshalableRef{Ref: headRef},
   197  		Remotes:  concurrentmap.New[string, Remote](),
   198  		Branches: concurrentmap.New[string, BranchConfig](),
   199  		Backups:  concurrentmap.New[string, Remote](),
   200  	}
   201  
   202  	err = rs.Save(fs)
   203  
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  
   208  	return rs, nil
   209  }
   210  
   211  // Save writes this repo state file to disk on the filesystem given
   212  func (rs RepoState) Save(fs filesys.ReadWriteFS) error {
   213  	data, err := json.MarshalIndent(rs, "", "  ")
   214  	if err != nil {
   215  		return err
   216  	}
   217  
   218  	return fs.WriteFile(getRepoStateFile(), data, os.ModePerm)
   219  }
   220  
   221  func (rs *RepoState) CWBHeadRef() ref.DoltRef {
   222  	return rs.Head.Ref
   223  }
   224  
   225  func (rs *RepoState) CWBHeadSpec() *doltdb.CommitSpec {
   226  	spec, _ := doltdb.NewCommitSpec("HEAD")
   227  	return spec
   228  }
   229  
   230  func (rs *RepoState) AddRemote(r Remote) {
   231  	rs.Remotes.Set(r.Name, r)
   232  }
   233  
   234  func (rs *RepoState) RemoveRemote(r Remote) {
   235  	rs.Remotes.Delete(r.Name)
   236  }
   237  
   238  func (rs *RepoState) AddBackup(r Remote) {
   239  	rs.Backups.Set(r.Name, r)
   240  }
   241  
   242  func (rs *RepoState) RemoveBackup(r Remote) {
   243  	rs.Backups.Delete(r.Name)
   244  }