mirror of
https://github.com/dolthub/dolt.git
synced 2026-03-13 01:54:38 -05:00
270 lines
7.9 KiB
Go
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
|
|
}
|