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 }