github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/env/actions/reset.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 actions 16 17 import ( 18 "context" 19 "fmt" 20 "time" 21 22 "github.com/dolthub/dolt/go/store/datas" 23 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/ref" 27 "github.com/dolthub/dolt/go/libraries/doltcore/schema" 28 "github.com/dolthub/dolt/go/libraries/utils/argparser" 29 ) 30 31 // resetHardTables resolves a new HEAD commit from a refSpec and updates working set roots by 32 // resetting the table contexts for tracked tables. New tables are ignored. Returns new HEAD 33 // Commit and Roots. 34 func resetHardTables(ctx context.Context, dbData env.DbData, cSpecStr string, roots doltdb.Roots) (*doltdb.Commit, doltdb.Roots, error) { 35 ddb := dbData.Ddb 36 rsr := dbData.Rsr 37 38 var newHead *doltdb.Commit 39 if cSpecStr != "" { 40 cs, err := doltdb.NewCommitSpec(cSpecStr) 41 if err != nil { 42 return nil, doltdb.Roots{}, err 43 } 44 45 headRef, err := rsr.CWBHeadRef() 46 if err != nil { 47 return nil, doltdb.Roots{}, err 48 } 49 optCmt, err := ddb.Resolve(ctx, cs, headRef) 50 if err != nil { 51 return nil, doltdb.Roots{}, err 52 } 53 54 var ok bool 55 if newHead, ok = optCmt.ToCommit(); !ok { 56 return nil, doltdb.Roots{}, doltdb.ErrGhostCommitEncountered 57 } 58 59 roots.Head, err = newHead.GetRootValue(ctx) 60 if err != nil { 61 return nil, doltdb.Roots{}, err 62 } 63 } 64 65 // mirroring Git behavior, untracked tables are ignored on 'reset --hard', 66 // save the state of these tables and apply them to |newHead|'s root. 67 // 68 // as a special case, if an untracked table has a tag collision with any 69 // tables in |newHead| we silently drop it from the new working set. 70 // these tag collision is typically cause by table renames (bug #751). 71 72 untracked, err := doltdb.GetAllSchemas(ctx, roots.Working) 73 if err != nil { 74 return nil, doltdb.Roots{}, err 75 } 76 // untracked tables exist in |working| but not in |staged| 77 staged, err := roots.Staged.GetTableNames(ctx, doltdb.DefaultSchemaName) 78 if err != nil { 79 return nil, doltdb.Roots{}, err 80 } 81 for _, name := range staged { 82 delete(untracked, name) 83 } 84 85 newWkRoot := roots.Head 86 87 ws, err := doltdb.GetAllSchemas(ctx, newWkRoot) 88 if err != nil { 89 return nil, doltdb.Roots{}, err 90 } 91 tags := mapColumnTags(ws) 92 93 for name, sch := range untracked { 94 for _, pk := range sch.GetAllCols().GetColumns() { 95 if _, ok := tags[pk.Tag]; ok { 96 // |pk.Tag| collides with a schema in |newWkRoot| 97 delete(untracked, name) 98 } 99 } 100 } 101 102 for name := range untracked { 103 tbl, _, err := roots.Working.GetTable(ctx, doltdb.TableName{Name: name}) 104 if err != nil { 105 return nil, doltdb.Roots{}, err 106 } 107 newWkRoot, err = newWkRoot.PutTable(ctx, doltdb.TableName{Name: name}, tbl) 108 if err != nil { 109 return nil, doltdb.Roots{}, fmt.Errorf("failed to write table back to database: %s", err) 110 } 111 } 112 113 // need to save the state of files that aren't tracked 114 untrackedTables := make(map[string]*doltdb.Table) 115 wTblNames, err := roots.Working.GetTableNames(ctx, doltdb.DefaultSchemaName) 116 117 if err != nil { 118 return nil, doltdb.Roots{}, err 119 } 120 121 for _, tblName := range wTblNames { 122 untrackedTables[tblName], _, err = roots.Working.GetTable(ctx, doltdb.TableName{Name: tblName}) 123 124 if err != nil { 125 return nil, doltdb.Roots{}, err 126 } 127 } 128 129 headTblNames, err := roots.Staged.GetTableNames(ctx, doltdb.DefaultSchemaName) 130 131 if err != nil { 132 return nil, doltdb.Roots{}, err 133 } 134 135 for _, tblName := range headTblNames { 136 delete(untrackedTables, tblName) 137 } 138 139 roots.Working = newWkRoot 140 roots.Staged = roots.Head 141 142 return newHead, roots, nil 143 } 144 145 // ResetHardTables resets the tables in working, staged, and head based on the given parameters. Returns the new 146 // head commit and resulting roots 147 func ResetHardTables(ctx context.Context, dbData env.DbData, cSpecStr string, roots doltdb.Roots) (*doltdb.Commit, doltdb.Roots, error) { 148 return resetHardTables(ctx, dbData, cSpecStr, roots) 149 } 150 151 // ResetHard resets the working, staged, and head to the ones in the provided roots and head ref. 152 // The reset can be performed on a non-current branch and working set. 153 // Returns an error if the reset fails. 154 func ResetHard( 155 ctx context.Context, 156 dbData env.DbData, 157 doltDb *doltdb.DoltDB, 158 username, email string, 159 cSpecStr string, 160 roots doltdb.Roots, 161 headRef ref.DoltRef, 162 ws *doltdb.WorkingSet, 163 ) error { 164 165 newHead, roots, err := resetHardTables(ctx, dbData, cSpecStr, roots) 166 if err != nil { 167 return err 168 } 169 170 currentWs, err := doltDb.ResolveWorkingSet(ctx, ws.Ref()) 171 if err != nil { 172 return err 173 } 174 175 h, err := currentWs.HashOf() 176 if err != nil { 177 return err 178 } 179 180 // TODO - refactor this to ensure the update to the head and working set are transactional. 181 err = doltDb.UpdateWorkingSet(ctx, ws.Ref(), ws.WithWorkingRoot(roots.Working).WithStagedRoot(roots.Staged).ClearMerge().ClearRebase(), h, &datas.WorkingSetMeta{ 182 Name: username, 183 Email: email, 184 Timestamp: uint64(time.Now().Unix()), 185 Description: "reset hard", 186 }, nil) 187 if err != nil { 188 return err 189 } 190 191 if newHead != nil { 192 err = doltDb.SetHeadToCommit(ctx, headRef, newHead) 193 if err != nil { 194 return err 195 } 196 } 197 198 return nil 199 } 200 201 func ResetSoftTables(ctx context.Context, dbData env.DbData, apr *argparser.ArgParseResults, roots doltdb.Roots) (doltdb.Roots, error) { 202 tables, err := getUnionedTables(ctx, apr.Args, roots.Staged, roots.Head) 203 if err != nil { 204 return doltdb.Roots{}, err 205 } 206 207 err = ValidateTables(context.TODO(), tables, roots.Staged, roots.Head) 208 if err != nil { 209 return doltdb.Roots{}, err 210 } 211 212 roots.Staged, err = MoveTablesBetweenRoots(ctx, tables, roots.Head, roots.Staged) 213 if err != nil { 214 return doltdb.Roots{}, err 215 } 216 217 return roots, nil 218 } 219 220 // ResetSoft resets the staged value from HEAD for the tables given and returns the updated roots. 221 func ResetSoft(ctx context.Context, dbData env.DbData, tables []string, roots doltdb.Roots) (doltdb.Roots, error) { 222 tables, err := getUnionedTables(ctx, tables, roots.Staged, roots.Head) 223 if err != nil { 224 return doltdb.Roots{}, err 225 } 226 227 err = ValidateTables(context.TODO(), tables, roots.Staged, roots.Head) 228 if err != nil { 229 return doltdb.Roots{}, err 230 } 231 return resetStaged(ctx, roots, tables) 232 } 233 234 // ResetSoftToRef matches the `git reset --soft <REF>` pattern. It returns a new Roots with the Staged and Head values 235 // set to the commit specified by the spec string. The Working root is not set 236 func ResetSoftToRef(ctx context.Context, dbData env.DbData, cSpecStr string) (doltdb.Roots, error) { 237 cs, err := doltdb.NewCommitSpec(cSpecStr) 238 if err != nil { 239 return doltdb.Roots{}, err 240 } 241 242 headRef, err := dbData.Rsr.CWBHeadRef() 243 if err != nil { 244 return doltdb.Roots{}, err 245 } 246 optCmt, err := dbData.Ddb.Resolve(ctx, cs, headRef) 247 if err != nil { 248 return doltdb.Roots{}, err 249 } 250 newHead, ok := optCmt.ToCommit() 251 if !ok { 252 return doltdb.Roots{}, doltdb.ErrGhostCommitEncountered 253 } 254 255 foundRoot, err := newHead.GetRootValue(ctx) 256 if err != nil { 257 return doltdb.Roots{}, err 258 } 259 260 // Update the head to this commit 261 if err = dbData.Ddb.SetHeadToCommit(ctx, headRef, newHead); err != nil { 262 return doltdb.Roots{}, err 263 } 264 265 return doltdb.Roots{ 266 Head: foundRoot, 267 Staged: foundRoot, 268 }, err 269 } 270 271 func getUnionedTables(ctx context.Context, tables []string, stagedRoot, headRoot doltdb.RootValue) ([]string, error) { 272 if len(tables) == 0 || (len(tables) == 1 && tables[0] == ".") { 273 var err error 274 tables, err = doltdb.UnionTableNames(ctx, stagedRoot, headRoot) 275 276 if err != nil { 277 return nil, err 278 } 279 } 280 281 return tables, nil 282 } 283 284 func resetStaged(ctx context.Context, roots doltdb.Roots, tbls []string) (doltdb.Roots, error) { 285 newStaged, err := MoveTablesBetweenRoots(ctx, tbls, roots.Head, roots.Staged) 286 if err != nil { 287 return doltdb.Roots{}, err 288 } 289 290 roots.Staged = newStaged 291 return roots, nil 292 } 293 294 // IsValidRef validates whether the input parameter is a valid cString 295 // TODO: this doesn't belong in this package 296 func IsValidRef(ctx context.Context, cSpecStr string, ddb *doltdb.DoltDB, rsr env.RepoStateReader) (bool, error) { 297 // The error return value is only for propagating unhandled errors from rsr.CWBHeadRef() 298 // All other errors merely indicate an invalid ref spec. 299 // TODO: It's much better to enumerate the expected errors, to make sure we don't suppress any unexpected ones. 300 cs, err := doltdb.NewCommitSpec(cSpecStr) 301 if err != nil { 302 return false, nil 303 } 304 305 headRef, err := rsr.CWBHeadRef() 306 if err == doltdb.ErrOperationNotSupportedInDetachedHead { 307 // This is safe because ddb.Resolve checks if headRef is nil, but only when the value is actually needed. 308 // Basically, this guarantees that resolving "HEAD" or similar will return an error but other resolves will work. 309 headRef = nil 310 } else if err != nil { 311 return false, err 312 } 313 314 _, err = ddb.Resolve(ctx, cs, headRef) 315 if err != nil { 316 return false, nil 317 } 318 319 return true, nil 320 } 321 322 // CleanUntracked deletes untracked tables from the working root. 323 // Evaluates untracked tables as: all working tables - all staged tables. 324 func CleanUntracked(ctx context.Context, roots doltdb.Roots, tables []string, dryrun bool, force bool) (doltdb.Roots, error) { 325 untrackedTables := make(map[string]struct{}) 326 327 var err error 328 if len(tables) == 0 { 329 tables, err = roots.Working.GetTableNames(ctx, doltdb.DefaultSchemaName) 330 if err != nil { 331 return doltdb.Roots{}, nil 332 } 333 } 334 335 for i := range tables { 336 name := tables[i] 337 _, _, err = roots.Working.GetTable(ctx, doltdb.TableName{Name: name}) 338 if err != nil { 339 return doltdb.Roots{}, err 340 } 341 untrackedTables[name] = struct{}{} 342 } 343 344 // untracked tables = working tables - staged tables 345 headTblNames, err := roots.Staged.GetTableNames(ctx, doltdb.DefaultSchemaName) 346 if err != nil { 347 return doltdb.Roots{}, err 348 } 349 350 for _, name := range headTblNames { 351 delete(untrackedTables, name) 352 } 353 354 newRoot := roots.Working 355 var toDelete []string 356 for t := range untrackedTables { 357 toDelete = append(toDelete, t) 358 } 359 360 newRoot, err = newRoot.RemoveTables(ctx, force, force, toDelete...) 361 if err != nil { 362 return doltdb.Roots{}, fmt.Errorf("failed to remove tables; %w", err) 363 } 364 365 if dryrun { 366 return roots, nil 367 } 368 roots.Working = newRoot 369 370 return roots, nil 371 } 372 373 // mapColumnTags takes a map from table name to schema.Schema and generates 374 // a map from column tags to table names (see RootValue.GetAllSchemas). 375 func mapColumnTags(tables map[string]schema.Schema) (m map[uint64]string) { 376 m = make(map[uint64]string, len(tables)) 377 for tbl, sch := range tables { 378 for _, tag := range sch.GetAllCols().Tags { 379 m[tag] = tbl 380 } 381 } 382 return 383 }