Files
dolt/go/libraries/doltcore/doltdb/commit.go
2023-04-25 13:15:57 -07:00

270 lines
7.9 KiB
Go

// Copyright 2019 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package doltdb
import (
"context"
"errors"
"fmt"
"github.com/dolthub/dolt/go/store/datas"
"github.com/dolthub/dolt/go/store/hash"
"github.com/dolthub/dolt/go/store/prolly"
"github.com/dolthub/dolt/go/store/prolly/tree"
"github.com/dolthub/dolt/go/store/types"
)
var errCommitHasNoMeta = errors.New("commit has no metadata")
var errHasNoRootValue = errors.New("no root value")
// Rootish is an object resolvable to a RootValue.
type Rootish interface {
// ResolveRootValue resolves a Rootish to a RootValue.
ResolveRootValue(ctx context.Context) (*RootValue, error)
// HashOf returns the hash.Hash of the Rootish.
HashOf() (hash.Hash, error)
}
// Commit contains information on a commit that was written to noms
type Commit struct {
vrw types.ValueReadWriter
ns tree.NodeStore
parents []*datas.Commit
dCommit *datas.Commit
}
var _ Rootish = &Commit{}
// NewCommit generates a new Commit object that wraps a supplies datas.Commit.
func NewCommit(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, commit *datas.Commit) (*Commit, error) {
parents, err := datas.GetCommitParents(ctx, vrw, commit.NomsValue())
if err != nil {
return nil, err
}
return &Commit{vrw, ns, parents, commit}, nil
}
// NewCommitFromValue generates a new Commit object that wraps a supplied types.Value.
func NewCommitFromValue(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, value types.Value) (*Commit, error) {
commit, err := datas.CommitFromValue(vrw.Format(), value)
if err != nil {
return nil, err
}
return NewCommit(ctx, vrw, ns, commit)
}
// HashOf returns the hash of the commit
func (c *Commit) HashOf() (hash.Hash, error) {
return c.dCommit.Addr(), nil
}
// Value returns the types.Value that backs the commit.
func (c *Commit) Value() types.Value {
return c.dCommit.NomsValue()
}
// GetCommitMeta gets the metadata associated with the commit
func (c *Commit) GetCommitMeta(ctx context.Context) (*datas.CommitMeta, error) {
return datas.GetCommitMeta(ctx, c.dCommit.NomsValue())
}
// DatasParents returns the []*datas.Commit of the commit parents.
func (c *Commit) DatasParents() []*datas.Commit {
return c.parents
}
// ParentHashes returns the commit hashes for all parent commits.
func (c *Commit) ParentHashes(ctx context.Context) ([]hash.Hash, error) {
hashes := make([]hash.Hash, len(c.parents))
for i, pr := range c.parents {
hashes[i] = pr.Addr()
}
return hashes, nil
}
// NumParents gets the number of parents a commit has.
func (c *Commit) NumParents() int {
return len(c.parents)
}
func (c *Commit) Height() (uint64, error) {
return c.dCommit.Height(), nil
}
// GetRootValue gets the RootValue of the commit.
func (c *Commit) GetRootValue(ctx context.Context) (*RootValue, error) {
rootV, err := datas.GetCommittedValue(ctx, c.vrw, c.dCommit.NomsValue())
if err != nil {
return nil, err
}
if rootV == nil {
return nil, errHasNoRootValue
}
return newRootValue(c.vrw, c.ns, rootV)
}
func (c *Commit) GetParent(ctx context.Context, idx int) (*Commit, error) {
return NewCommit(ctx, c.vrw, c.ns, c.parents[idx])
}
func (c *Commit) GetCommitClosure(ctx context.Context) (prolly.CommitClosure, error) {
switch v := c.dCommit.NomsValue().(type) {
case types.SerialMessage:
return datas.NewParentsClosure(ctx, c.dCommit, v, c.vrw, c.ns)
default:
return prolly.CommitClosure{}, fmt.Errorf("old format lacks commit closure")
}
}
var ErrNoCommonAncestor = errors.New("no common ancestor")
func GetCommitAncestor(ctx context.Context, cm1, cm2 *Commit) (*Commit, error) {
addr, err := getCommitAncestorAddr(ctx, cm1.dCommit, cm2.dCommit, cm1.vrw, cm2.vrw, cm1.ns, cm2.ns)
if err != nil {
return nil, err
}
targetCommit, err := datas.LoadCommitAddr(ctx, cm1.vrw, addr)
if err != nil {
return nil, err
}
return NewCommit(ctx, cm1.vrw, cm1.ns, targetCommit)
}
func getCommitAncestorAddr(ctx context.Context, c1, c2 *datas.Commit, vrw1, vrw2 types.ValueReadWriter, ns1, ns2 tree.NodeStore) (hash.Hash, error) {
ancestorAddr, ok, err := datas.FindCommonAncestor(ctx, c1, c2, vrw1, vrw2, ns1, ns2)
if err != nil {
return hash.Hash{}, err
}
if !ok {
return hash.Hash{}, ErrNoCommonAncestor
}
return ancestorAddr, nil
}
func (c *Commit) CanFastForwardTo(ctx context.Context, new *Commit) (bool, error) {
ancestor, err := GetCommitAncestor(ctx, c, new)
if err != nil {
return false, err
} else if ancestor == nil {
return false, errors.New("cannot perform fast forward merge; commits have no common ancestor")
} else if ancestor.dCommit.Addr() == c.dCommit.Addr() {
if ancestor.dCommit.Addr() == new.dCommit.Addr() {
return true, ErrUpToDate
}
return true, nil
} else if ancestor.dCommit.Addr() == new.dCommit.Addr() {
return false, ErrIsAhead
}
return false, nil
}
func (c *Commit) CanFastReverseTo(ctx context.Context, new *Commit) (bool, error) {
ancestor, err := GetCommitAncestor(ctx, c, new)
if err != nil {
return false, err
} else if ancestor == nil {
return false, errors.New("cannot perform fast forward merge; commits have no common ancestor")
} else if ancestor.dCommit.Addr() == new.dCommit.Addr() {
if ancestor.dCommit.Addr() == c.dCommit.Addr() {
return true, ErrUpToDate
}
return true, nil
} else if ancestor.dCommit.Addr() == c.dCommit.Addr() {
return false, ErrIsBehind
}
return false, nil
}
func (c *Commit) GetAncestor(ctx context.Context, as *AncestorSpec) (*Commit, error) {
if as == nil || len(as.Instructions) == 0 {
return c, nil
}
cur := c
instructions := as.Instructions
for _, inst := range instructions {
if inst >= cur.NumParents() {
return nil, ErrInvalidAncestorSpec
}
var err error
cur, err = cur.GetParent(ctx, inst)
if err != nil {
return nil, err
}
if cur == nil {
return nil, ErrInvalidAncestorSpec
}
}
return cur, nil
}
// ResolveRootValue implements Rootish.
func (c *Commit) ResolveRootValue(ctx context.Context) (*RootValue, error) {
return c.GetRootValue(ctx)
}
// PendingCommit represents a commit that hasn't yet been written to storage. It contains a root value and options to
// use when committing it. Use a PendingCommit when it's important to update the working set and HEAD together
// atomically, via doltdb.CommitWithWorkingSet
type PendingCommit struct {
Roots Roots
Val types.Value
CommitOptions datas.CommitOptions
}
// NewPendingCommit returns a new PendingCommit object to be written with doltdb.CommitWithWorkingSet.
// |roots| are the current roots to include in the PendingCommit. roots.Staged is used as the new root to package in the
// commit, once written.
// |headRef| is the ref of the HEAD the commit will update
// |mergeParentCommits| are any merge parents for this commit
// |cm| is the metadata for the commit
// The current branch head will be automatically filled in as the first parent at commit time.
func (ddb *DoltDB) NewPendingCommit(
ctx context.Context,
roots Roots,
mergeParentCommits []*Commit,
cm *datas.CommitMeta,
) (*PendingCommit, error) {
newstaged, val, err := ddb.writeRootValue(ctx, roots.Staged)
if err != nil {
return nil, err
}
roots.Staged = newstaged
var parents []hash.Hash
for _, pc := range mergeParentCommits {
parents = append(parents, pc.dCommit.Addr())
}
commitOpts := datas.CommitOptions{Parents: parents, Meta: cm}
return &PendingCommit{
Roots: roots,
Val: val,
CommitOptions: commitOpts,
}, nil
}