mirror of
https://github.com/dolthub/dolt.git
synced 2026-02-12 10:32:27 -06:00
tags as separate struct type
This commit is contained in:
@@ -74,20 +74,6 @@ func NewCommitMetaWithUserTS(name, email, desc string, userTS time.Time) (*Commi
|
||||
return &CommitMeta{n, e, ms, d, userMS}, nil
|
||||
}
|
||||
|
||||
// NewTagMeta returns CommitMeta that can be used to create a tag.
|
||||
func NewTagMeta(name, email, desc string) *CommitMeta {
|
||||
n := strings.TrimSpace(name)
|
||||
e := strings.TrimSpace(email)
|
||||
d := strings.TrimSpace(desc)
|
||||
|
||||
ns := uint64(CommitNowFunc().UnixNano())
|
||||
ms := ns / uMilliToNano
|
||||
|
||||
userMS := int64(ns) / milliToNano
|
||||
|
||||
return &CommitMeta{n, e, ms, d, userMS}
|
||||
}
|
||||
|
||||
func getRequiredFromSt(st types.Struct, k string) (types.Value, error) {
|
||||
if v, ok, err := st.MaybeGet(k); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -194,11 +194,35 @@ func getCommitStForRefStr(ctx context.Context, db datas.Database, ref string) (t
|
||||
}
|
||||
|
||||
dsHead, hasHead := ds.MaybeHead()
|
||||
if hasHead {
|
||||
|
||||
if !hasHead {
|
||||
return types.EmptyStruct(db.Format()), ErrBranchNotFound
|
||||
}
|
||||
|
||||
if dsHead.Name() == datas.CommitName {
|
||||
return dsHead, nil
|
||||
}
|
||||
|
||||
return types.EmptyStruct(db.Format()), ErrBranchNotFound
|
||||
if dsHead.Name() == datas.TagName {
|
||||
cmRef, ok, err := dsHead.MaybeGet(datas.TagCommitRefField)
|
||||
if err != nil {
|
||||
return types.EmptyStruct(db.Format()), err
|
||||
}
|
||||
if !ok {
|
||||
err = fmt.Errorf("tag struct does not have field %s", datas.TagCommitRefField)
|
||||
return types.EmptyStruct(db.Format()), err
|
||||
}
|
||||
|
||||
commitSt, err := cmRef.(types.Ref).TargetValue(ctx, db)
|
||||
if err != nil {
|
||||
return types.EmptyStruct(db.Format()), err
|
||||
}
|
||||
|
||||
return commitSt.(types.Struct), nil
|
||||
}
|
||||
|
||||
err = fmt.Errorf("dataset head is neither commit nor tag")
|
||||
return types.EmptyStruct(db.Format()), err
|
||||
}
|
||||
|
||||
func getCommitStForHash(ctx context.Context, db datas.Database, c string) (types.Struct, error) {
|
||||
@@ -768,10 +792,6 @@ func (ddb *DoltDB) NewBranchAtCommit(ctx context.Context, dref ref.DoltRef, comm
|
||||
panic(fmt.Sprintf("invalid branch name %s, use IsValidUserBranchName check", dref.String()))
|
||||
}
|
||||
|
||||
return ddb.newRefAtCommit(ctx, dref, commit)
|
||||
}
|
||||
|
||||
func (ddb *DoltDB) newRefAtCommit(ctx context.Context, dref ref.DoltRef, commit *Commit) error {
|
||||
ds, err := ddb.db.GetDataset(ctx, dref.String())
|
||||
|
||||
if err != nil {
|
||||
@@ -810,12 +830,43 @@ func (ddb *DoltDB) deleteRef(ctx context.Context, dref ref.DoltRef) error {
|
||||
}
|
||||
|
||||
// NewTagAtCommit create a new tag at the commit given.
|
||||
func (ddb *DoltDB) NewTagAtCommit(ctx context.Context, tagRef ref.DoltRef, commit *Commit) error {
|
||||
func (ddb *DoltDB) NewTagAtCommit(ctx context.Context, tagRef ref.DoltRef, c *Commit, meta *CommitMeta) error {
|
||||
if !IsValidTagRef(tagRef) {
|
||||
panic(fmt.Sprintf("invalid tag name %s, use IsValidUserTagName check", tagRef.String()))
|
||||
}
|
||||
|
||||
return ddb.newRefAtCommit(ctx, tagRef, commit)
|
||||
ds, err := ddb.db.GetDataset(ctx, tagRef.String())
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, hasHead, err := ds.MaybeHeadRef()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hasHead {
|
||||
return fmt.Errorf("dataset already exists for tag %s", tagRef.String())
|
||||
}
|
||||
|
||||
r, err := types.NewRef(c.commitSt, ddb.Format())
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
st, err := meta.toNomsStruct(ddb.db.Format())
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tag := datas.TagOptions{Meta: st}
|
||||
|
||||
ds, err = ddb.db.Tag(ctx, ds, r, tag)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (ddb *DoltDB) DeleteTag(ctx context.Context, tag ref.DoltRef) error {
|
||||
|
||||
19
go/libraries/doltcore/doltdb/tag.go
Normal file
19
go/libraries/doltcore/doltdb/tag.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package doltdb
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NewTagMeta returns CommitMeta that can be used to create a tag.
|
||||
func NewTagMeta(name, email, desc string) *CommitMeta {
|
||||
n := strings.TrimSpace(name)
|
||||
e := strings.TrimSpace(email)
|
||||
d := strings.TrimSpace(desc)
|
||||
|
||||
ns := uint64(CommitNowFunc().UnixNano())
|
||||
ms := ns / uMilliToNano
|
||||
|
||||
userMS := int64(ns) / milliToNano
|
||||
|
||||
return &CommitMeta{n, e, ms, d, userMS}
|
||||
}
|
||||
21
go/libraries/doltcore/env/actions/tag.go
vendored
21
go/libraries/doltcore/env/actions/tag.go
vendored
@@ -58,28 +58,9 @@ func CreateTag(ctx context.Context, dEnv *env.DoltEnv, tagName, startPoint strin
|
||||
return err
|
||||
}
|
||||
|
||||
err = dEnv.DoltDB.NewTagAtCommit(ctx, tagRef, cm)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
root, err := cm.GetRootValue()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h, err := root.HashOf()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
meta := doltdb.NewTagMeta(props.TaggerName, props.TaggerEmail, props.Description)
|
||||
|
||||
_, err = dEnv.DoltDB.CommitWithParentCommits(ctx, h, tagRef, nil, meta)
|
||||
return err
|
||||
return dEnv.DoltDB.NewTagAtCommit(ctx, tagRef, cm, meta)
|
||||
}
|
||||
|
||||
func DeleteTags(ctx context.Context, dEnv *env.DoltEnv, tagNames ...string) error {
|
||||
|
||||
@@ -41,15 +41,15 @@ const (
|
||||
ParentsListField = "parents_list"
|
||||
ValueField = "value"
|
||||
MetaField = "meta"
|
||||
commitName = "Commit"
|
||||
CommitName = "Commit"
|
||||
)
|
||||
|
||||
var commitTemplate = types.MakeStructTemplate(commitName, []string{MetaField, ParentsField, ParentsListField, ValueField})
|
||||
var commitTemplate = types.MakeStructTemplate(CommitName, []string{MetaField, ParentsField, ParentsListField, ValueField})
|
||||
|
||||
var valueCommitType = nomdl.MustParseType(`Struct Commit {
|
||||
meta: Struct {},
|
||||
parents: Set<Ref<Cycle<Commit>>>,
|
||||
parentsList?: List<Ref<Cycle<Commit>>>,
|
||||
parents_list?: List<Ref<Cycle<Commit>>>,
|
||||
value: Value,
|
||||
}`)
|
||||
|
||||
|
||||
@@ -95,6 +95,8 @@ type Database interface {
|
||||
// of a conflict, Commit returns an 'ErrMergeNeeded' error.
|
||||
CommitValue(ctx context.Context, ds Dataset, v types.Value) (Dataset, error)
|
||||
|
||||
Tag(ctx context.Context, ds Dataset, ref types.Ref, opts TagOptions) (Dataset, error)
|
||||
|
||||
// Delete removes the Dataset named ds.ID() from the map at the root of
|
||||
// the Database. The Dataset data is not necessarily cleaned up at this
|
||||
// time, but may be garbage collected in the future.
|
||||
|
||||
@@ -417,6 +417,78 @@ func (db *database) doCommit(ctx context.Context, datasetID string, commit types
|
||||
return tryCommitErr
|
||||
}
|
||||
|
||||
func (db *database) Tag(ctx context.Context, ds Dataset, ref types.Ref, opts TagOptions) (Dataset, error) {
|
||||
return db.doHeadUpdate(
|
||||
ctx,
|
||||
ds,
|
||||
func(ds Dataset) error {
|
||||
st, err := NewTag(ctx, ref, opts.Meta)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return db.doTag(ctx, ds.ID(), st)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// doTag manages concurrent access the single logical piece of mutable state: the current Root. It uses
|
||||
// the same optimistic writing algorithm as doCommit (see above).
|
||||
func (db *database) doTag(ctx context.Context, datasetID string, tag types.Struct) error {
|
||||
if is, err := db.validateTag(ctx, tag); err != nil {
|
||||
return err
|
||||
} else if !is {
|
||||
d.Panic("Can't tag using a non-Commit struct (dataset %s)", datasetID)
|
||||
}
|
||||
|
||||
// This could loop forever, given enough simultaneous writers. BUG 2565
|
||||
var tryCommitErr error
|
||||
for tryCommitErr = ErrOptimisticLockFailed; tryCommitErr == ErrOptimisticLockFailed; {
|
||||
currentRootHash, err := db.rt.Root(ctx)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentDatasets, err := db.Datasets(ctx)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tagRef, err := db.WriteValue(ctx, tag) // will be orphaned if the tryCommitChunks() below fails
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, hasHead, err := currentDatasets.MaybeGet(ctx, types.String(datasetID))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hasHead {
|
||||
d.Panic("datasets for tags (refs/tags/*) cannot be altered after creation")
|
||||
}
|
||||
|
||||
ref, err := types.ToRefOfValue(tagRef, db.Format())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentDatasets, err = currentDatasets.Edit().Set(types.String(datasetID), ref).Map(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tryCommitErr = db.tryCommitChunks(ctx, currentDatasets, currentRootHash)
|
||||
}
|
||||
|
||||
return tryCommitErr
|
||||
}
|
||||
|
||||
func (db *database) Delete(ctx context.Context, ds Dataset) (Dataset, error) {
|
||||
return db.doHeadUpdate(ctx, ds, func(ds Dataset) error { return db.doDelete(ctx, ds.ID()) })
|
||||
}
|
||||
@@ -519,6 +591,32 @@ func (db *database) validateRefAsCommit(ctx context.Context, r types.Ref) (types
|
||||
return v.(types.Struct), nil
|
||||
}
|
||||
|
||||
func (db *database) validateTag(ctx context.Context, t types.Struct) (bool, error) {
|
||||
is, err := IsTag(t)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !is {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
r, ok, err := t.MaybeGet(TagCommitRefField)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !ok {
|
||||
return false, fmt.Errorf("tag is missing field %s", TagCommitRefField)
|
||||
}
|
||||
|
||||
_, err = db.validateRefAsCommit(ctx, r.(types.Ref))
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func buildNewCommit(ctx context.Context, ds Dataset, v types.Value, opts CommitOptions) (types.Struct, error) {
|
||||
parents := opts.ParentsList
|
||||
if parents == types.EmptyList || parents.Len() == 0 {
|
||||
|
||||
@@ -44,10 +44,19 @@ type Dataset struct {
|
||||
}
|
||||
|
||||
func newDataset(db Database, id string, head types.Value) (Dataset, error) {
|
||||
headNilOrIsCommit := head == nil
|
||||
if !headNilOrIsCommit {
|
||||
var err error
|
||||
headNilOrIsCommit, err = IsCommit(head)
|
||||
check := head == nil
|
||||
|
||||
var err error
|
||||
if !check {
|
||||
check, err = IsCommit(head)
|
||||
|
||||
if err != nil {
|
||||
return Dataset{}, err
|
||||
}
|
||||
}
|
||||
|
||||
if !check {
|
||||
check, err = IsTag(head)
|
||||
|
||||
if err != nil {
|
||||
return Dataset{}, err
|
||||
@@ -55,7 +64,7 @@ func newDataset(db Database, id string, head types.Value) (Dataset, error) {
|
||||
}
|
||||
|
||||
// precondition checks
|
||||
d.PanicIfFalse(headNilOrIsCommit)
|
||||
d.PanicIfFalse(check)
|
||||
return Dataset{db, id, head}, nil
|
||||
}
|
||||
|
||||
|
||||
67
go/store/datas/tag.go
Normal file
67
go/store/datas/tag.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2020 Liquidata, 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 datas
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/liquidata-inc/dolt/go/store/nomdl"
|
||||
"github.com/liquidata-inc/dolt/go/store/types"
|
||||
)
|
||||
|
||||
const (
|
||||
TagMetaField = "meta"
|
||||
TagCommitRefField = "ref"
|
||||
TagName = "Tag"
|
||||
)
|
||||
|
||||
var tagTemplate = types.MakeStructTemplate(TagName, []string{TagMetaField, TagCommitRefField})
|
||||
|
||||
// ref is a Ref<Commit>, but 'Commit' is not defined in this snippet.
|
||||
// Tag refs are validated to point at Commits during write.
|
||||
var valueTagType = nomdl.MustParseType(`Struct Tag {
|
||||
meta: Struct {},
|
||||
ref: Ref<Value>,
|
||||
}`)
|
||||
|
||||
// TagOptions is used to pass options into Tag.
|
||||
type TagOptions struct {
|
||||
// Meta is a Struct that describes arbitrary metadata about this Tag,
|
||||
// e.g. a timestamp or descriptive text.
|
||||
Meta types.Struct
|
||||
}
|
||||
|
||||
// NewTag creates a new tag object.
|
||||
//
|
||||
// A tag has the following type:
|
||||
//
|
||||
// ```
|
||||
// struct Tag {
|
||||
// meta: M,
|
||||
// commitRef: T,
|
||||
// }
|
||||
// ```
|
||||
// where M is a struct type and R is a ref type.
|
||||
func NewTag(_ context.Context, commitRef types.Ref, meta types.Struct) (types.Struct, error) {
|
||||
return tagTemplate.NewStruct(meta.Format(), []types.Value{meta, commitRef})
|
||||
}
|
||||
|
||||
func IsTag(v types.Value) (bool, error) {
|
||||
if s, ok := v.(types.Struct); !ok {
|
||||
return false, nil
|
||||
} else {
|
||||
return types.IsValueSubtypeOf(s.Format(), v, valueTagType)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user