github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/doltdb/commit.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 doltdb
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  
    22  	"github.com/dolthub/dolt/go/store/datas"
    23  	"github.com/dolthub/dolt/go/store/hash"
    24  	"github.com/dolthub/dolt/go/store/prolly"
    25  	"github.com/dolthub/dolt/go/store/prolly/tree"
    26  	"github.com/dolthub/dolt/go/store/types"
    27  )
    28  
    29  var errCommitHasNoMeta = errors.New("commit has no metadata")
    30  var errHasNoRootValue = errors.New("no root value")
    31  
    32  // TODO: Include the commit id in the error. Unfortunately, this message is passed through the SQL layer. The only way we currently
    33  // have on the client side to match an error is with string matching. We possibly need error codes as a prefix to the error message, but
    34  // currently there is not standard for doing this in Dolt.
    35  var ErrGhostCommitEncountered = errors.New("Commit not found. You are using a shallow clone which does not contain the requested commit. Please do a full clone.")
    36  var ErrGhostCommitRuntimeFailure = errors.New("runtime failure: Ghost commit encountered unexpectedly. Please report bug to: https://github.com/dolthub/dolt/issues")
    37  
    38  // Rootish is an object resolvable to a RootValue.
    39  type Rootish interface {
    40  	// ResolveRootValue resolves a Rootish to a RootValue.
    41  	ResolveRootValue(ctx context.Context) (RootValue, error)
    42  
    43  	// HashOf returns the hash.Hash of the Rootish.
    44  	HashOf() (hash.Hash, error)
    45  }
    46  
    47  // Commit contains information on a commit that was written to noms
    48  type Commit struct {
    49  	vrw     types.ValueReadWriter
    50  	ns      tree.NodeStore
    51  	parents []*datas.Commit
    52  	dCommit *datas.Commit
    53  }
    54  
    55  type OptionalCommit struct {
    56  	Commit *Commit
    57  	Addr   hash.Hash
    58  }
    59  
    60  // ToCommit unwraps the *Commit contained by the OptionalCommit. If the commit is invalid, it returns (nil, false).
    61  // Otherwise, it returns (commit, true).
    62  func (cmt *OptionalCommit) ToCommit() (*Commit, bool) {
    63  	if cmt.Commit == nil {
    64  		return nil, false
    65  	}
    66  	return cmt.Commit, true
    67  }
    68  
    69  var _ Rootish = &Commit{}
    70  
    71  // NewCommit generates a new Commit object that wraps a supplied datas.Commit.
    72  func NewCommit(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, commit *datas.Commit) (*Commit, error) {
    73  	if commit.IsGhost() {
    74  		return nil, ErrGhostCommitRuntimeFailure
    75  	}
    76  
    77  	parents, err := datas.GetCommitParents(ctx, vrw, commit.NomsValue())
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	return &Commit{vrw, ns, parents, commit}, nil
    82  }
    83  
    84  // NewCommitFromValue generates a new Commit object that wraps a supplied types.Value.
    85  func NewCommitFromValue(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, value types.Value) (*Commit, error) {
    86  	commit, err := datas.CommitFromValue(vrw.Format(), value)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  	return NewCommit(ctx, vrw, ns, commit)
    91  }
    92  
    93  // HashOf returns the hash of the commit
    94  func (c *Commit) HashOf() (hash.Hash, error) {
    95  	return c.dCommit.Addr(), nil
    96  }
    97  
    98  // Value returns the types.Value that backs the commit.
    99  func (c *Commit) Value() types.Value {
   100  	return c.dCommit.NomsValue()
   101  }
   102  
   103  // GetCommitMeta gets the metadata associated with the commit
   104  func (c *Commit) GetCommitMeta(ctx context.Context) (*datas.CommitMeta, error) {
   105  	return datas.GetCommitMeta(ctx, c.dCommit.NomsValue())
   106  }
   107  
   108  // DatasParents returns the []*datas.Commit of the commit parents.
   109  func (c *Commit) DatasParents() []*datas.Commit {
   110  	return c.parents
   111  }
   112  
   113  // ParentHashes returns the commit hashes for all parent commits.
   114  func (c *Commit) ParentHashes(ctx context.Context) ([]hash.Hash, error) {
   115  	hashes := make([]hash.Hash, len(c.parents))
   116  	for i, pr := range c.parents {
   117  		hashes[i] = pr.Addr()
   118  	}
   119  	return hashes, nil
   120  }
   121  
   122  // NumParents gets the number of parents a commit has.
   123  func (c *Commit) NumParents() int {
   124  	return len(c.parents)
   125  }
   126  
   127  func (c *Commit) Height() (uint64, error) {
   128  	return c.dCommit.Height(), nil
   129  }
   130  
   131  // GetRootValue gets the RootValue of the commit.
   132  func (c *Commit) GetRootValue(ctx context.Context) (RootValue, error) {
   133  	rootV, err := datas.GetCommittedValue(ctx, c.vrw, c.dCommit.NomsValue())
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	if rootV == nil {
   138  		return nil, errHasNoRootValue
   139  	}
   140  	return NewRootValue(ctx, c.vrw, c.ns, rootV)
   141  }
   142  
   143  func (c *Commit) GetParent(ctx context.Context, idx int) (*OptionalCommit, error) {
   144  	parent := c.parents[idx]
   145  	if parent.IsGhost() {
   146  		return &OptionalCommit{nil, parent.Addr()}, nil
   147  	}
   148  
   149  	cmt, err := NewCommit(ctx, c.vrw, c.ns, parent)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  	return &OptionalCommit{cmt, parent.Addr()}, nil
   154  }
   155  
   156  func (c *Commit) GetCommitClosure(ctx context.Context) (prolly.CommitClosure, error) {
   157  	return getCommitClosure(ctx, c.dCommit, c.vrw, c.ns)
   158  }
   159  
   160  func getCommitClosure(ctx context.Context, cmt *datas.Commit, vrw types.ValueReadWriter, ns tree.NodeStore) (prolly.CommitClosure, error) {
   161  	switch v := cmt.NomsValue().(type) {
   162  	case types.SerialMessage:
   163  		return datas.NewParentsClosure(ctx, cmt, v, vrw, ns)
   164  	default:
   165  		return prolly.CommitClosure{}, fmt.Errorf("old format lacks commit closure")
   166  	}
   167  }
   168  
   169  var ErrNoCommonAncestor = errors.New("no common ancestor")
   170  
   171  func GetCommitAncestor(ctx context.Context, cm1, cm2 *Commit) (*OptionalCommit, error) {
   172  	addr, err := getCommitAncestorAddr(ctx, cm1.dCommit, cm2.dCommit, cm1.vrw, cm2.vrw, cm1.ns, cm2.ns)
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  
   177  	targetCommit, err := datas.LoadCommitAddr(ctx, cm1.vrw, addr)
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  
   182  	if targetCommit.IsGhost() {
   183  		return &OptionalCommit{nil, addr}, nil
   184  	}
   185  
   186  	cmt, err := NewCommit(ctx, cm1.vrw, cm1.ns, targetCommit)
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  	return &OptionalCommit{cmt, addr}, nil
   191  }
   192  
   193  func getCommitAncestorAddr(ctx context.Context, c1, c2 *datas.Commit, vrw1, vrw2 types.ValueReadWriter, ns1, ns2 tree.NodeStore) (hash.Hash, error) {
   194  	ancestorAddr, ok, err := datas.FindCommonAncestor(ctx, c1, c2, vrw1, vrw2, ns1, ns2)
   195  	if err != nil {
   196  		return hash.Hash{}, err
   197  	}
   198  
   199  	if !ok {
   200  		return hash.Hash{}, ErrNoCommonAncestor
   201  	}
   202  
   203  	return ancestorAddr, nil
   204  }
   205  
   206  func (c *Commit) CanFastForwardTo(ctx context.Context, new *Commit) (bool, error) {
   207  	optAnc, err := GetCommitAncestor(ctx, c, new)
   208  	if err != nil {
   209  		return false, err
   210  	}
   211  
   212  	ancestor, ok := optAnc.ToCommit()
   213  	if !ok {
   214  		return false, fmt.Errorf("Unexpected Ghost Commit")
   215  	}
   216  	if ancestor == nil {
   217  		return false, errors.New("cannot perform fast forward merge; commits have no common ancestor")
   218  	} else if ancestor.dCommit.Addr() == c.dCommit.Addr() {
   219  		if ancestor.dCommit.Addr() == new.dCommit.Addr() {
   220  			return true, ErrUpToDate
   221  		}
   222  		return true, nil
   223  	} else if ancestor.dCommit.Addr() == new.dCommit.Addr() {
   224  		return false, ErrIsAhead
   225  	}
   226  
   227  	return false, nil
   228  }
   229  
   230  func (c *Commit) CanFastReverseTo(ctx context.Context, new *Commit) (bool, error) {
   231  	optAnc, err := GetCommitAncestor(ctx, c, new)
   232  	if err != nil {
   233  		return false, err
   234  	}
   235  
   236  	ancestor, ok := optAnc.ToCommit()
   237  	if !ok {
   238  		return false, ErrGhostCommitEncountered
   239  	}
   240  	if ancestor == nil {
   241  		return false, errors.New("cannot perform fast forward merge; commits have no common ancestor")
   242  	} else if ancestor.dCommit.Addr() == new.dCommit.Addr() {
   243  		if ancestor.dCommit.Addr() == c.dCommit.Addr() {
   244  			return true, ErrUpToDate
   245  		}
   246  		return true, nil
   247  	} else if ancestor.dCommit.Addr() == c.dCommit.Addr() {
   248  		return false, ErrIsBehind
   249  	}
   250  
   251  	return false, nil
   252  }
   253  
   254  func (c *Commit) GetAncestor(ctx context.Context, as *AncestorSpec) (*OptionalCommit, error) {
   255  	addr, err := c.HashOf()
   256  	if err != nil {
   257  		return nil, err
   258  	}
   259  	optInst := &OptionalCommit{c, addr}
   260  	if as == nil || len(as.Instructions) == 0 {
   261  		return optInst, nil
   262  	}
   263  
   264  	hardInst := c
   265  	instructions := as.Instructions
   266  	for _, inst := range instructions {
   267  		if inst >= hardInst.NumParents() {
   268  			return nil, ErrInvalidAncestorSpec
   269  		}
   270  
   271  		var err error
   272  		optInst, err = hardInst.GetParent(ctx, inst)
   273  		if err != nil {
   274  			return nil, err
   275  		}
   276  
   277  		var ok bool
   278  		hardInst, ok = optInst.ToCommit()
   279  		if !ok {
   280  			break
   281  		}
   282  	}
   283  
   284  	return optInst, nil
   285  }
   286  
   287  // ResolveRootValue implements Rootish.
   288  func (c *Commit) ResolveRootValue(ctx context.Context) (RootValue, error) {
   289  	return c.GetRootValue(ctx)
   290  }
   291  
   292  // PendingCommit represents a commit that hasn't yet been written to storage. It contains a root value and options to
   293  // use when committing it. Use a PendingCommit when it's important to update the working set and HEAD together
   294  // atomically, via doltdb.CommitWithWorkingSet
   295  type PendingCommit struct {
   296  	Roots         Roots
   297  	Val           types.Value
   298  	CommitOptions datas.CommitOptions
   299  }
   300  
   301  // NewPendingCommit returns a new PendingCommit object to be written with doltdb.CommitWithWorkingSet.
   302  // |roots| are the current roots to include in the PendingCommit. roots.Staged is used as the new root to package in the
   303  // commit, once written.
   304  // |headRef| is the ref of the HEAD the commit will update
   305  // |mergeParentCommits| are any merge parents for this commit
   306  // |cm| is the metadata for the commit
   307  // The current branch head will be automatically filled in as the first parent at commit time.
   308  func (ddb *DoltDB) NewPendingCommit(
   309  	ctx context.Context,
   310  	roots Roots,
   311  	mergeParentCommits []*Commit,
   312  	cm *datas.CommitMeta,
   313  ) (*PendingCommit, error) {
   314  	newstaged, val, err := ddb.writeRootValue(ctx, roots.Staged)
   315  	if err != nil {
   316  		return nil, err
   317  	}
   318  	roots.Staged = newstaged
   319  
   320  	var parents []hash.Hash
   321  	for _, pc := range mergeParentCommits {
   322  		parents = append(parents, pc.dCommit.Addr())
   323  	}
   324  
   325  	commitOpts := datas.CommitOptions{Parents: parents, Meta: cm}
   326  	return &PendingCommit{
   327  		Roots:         roots,
   328  		Val:           val,
   329  		CommitOptions: commitOpts,
   330  	}, nil
   331  }