github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/env/actions/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 actions
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"sort"
    21  	"time"
    22  
    23  	"github.com/dolthub/dolt/go/libraries/doltcore/fkconstrain"
    24  
    25  	"github.com/dolthub/dolt/go/libraries/doltcore/diff"
    26  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    27  	"github.com/dolthub/dolt/go/libraries/doltcore/env"
    28  	"github.com/dolthub/dolt/go/libraries/utils/config"
    29  	"github.com/dolthub/dolt/go/store/hash"
    30  )
    31  
    32  type CommitStagedProps struct {
    33  	Message          string
    34  	Date             time.Time
    35  	AllowEmpty       bool
    36  	CheckForeignKeys bool
    37  	Name             string
    38  	Email            string
    39  }
    40  
    41  // GetNameAndEmail returns the name and email from the supplied config
    42  func GetNameAndEmail(cfg config.ReadableConfig) (string, string, error) {
    43  	name, err := cfg.GetString(env.UserNameKey)
    44  
    45  	if err == config.ErrConfigParamNotFound {
    46  		return "", "", doltdb.ErrNameNotConfigured
    47  	} else if err != nil {
    48  		return "", "", err
    49  	}
    50  
    51  	email, err := cfg.GetString(env.UserEmailKey)
    52  
    53  	if err == config.ErrConfigParamNotFound {
    54  		return "", "", doltdb.ErrEmailNotConfigured
    55  	} else if err != nil {
    56  		return "", "", err
    57  	}
    58  
    59  	return name, email, nil
    60  }
    61  
    62  // CommitStaged adds a new commit to HEAD with the given props. Returns the new commit's hash as a string and an error.
    63  func CommitStaged(ctx context.Context, dbData env.DbData, props CommitStagedProps) (string, error) {
    64  	ddb := dbData.Ddb
    65  	rsr := dbData.Rsr
    66  	rsw := dbData.Rsw
    67  	drw := dbData.Drw
    68  
    69  	if props.Message == "" {
    70  		return "", doltdb.ErrEmptyCommitMessage
    71  	}
    72  
    73  	staged, notStaged, err := diff.GetStagedUnstagedTableDeltas(ctx, ddb, rsr)
    74  	if err != nil {
    75  		return "", err
    76  	}
    77  
    78  	var stagedTblNames []string
    79  	for _, td := range staged {
    80  		n := td.ToName
    81  		if td.IsDrop() {
    82  			n = td.FromName
    83  		}
    84  		stagedTblNames = append(stagedTblNames, n)
    85  	}
    86  
    87  	if len(staged) == 0 && !rsr.IsMergeActive() && !props.AllowEmpty {
    88  		_, notStagedDocs, err := diff.GetDocDiffs(ctx, ddb, rsr, drw)
    89  		if err != nil {
    90  			return "", err
    91  		}
    92  		return "", NothingStaged{notStaged, notStagedDocs}
    93  	}
    94  
    95  	var mergeCmSpec []*doltdb.CommitSpec
    96  	if rsr.IsMergeActive() {
    97  		root, err := env.WorkingRoot(ctx, ddb, rsr)
    98  		if err != nil {
    99  			return "", err
   100  		}
   101  		inConflict, err := root.TablesInConflict(ctx)
   102  		if err != nil {
   103  			return "", err
   104  		}
   105  		if len(inConflict) > 0 {
   106  			return "", NewTblInConflictError(inConflict)
   107  		}
   108  
   109  		spec, err := doltdb.NewCommitSpec(rsr.GetMergeCommit())
   110  
   111  		if err != nil {
   112  			panic("Corrupted repostate. Active merge state is not valid.")
   113  		}
   114  
   115  		mergeCmSpec = []*doltdb.CommitSpec{spec}
   116  	}
   117  
   118  	srt, err := env.StagedRoot(ctx, ddb, rsr)
   119  
   120  	if err != nil {
   121  		return "", err
   122  	}
   123  
   124  	hrt, err := env.HeadRoot(ctx, ddb, rsr)
   125  
   126  	if err != nil {
   127  		return "", err
   128  	}
   129  
   130  	srt, err = srt.UpdateSuperSchemasFromOther(ctx, stagedTblNames, srt)
   131  
   132  	if err != nil {
   133  		return "", err
   134  	}
   135  
   136  	if props.CheckForeignKeys {
   137  		srt, err = srt.ValidateForeignKeysOnSchemas(ctx)
   138  
   139  		if err != nil {
   140  			return "", err
   141  		}
   142  
   143  		err = fkconstrain.Validate(ctx, hrt, srt)
   144  
   145  		if err != nil {
   146  			return "", err
   147  		}
   148  	}
   149  
   150  	h, err := env.UpdateStagedRoot(ctx, ddb, rsw, srt)
   151  
   152  	if err != nil {
   153  		return "", err
   154  	}
   155  
   156  	wrt, err := env.WorkingRoot(ctx, ddb, rsr)
   157  
   158  	if err != nil {
   159  		return "", err
   160  	}
   161  
   162  	wrt, err = wrt.UpdateSuperSchemasFromOther(ctx, stagedTblNames, srt)
   163  
   164  	if err != nil {
   165  		return "", err
   166  	}
   167  
   168  	_, err = env.UpdateWorkingRoot(ctx, ddb, rsw, wrt)
   169  
   170  	if err != nil {
   171  		return "", err
   172  	}
   173  
   174  	meta, err := doltdb.NewCommitMetaWithUserTS(props.Name, props.Email, props.Message, props.Date)
   175  	if err != nil {
   176  		return "", err
   177  	}
   178  
   179  	// DoltDB resolves the current working branch head ref to provide a parent commit.
   180  	// Any commit specs in mergeCmSpec are also resolved and added.
   181  	c, err := ddb.CommitWithParentSpecs(ctx, h, rsr.CWBHeadRef(), mergeCmSpec, meta)
   182  
   183  	if err != nil {
   184  		return "", err
   185  	}
   186  
   187  	err = rsw.ClearMerge()
   188  
   189  	if err != nil {
   190  		return "", err
   191  	}
   192  
   193  	h, err = c.HashOf()
   194  
   195  	if err != nil {
   196  		return "", err
   197  	}
   198  
   199  	return h.String(), nil
   200  }
   201  
   202  func ValidateForeignKeysOnCommit(ctx context.Context, srt *doltdb.RootValue, stagedTblNames []string) (*doltdb.RootValue, error) {
   203  	// Validate schemas
   204  	srt, err := srt.ValidateForeignKeysOnSchemas(ctx)
   205  	if err != nil {
   206  		return nil, err
   207  	}
   208  	// Validate data
   209  	//TODO: make this more efficient, perhaps by leveraging diffs?
   210  	fkColl, err := srt.GetForeignKeyCollection(ctx)
   211  	if err != nil {
   212  		return nil, err
   213  	}
   214  
   215  	fksToCheck := make(map[string]doltdb.ForeignKey)
   216  	for _, tblName := range stagedTblNames {
   217  		declaredFk, referencedByFk := fkColl.KeysForTable(tblName)
   218  		for _, fk := range declaredFk {
   219  			fksToCheck[fk.Name] = fk
   220  		}
   221  		for _, fk := range referencedByFk {
   222  			fksToCheck[fk.Name] = fk
   223  		}
   224  	}
   225  
   226  	for _, fk := range fksToCheck {
   227  		childTbl, _, ok, err := srt.GetTableInsensitive(ctx, fk.TableName)
   228  		if err != nil {
   229  			return nil, err
   230  		}
   231  		if !ok {
   232  			return nil, fmt.Errorf("foreign key '%s' references missing table '%s'", fk.Name, fk.TableName)
   233  		}
   234  		childSch, err := childTbl.GetSchema(ctx)
   235  		if err != nil {
   236  			return nil, err
   237  		}
   238  		childIdx := childSch.Indexes().GetByName(fk.TableIndex)
   239  		childIdxRowData, err := childTbl.GetIndexRowData(ctx, fk.TableIndex)
   240  		if err != nil {
   241  			return nil, err
   242  		}
   243  		parentTbl, _, ok, err := srt.GetTableInsensitive(ctx, fk.ReferencedTableName)
   244  		if err != nil {
   245  			return nil, err
   246  		}
   247  		if !ok {
   248  			return nil, fmt.Errorf("foreign key '%s' references missing table '%s'", fk.Name, fk.ReferencedTableName)
   249  		}
   250  		parentTblSch, err := parentTbl.GetSchema(ctx)
   251  		if err != nil {
   252  			return nil, err
   253  		}
   254  		parentIdx := parentTblSch.Indexes().GetByName(fk.ReferencedTableIndex)
   255  		parentIdxRowData, err := parentTbl.GetIndexRowData(ctx, fk.ReferencedTableIndex)
   256  		if err != nil {
   257  			return nil, err
   258  		}
   259  		err = fk.ValidateData(ctx, childIdxRowData, parentIdxRowData, childIdx, parentIdx)
   260  		if err != nil {
   261  			return nil, err
   262  		}
   263  	}
   264  	return srt, nil
   265  }
   266  
   267  // TimeSortedCommits returns a reverse-chronological (latest-first) list of the most recent `n` ancestors of `commit`.
   268  // Passing a negative value for `n` will result in all ancestors being returned.
   269  func TimeSortedCommits(ctx context.Context, ddb *doltdb.DoltDB, commit *doltdb.Commit, n int) ([]*doltdb.Commit, error) {
   270  	hashToCommit := make(map[hash.Hash]*doltdb.Commit)
   271  	err := AddCommits(ctx, ddb, commit, hashToCommit, n)
   272  
   273  	if err != nil {
   274  		return nil, err
   275  	}
   276  
   277  	idx := 0
   278  	uniqueCommits := make([]*doltdb.Commit, len(hashToCommit))
   279  	for _, v := range hashToCommit {
   280  		uniqueCommits[idx] = v
   281  		idx++
   282  	}
   283  
   284  	var sortErr error
   285  	var metaI, metaJ *doltdb.CommitMeta
   286  	sort.Slice(uniqueCommits, func(i, j int) bool {
   287  		if sortErr != nil {
   288  			return false
   289  		}
   290  
   291  		metaI, sortErr = uniqueCommits[i].GetCommitMeta()
   292  
   293  		if sortErr != nil {
   294  			return false
   295  		}
   296  
   297  		metaJ, sortErr = uniqueCommits[j].GetCommitMeta()
   298  
   299  		if sortErr != nil {
   300  			return false
   301  		}
   302  
   303  		return metaI.UserTimestamp > metaJ.UserTimestamp
   304  	})
   305  
   306  	if sortErr != nil {
   307  		return nil, sortErr
   308  	}
   309  
   310  	return uniqueCommits, nil
   311  }
   312  
   313  func AddCommits(ctx context.Context, ddb *doltdb.DoltDB, commit *doltdb.Commit, hashToCommit map[hash.Hash]*doltdb.Commit, n int) error {
   314  	hash, err := commit.HashOf()
   315  
   316  	if err != nil {
   317  		return err
   318  	}
   319  
   320  	if _, ok := hashToCommit[hash]; ok {
   321  		return nil
   322  	}
   323  
   324  	hashToCommit[hash] = commit
   325  
   326  	numParents, err := commit.NumParents()
   327  
   328  	if err != nil {
   329  		return err
   330  	}
   331  
   332  	for i := 0; i < numParents && len(hashToCommit) != n; i++ {
   333  		parentCommit, err := ddb.ResolveParent(ctx, commit, i)
   334  
   335  		if err != nil {
   336  			return err
   337  		}
   338  
   339  		err = AddCommits(ctx, ddb, parentCommit, hashToCommit, n)
   340  
   341  		if err != nil {
   342  			return err
   343  		}
   344  	}
   345  
   346  	return nil
   347  }