github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/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  	"fmt"
    21  
    22  	"github.com/dolthub/dolt/go/cmd/dolt/errhand"
    23  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    24  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdocs"
    25  	"github.com/dolthub/dolt/go/libraries/doltcore/ref"
    26  	"github.com/dolthub/dolt/go/libraries/utils/filesys"
    27  	"github.com/dolthub/dolt/go/store/hash"
    28  )
    29  
    30  type RepoStateReader interface {
    31  	CWBHeadRef() ref.DoltRef
    32  	CWBHeadSpec() *doltdb.CommitSpec
    33  	CWBHeadHash(ctx context.Context) (hash.Hash, error)
    34  	WorkingHash() hash.Hash
    35  	StagedHash() hash.Hash
    36  	IsMergeActive() bool
    37  	GetMergeCommit() string
    38  	GetPreMergeWorking() string
    39  }
    40  
    41  type RepoStateWriter interface {
    42  	// SetCWBHeadSpec(context.Context, *doltdb.CommitSpec) error
    43  	SetStagedHash(context.Context, hash.Hash) error
    44  	SetWorkingHash(context.Context, hash.Hash) error
    45  	SetCWBHeadRef(context.Context, ref.MarshalableRef) error
    46  	AbortMerge() error
    47  	ClearMerge() error
    48  	StartMerge(commitStr string) error
    49  }
    50  
    51  type DocsReadWriter interface {
    52  	// GetDocsOnDisk returns the docs in the filesytem optionally filtered by docNames.
    53  	GetDocsOnDisk(docNames ...string) (doltdocs.Docs, error)
    54  	// WriteDocsToDisk updates the documents stored in the filesystem with the contents in docs.
    55  	WriteDocsToDisk(docs doltdocs.Docs) error
    56  }
    57  
    58  type DbData struct {
    59  	Ddb *doltdb.DoltDB
    60  	Rsw RepoStateWriter
    61  	Rsr RepoStateReader
    62  	Drw DocsReadWriter
    63  }
    64  
    65  type BranchConfig struct {
    66  	Merge  ref.MarshalableRef `json:"head"`
    67  	Remote string             `json:"remote"`
    68  }
    69  
    70  type MergeState struct {
    71  	Commit          string `json:"commit"`
    72  	PreMergeWorking string `json:"working_pre_merge"`
    73  }
    74  
    75  type RepoState struct {
    76  	Head     ref.MarshalableRef      `json:"head"`
    77  	Staged   string                  `json:"staged"`
    78  	Working  string                  `json:"working"`
    79  	Merge    *MergeState             `json:"merge"`
    80  	Remotes  map[string]Remote       `json:"remotes"`
    81  	Branches map[string]BranchConfig `json:"branches"`
    82  }
    83  
    84  func LoadRepoState(fs filesys.ReadWriteFS) (*RepoState, error) {
    85  	path := getRepoStateFile()
    86  	data, err := fs.ReadFile(path)
    87  
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	var repoState RepoState
    93  	err = json.Unmarshal(data, &repoState)
    94  
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  
    99  	return &repoState, nil
   100  }
   101  
   102  func CloneRepoState(fs filesys.ReadWriteFS, r Remote) (*RepoState, error) {
   103  	h := hash.Hash{}
   104  	hashStr := h.String()
   105  	rs := &RepoState{ref.MarshalableRef{
   106  		Ref: ref.NewBranchRef("master")},
   107  		hashStr,
   108  		hashStr,
   109  		nil,
   110  		map[string]Remote{r.Name: r},
   111  		make(map[string]BranchConfig),
   112  	}
   113  
   114  	err := rs.Save(fs)
   115  
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	return rs, nil
   121  }
   122  
   123  func CreateRepoState(fs filesys.ReadWriteFS, br string, rootHash hash.Hash) (*RepoState, error) {
   124  	hashStr := rootHash.String()
   125  	headRef, err := ref.Parse(br)
   126  
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	rs := &RepoState{
   132  		ref.MarshalableRef{Ref: headRef},
   133  		hashStr,
   134  		hashStr,
   135  		nil,
   136  		make(map[string]Remote),
   137  		make(map[string]BranchConfig),
   138  	}
   139  
   140  	err = rs.Save(fs)
   141  
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  
   146  	return rs, nil
   147  }
   148  
   149  func (rs *RepoState) Save(fs filesys.ReadWriteFS) error {
   150  	data, err := json.MarshalIndent(rs, "", "  ")
   151  
   152  	if err != nil {
   153  		return err
   154  	}
   155  
   156  	path := getRepoStateFile()
   157  
   158  	return fs.WriteFile(path, data)
   159  }
   160  
   161  func (rs *RepoState) CWBHeadRef() ref.DoltRef {
   162  	return rs.Head.Ref
   163  }
   164  
   165  func (rs *RepoState) CWBHeadSpec() *doltdb.CommitSpec {
   166  	spec, _ := doltdb.NewCommitSpec("HEAD")
   167  	return spec
   168  }
   169  
   170  func (rs *RepoState) StartMerge(commit string, fs filesys.Filesys) error {
   171  	rs.Merge = &MergeState{commit, rs.Working}
   172  	return rs.Save(fs)
   173  }
   174  
   175  func (rs *RepoState) AbortMerge(fs filesys.Filesys) error {
   176  	rs.Working = rs.Merge.PreMergeWorking
   177  	return rs.ClearMerge(fs)
   178  }
   179  
   180  func (rs *RepoState) ClearMerge(fs filesys.Filesys) error {
   181  	rs.Merge = nil
   182  	return rs.Save(fs)
   183  }
   184  
   185  func (rs *RepoState) AddRemote(r Remote) {
   186  	rs.Remotes[r.Name] = r
   187  }
   188  
   189  func (rs *RepoState) WorkingHash() hash.Hash {
   190  	return hash.Parse(rs.Working)
   191  }
   192  
   193  func (rs *RepoState) StagedHash() hash.Hash {
   194  	return hash.Parse(rs.Staged)
   195  }
   196  
   197  func (rs *RepoState) IsMergeActive() bool {
   198  	return rs.Merge != nil
   199  }
   200  
   201  func (rs *RepoState) GetMergeCommit() string {
   202  	return rs.Merge.Commit
   203  }
   204  
   205  // Returns the working root.
   206  func WorkingRoot(ctx context.Context, ddb *doltdb.DoltDB, rsr RepoStateReader) (*doltdb.RootValue, error) {
   207  	return ddb.ReadRootValue(ctx, rsr.WorkingHash())
   208  }
   209  
   210  // Updates the working root.
   211  func UpdateWorkingRoot(ctx context.Context, ddb *doltdb.DoltDB, rsw RepoStateWriter, newRoot *doltdb.RootValue) (hash.Hash, error) {
   212  	h, err := ddb.WriteRootValue(ctx, newRoot)
   213  
   214  	if err != nil {
   215  		return hash.Hash{}, doltdb.ErrNomsIO
   216  	}
   217  
   218  	err = rsw.SetWorkingHash(ctx, h)
   219  
   220  	if err != nil {
   221  		return hash.Hash{}, ErrStateUpdate
   222  	}
   223  
   224  	return h, nil
   225  }
   226  
   227  // Returns the head root.
   228  func HeadRoot(ctx context.Context, ddb *doltdb.DoltDB, rsr RepoStateReader) (*doltdb.RootValue, error) {
   229  	commit, err := ddb.ResolveCommitRef(ctx, rsr.CWBHeadRef())
   230  
   231  	if err != nil {
   232  		return nil, err
   233  	}
   234  
   235  	return commit.GetRootValue()
   236  }
   237  
   238  // Returns the staged root.
   239  func StagedRoot(ctx context.Context, ddb *doltdb.DoltDB, rsr RepoStateReader) (*doltdb.RootValue, error) {
   240  	return ddb.ReadRootValue(ctx, rsr.StagedHash())
   241  }
   242  
   243  // Updates the staged root.
   244  func UpdateStagedRoot(ctx context.Context, ddb *doltdb.DoltDB, rsw RepoStateWriter, newRoot *doltdb.RootValue) (hash.Hash, error) {
   245  	h, err := ddb.WriteRootValue(ctx, newRoot)
   246  
   247  	if err != nil {
   248  		return hash.Hash{}, doltdb.ErrNomsIO
   249  	}
   250  
   251  	err = rsw.SetStagedHash(ctx, h)
   252  
   253  	if err != nil {
   254  		return hash.Hash{}, ErrStateUpdate
   255  	}
   256  
   257  	return h, nil
   258  }
   259  
   260  func UpdateStagedRootWithVErr(ddb *doltdb.DoltDB, rsw RepoStateWriter, updatedRoot *doltdb.RootValue) errhand.VerboseError {
   261  	_, err := UpdateStagedRoot(context.Background(), ddb, rsw, updatedRoot)
   262  
   263  	switch err {
   264  	case doltdb.ErrNomsIO:
   265  		return errhand.BuildDError("fatal: failed to write value").Build()
   266  	case ErrStateUpdate:
   267  		return errhand.BuildDError("fatal: failed to update the staged root state").Build()
   268  	}
   269  
   270  	return nil
   271  }
   272  
   273  func GetRoots(ctx context.Context, ddb *doltdb.DoltDB, rsr RepoStateReader) (working *doltdb.RootValue, staged *doltdb.RootValue, head *doltdb.RootValue, err error) {
   274  	working, err = WorkingRoot(ctx, ddb, rsr)
   275  
   276  	if err != nil {
   277  		return nil, nil, nil, err
   278  	}
   279  
   280  	staged, err = StagedRoot(ctx, ddb, rsr)
   281  
   282  	if err != nil {
   283  		return nil, nil, nil, err
   284  	}
   285  
   286  	head, err = HeadRoot(ctx, ddb, rsr)
   287  
   288  	if err != nil {
   289  		return nil, nil, nil, err
   290  	}
   291  
   292  	return working, staged, head, nil
   293  }
   294  
   295  func MergeWouldStompChanges(ctx context.Context, mergeCommit *doltdb.Commit, dbData DbData) ([]string, map[string]hash.Hash, error) {
   296  	headRoot, err := HeadRoot(ctx, dbData.Ddb, dbData.Rsr)
   297  
   298  	if err != nil {
   299  		return nil, nil, err
   300  	}
   301  
   302  	workingRoot, err := WorkingRoot(ctx, dbData.Ddb, dbData.Rsr)
   303  
   304  	if err != nil {
   305  		return nil, nil, err
   306  	}
   307  
   308  	mergeRoot, err := mergeCommit.GetRootValue()
   309  
   310  	if err != nil {
   311  		return nil, nil, err
   312  	}
   313  
   314  	headTableHashes, err := mapTableHashes(ctx, headRoot)
   315  
   316  	if err != nil {
   317  		return nil, nil, err
   318  	}
   319  
   320  	workingTableHashes, err := mapTableHashes(ctx, workingRoot)
   321  
   322  	if err != nil {
   323  		return nil, nil, err
   324  	}
   325  
   326  	mergeTableHashes, err := mapTableHashes(ctx, mergeRoot)
   327  
   328  	if err != nil {
   329  		return nil, nil, err
   330  	}
   331  
   332  	headWorkingDiffs := diffTableHashes(headTableHashes, workingTableHashes)
   333  	mergeWorkingDiffs := diffTableHashes(headTableHashes, mergeTableHashes)
   334  
   335  	stompedTables := make([]string, 0, len(headWorkingDiffs))
   336  	for tName, _ := range headWorkingDiffs {
   337  		if _, ok := mergeWorkingDiffs[tName]; ok {
   338  			// even if the working changes match the merge changes, don't allow (matches git behavior).
   339  			stompedTables = append(stompedTables, tName)
   340  		}
   341  	}
   342  
   343  	return stompedTables, headWorkingDiffs, nil
   344  }
   345  
   346  // GetGCKeepers queries |rsr| to find a list of values that need to be temporarily saved during GC.
   347  func GetGCKeepers(ctx context.Context, rsr RepoStateReader, ddb *doltdb.DoltDB) ([]hash.Hash, error) {
   348  	keepers := []hash.Hash{
   349  		rsr.WorkingHash(),
   350  		rsr.StagedHash(),
   351  	}
   352  
   353  	if rsr.IsMergeActive() {
   354  		spec, err := doltdb.NewCommitSpec(rsr.GetMergeCommit())
   355  		if err != nil {
   356  			return nil, err
   357  		}
   358  
   359  		cm, err := ddb.Resolve(ctx, spec, nil)
   360  		if err != nil {
   361  			return nil, err
   362  		}
   363  
   364  		ch, err := cm.HashOf()
   365  		if err != nil {
   366  			return nil, err
   367  		}
   368  
   369  		pmw := hash.Parse(rsr.GetPreMergeWorking())
   370  		val, err := ddb.ValueReadWriter().ReadValue(ctx, pmw)
   371  		if err != nil {
   372  			return nil, err
   373  		}
   374  		if val == nil {
   375  			return nil, fmt.Errorf("MergeState.PreMergeWorking is a dangling hash")
   376  		}
   377  
   378  		keepers = append(keepers, ch, pmw)
   379  	}
   380  
   381  	return keepers, nil
   382  }
   383  
   384  func mapTableHashes(ctx context.Context, root *doltdb.RootValue) (map[string]hash.Hash, error) {
   385  	names, err := root.GetTableNames(ctx)
   386  
   387  	if err != nil {
   388  		return nil, err
   389  	}
   390  
   391  	nameToHash := make(map[string]hash.Hash)
   392  	for _, name := range names {
   393  		h, ok, err := root.GetTableHash(ctx, name)
   394  
   395  		if err != nil {
   396  			return nil, err
   397  		} else if !ok {
   398  			panic("GetTableNames returned a table that GetTableHash says isn't there.")
   399  		} else {
   400  			nameToHash[name] = h
   401  		}
   402  	}
   403  
   404  	return nameToHash, nil
   405  }
   406  
   407  func diffTableHashes(headTableHashes, otherTableHashes map[string]hash.Hash) map[string]hash.Hash {
   408  	diffs := make(map[string]hash.Hash)
   409  	for tName, hh := range headTableHashes {
   410  		if h, ok := otherTableHashes[tName]; ok {
   411  			if h != hh {
   412  				// modification
   413  				diffs[tName] = h
   414  			}
   415  		} else {
   416  			// deletion
   417  			diffs[tName] = hash.Hash{}
   418  		}
   419  	}
   420  
   421  	for tName, h := range otherTableHashes {
   422  		if _, ok := headTableHashes[tName]; !ok {
   423  			// addition
   424  			diffs[tName] = h
   425  		}
   426  	}
   427  
   428  	return diffs
   429  }