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 }