github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/sqle/dfunctions/dolt_commit.go (about)

     1  // Copyright 2020 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 dfunctions
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"github.com/dolthub/go-mysql-server/sql"
    21  	"github.com/dolthub/vitess/go/vt/proto/query"
    22  
    23  	"github.com/dolthub/dolt/go/cmd/dolt/cli"
    24  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    25  	"github.com/dolthub/dolt/go/libraries/doltcore/env"
    26  	"github.com/dolthub/dolt/go/libraries/doltcore/env/actions"
    27  	"github.com/dolthub/dolt/go/libraries/doltcore/sqle"
    28  )
    29  
    30  const DoltCommitFuncName = "dolt_commit"
    31  
    32  var hashType = sql.MustCreateString(query.Type_TEXT, 32, sql.Collation_ascii_bin)
    33  
    34  type DoltCommitFunc struct {
    35  	children []sql.Expression
    36  }
    37  
    38  // NewDoltCommitFunc creates a new DoltCommitFunc expression whose children represents the args passed in DOLT_COMMIT.
    39  func NewDoltCommitFunc(ctx *sql.Context, args ...sql.Expression) (sql.Expression, error) {
    40  	return &DoltCommitFunc{children: args}, nil
    41  }
    42  
    43  // Runs DOLT_COMMIT in the sql engine which models the behavior of `dolt commit`. Commits staged staged changes to head.
    44  func (d DoltCommitFunc) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
    45  	// Get the information for the sql context.
    46  	dbName := ctx.GetCurrentDatabase()
    47  	dSess := sqle.DSessFromSess(ctx.Session)
    48  	dbData, ok := dSess.GetDbData(dbName)
    49  
    50  	if !ok {
    51  		return nil, fmt.Errorf("Could not load database %s", dbName)
    52  	}
    53  
    54  	ddb := dbData.Ddb
    55  	rsr := dbData.Rsr
    56  
    57  	ap := cli.CreateCommitArgParser()
    58  
    59  	// Get the args for DOLT_COMMIT.
    60  	args, err := getDoltArgs(ctx, row, d.Children())
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	apr, err := ap.Parse(args)
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  
    70  	allFlag := apr.Contains(cli.AllFlag)
    71  	allowEmpty := apr.Contains(cli.AllowEmptyFlag)
    72  
    73  	// Check if there are no changes in the staged set but the -a flag is false
    74  	hasStagedChanges, err := hasStagedSetChanges(ctx, ddb, rsr)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	if !allFlag && !hasStagedChanges && !allowEmpty {
    80  		return nil, fmt.Errorf("Cannot commit an empty commit. See the --allow-empty if you want to.")
    81  	}
    82  
    83  	// Check if there are no changes in the working set but the -a flag is true.
    84  	// The -a flag is fine when a merge is active or there are staged changes as result of a merge or an add.
    85  	if allFlag && !hasWorkingSetChanges(rsr) && !allowEmpty && !rsr.IsMergeActive() && !hasStagedChanges {
    86  		return nil, fmt.Errorf("Cannot commit an empty commit. See the --allow-empty if you want to.")
    87  	}
    88  
    89  	if allFlag {
    90  		err = actions.StageAllTables(ctx, dbData)
    91  	}
    92  
    93  	if err != nil {
    94  		return nil, fmt.Errorf(err.Error())
    95  	}
    96  
    97  	// Parse the author flag. Return an error if not.
    98  	var name, email string
    99  	if authorStr, ok := apr.GetValue(cli.AuthorParam); ok {
   100  		name, email, err = cli.ParseAuthor(authorStr)
   101  		if err != nil {
   102  			return nil, err
   103  		}
   104  	} else {
   105  		name = dSess.Username
   106  		email = dSess.Email
   107  	}
   108  
   109  	// Get the commit message.
   110  	msg, msgOk := apr.GetValue(cli.CommitMessageArg)
   111  	if !msgOk {
   112  		return nil, fmt.Errorf("Must provide commit message.")
   113  	}
   114  
   115  	// Specify the time if the date parameter is not.
   116  	t := ctx.QueryTime()
   117  	if commitTimeStr, ok := apr.GetValue(cli.DateParam); ok {
   118  		var err error
   119  		t, err = cli.ParseDate(commitTimeStr)
   120  
   121  		if err != nil {
   122  			return nil, fmt.Errorf(err.Error())
   123  		}
   124  	}
   125  
   126  	h, err := actions.CommitStaged(ctx, dbData, actions.CommitStagedProps{
   127  		Message:          msg,
   128  		Date:             t,
   129  		AllowEmpty:       apr.Contains(cli.AllowEmptyFlag),
   130  		CheckForeignKeys: !apr.Contains(cli.ForceFlag),
   131  		Name:             name,
   132  		Email:            email,
   133  	})
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  
   138  	if allFlag {
   139  		err = setHeadAndWorkingSessionRoot(ctx, h)
   140  	} else {
   141  		err = setSessionRootExplicit(ctx, h, sqle.HeadKeySuffix)
   142  	}
   143  
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  
   148  	return h, nil
   149  }
   150  
   151  func hasWorkingSetChanges(rsr env.RepoStateReader) bool {
   152  	return rsr.WorkingHash() != rsr.StagedHash()
   153  }
   154  
   155  // TODO: We should not be dealing with root objects here but commit specs.
   156  func hasStagedSetChanges(ctx *sql.Context, ddb *doltdb.DoltDB, rsr env.RepoStateReader) (bool, error) {
   157  	root, err := env.HeadRoot(ctx, ddb, rsr)
   158  
   159  	if err != nil {
   160  		return false, err
   161  	}
   162  
   163  	headHash, err := root.HashOf()
   164  
   165  	if err != nil {
   166  		return false, err
   167  	}
   168  
   169  	return rsr.StagedHash() != headHash, nil
   170  }
   171  
   172  func getDoltArgs(ctx *sql.Context, row sql.Row, children []sql.Expression) ([]string, error) {
   173  	args := make([]string, len(children))
   174  	for i := range children {
   175  		childVal, err := children[i].Eval(ctx, row)
   176  
   177  		if err != nil {
   178  			return nil, err
   179  		}
   180  
   181  		text, err := sql.Text.Convert(childVal)
   182  
   183  		if err != nil {
   184  			return nil, err
   185  		}
   186  
   187  		args[i] = text.(string)
   188  	}
   189  
   190  	return args, nil
   191  }
   192  
   193  func (d DoltCommitFunc) String() string {
   194  	childrenStrings := make([]string, len(d.children))
   195  
   196  	for _, child := range d.children {
   197  		childrenStrings = append(childrenStrings, child.String())
   198  	}
   199  	return fmt.Sprintf("commit_hash")
   200  }
   201  
   202  func (d DoltCommitFunc) Type() sql.Type {
   203  	return sql.Text
   204  }
   205  
   206  func (d DoltCommitFunc) IsNullable() bool {
   207  	return false
   208  }
   209  
   210  func (d DoltCommitFunc) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
   211  	return NewDoltCommitFunc(ctx, children...)
   212  }
   213  
   214  func (d DoltCommitFunc) Resolved() bool {
   215  	for _, child := range d.Children() {
   216  		if !child.Resolved() {
   217  			return false
   218  		}
   219  	}
   220  	return true
   221  }
   222  
   223  func (d DoltCommitFunc) Children() []sql.Expression {
   224  	return d.children
   225  }
   226  
   227  // setHeadAndWorkingSessionRoot takes in a ctx and the new head hashstring and updates the session head and working hashes.
   228  func setHeadAndWorkingSessionRoot(ctx *sql.Context, headHashStr string) error {
   229  	key := ctx.GetCurrentDatabase() + sqle.HeadKeySuffix
   230  	dsess := sqle.DSessFromSess(ctx.Session)
   231  
   232  	return dsess.SetSessionVariable(ctx, key, headHashStr)
   233  }
   234  
   235  // setSessionRootExplicit sets a session variable (either HEAD or WORKING) to a hash string. For HEAD, the hash string
   236  // should come from the commit string. For working the commit string needs to come from the root.
   237  func setSessionRootExplicit(ctx *sql.Context, hashString string, suffix string) error {
   238  	key := ctx.GetCurrentDatabase() + suffix
   239  	dsess := sqle.DSessFromSess(ctx.Session)
   240  
   241  	return dsess.SetSessionVarDirectly(ctx, key, hashString)
   242  }