github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/env/actions/branch.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 "errors" 20 "fmt" 21 22 "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" 23 "github.com/dolthub/dolt/go/libraries/doltcore/env" 24 "github.com/dolthub/dolt/go/libraries/doltcore/ref" 25 "github.com/dolthub/dolt/go/libraries/utils/set" 26 "github.com/dolthub/dolt/go/store/hash" 27 ) 28 29 var ErrAlreadyExists = errors.New("already exists") 30 var ErrCOBranchDelete = errors.New("attempted to delete checked out branch") 31 var ErrUnmergedBranchDelete = errors.New("attempted to delete a branch that is not fully merged into master; use `-f` to force") 32 33 func MoveBranch(ctx context.Context, dEnv *env.DoltEnv, oldBranch, newBranch string, force bool) error { 34 oldRef := ref.NewBranchRef(oldBranch) 35 newRef := ref.NewBranchRef(newBranch) 36 37 err := CopyBranch(ctx, dEnv, oldBranch, newBranch, force) 38 39 if err != nil { 40 return err 41 } 42 43 if ref.Equals(dEnv.RepoState.CWBHeadRef(), oldRef) { 44 dEnv.RepoState.Head = ref.MarshalableRef{Ref: newRef} 45 err = dEnv.RepoState.Save(dEnv.FS) 46 47 if err != nil { 48 return err 49 } 50 } 51 52 return DeleteBranch(ctx, dEnv, oldBranch, DeleteOptions{Force: true}) 53 } 54 55 func CopyBranch(ctx context.Context, dEnv *env.DoltEnv, oldBranch, newBranch string, force bool) error { 56 return CopyBranchOnDB(ctx, dEnv.DoltDB, oldBranch, newBranch, force) 57 } 58 59 func CopyBranchOnDB(ctx context.Context, ddb *doltdb.DoltDB, oldBranch, newBranch string, force bool) error { 60 oldRef := ref.NewBranchRef(oldBranch) 61 newRef := ref.NewBranchRef(newBranch) 62 63 hasOld, oldErr := ddb.HasRef(ctx, oldRef) 64 65 if oldErr != nil { 66 return oldErr 67 } 68 69 hasNew, newErr := ddb.HasRef(ctx, newRef) 70 71 if newErr != nil { 72 return newErr 73 } 74 75 if !hasOld { 76 return doltdb.ErrBranchNotFound 77 } else if !force && hasNew { 78 return ErrAlreadyExists 79 } else if !doltdb.IsValidUserBranchName(newBranch) { 80 return doltdb.ErrInvBranchName 81 } 82 83 cs, _ := doltdb.NewCommitSpec(oldBranch) 84 cm, err := ddb.Resolve(ctx, cs, nil) 85 86 if err != nil { 87 return err 88 } 89 90 return ddb.NewBranchAtCommit(ctx, newRef, cm) 91 } 92 93 type DeleteOptions struct { 94 Force bool 95 Remote bool 96 } 97 98 func DeleteBranch(ctx context.Context, dEnv *env.DoltEnv, brName string, opts DeleteOptions) error { 99 var dref ref.DoltRef 100 if opts.Remote { 101 var err error 102 dref, err = ref.NewRemoteRefFromPathStr(brName) 103 if err != nil { 104 return err 105 } 106 } else { 107 dref = ref.NewBranchRef(brName) 108 if ref.Equals(dEnv.RepoState.CWBHeadRef(), dref) { 109 return ErrCOBranchDelete 110 } 111 } 112 113 return DeleteBranchOnDB(ctx, dEnv.DoltDB, dref, opts) 114 } 115 116 func DeleteBranchOnDB(ctx context.Context, ddb *doltdb.DoltDB, dref ref.DoltRef, opts DeleteOptions) error { 117 hasRef, err := ddb.HasRef(ctx, dref) 118 119 if err != nil { 120 return err 121 } else if !hasRef { 122 return doltdb.ErrBranchNotFound 123 } 124 125 if !opts.Force && !opts.Remote { 126 ms, err := doltdb.NewCommitSpec("master") 127 if err != nil { 128 return err 129 } 130 131 master, err := ddb.Resolve(ctx, ms, nil) 132 if err != nil { 133 return err 134 } 135 136 cs, err := doltdb.NewCommitSpec(dref.String()) 137 if err != nil { 138 return err 139 } 140 141 cm, err := ddb.Resolve(ctx, cs, nil) 142 if err != nil { 143 return err 144 } 145 146 isMerged, _ := master.CanFastReverseTo(ctx, cm) 147 if err != nil && err != doltdb.ErrUpToDate { 148 return err 149 } 150 if !isMerged { 151 return ErrUnmergedBranchDelete 152 } 153 } 154 155 return ddb.DeleteBranch(ctx, dref) 156 } 157 158 func CreateBranchWithStartPt(ctx context.Context, dbData env.DbData, newBranch, startPt string, force bool) error { 159 err := createBranch(ctx, dbData, newBranch, startPt, force) 160 161 if err != nil { 162 if err == ErrAlreadyExists { 163 return fmt.Errorf("fatal: A branch named '%s' already exists.", newBranch) 164 } else if err == doltdb.ErrInvBranchName { 165 return fmt.Errorf("fatal: '%s' is an invalid branch name.", newBranch) 166 } else if err == doltdb.ErrInvHash || doltdb.IsNotACommit(err) { 167 return fmt.Errorf("fatal: '%s' is not a commit and a branch '%s' cannot be created from it", startPt, newBranch) 168 } else { 169 return fmt.Errorf("fatal: Unexpected error creating branch '%s' : %v", newBranch, err) 170 } 171 } 172 173 return nil 174 } 175 176 func CreateBranchOnDB(ctx context.Context, ddb *doltdb.DoltDB, newBranch, startingPoint string, force bool, headRef ref.DoltRef) error { 177 newRef := ref.NewBranchRef(newBranch) 178 hasRef, err := ddb.HasRef(ctx, newRef) 179 180 if err != nil { 181 return err 182 } 183 184 if !force && hasRef { 185 return ErrAlreadyExists 186 } 187 188 if !doltdb.IsValidUserBranchName(newBranch) { 189 return doltdb.ErrInvBranchName 190 } 191 192 cs, err := doltdb.NewCommitSpec(startingPoint) 193 194 if err != nil { 195 return err 196 } 197 198 cm, err := ddb.Resolve(ctx, cs, headRef) 199 200 if err != nil { 201 return err 202 } 203 204 return ddb.NewBranchAtCommit(ctx, newRef, cm) 205 } 206 207 func createBranch(ctx context.Context, dbData env.DbData, newBranch, startingPoint string, force bool) error { 208 return CreateBranchOnDB(ctx, dbData.Ddb, newBranch, startingPoint, force, dbData.Rsr.CWBHeadRef()) 209 } 210 211 // updateRootsForBranch writes the roots needed for a checkout and returns the updated work and staged hash. 212 func updateRootsForBranch(ctx context.Context, dbData env.DbData, dref ref.DoltRef, brName string) (wrkHash hash.Hash, stgHash hash.Hash, err error) { 213 hasRef, err := dbData.Ddb.HasRef(ctx, dref) 214 if err != nil { 215 return hash.Hash{}, hash.Hash{}, err 216 } 217 if !hasRef { 218 return hash.Hash{}, hash.Hash{}, doltdb.ErrBranchNotFound 219 } 220 if ref.Equals(dbData.Rsr.CWBHeadRef(), dref) { 221 return hash.Hash{}, hash.Hash{}, doltdb.ErrAlreadyOnBranch 222 } 223 224 currRoots, err := getRoots(ctx, dbData.Ddb, dbData.Rsr, doltdb.HeadRoot, doltdb.WorkingRoot, doltdb.StagedRoot) 225 if err != nil { 226 return hash.Hash{}, hash.Hash{}, err 227 } 228 229 cs, err := doltdb.NewCommitSpec(brName) 230 if err != nil { 231 return hash.Hash{}, hash.Hash{}, doltdb.RootValueUnreadable{RootType: doltdb.HeadRoot, Cause: err} 232 } 233 234 cm, err := dbData.Ddb.Resolve(ctx, cs, nil) 235 if err != nil { 236 return hash.Hash{}, hash.Hash{}, doltdb.RootValueUnreadable{RootType: doltdb.HeadRoot, Cause: err} 237 } 238 239 newRoot, err := cm.GetRootValue() 240 if err != nil { 241 return hash.Hash{}, hash.Hash{}, err 242 } 243 244 conflicts := set.NewStrSet([]string{}) 245 246 wrkTblHashes, err := moveModifiedTables(ctx, currRoots[doltdb.HeadRoot], newRoot, currRoots[doltdb.WorkingRoot], conflicts) 247 if err != nil { 248 return hash.Hash{}, hash.Hash{}, err 249 } 250 251 stgTblHashes, err := moveModifiedTables(ctx, currRoots[doltdb.HeadRoot], newRoot, currRoots[doltdb.StagedRoot], conflicts) 252 if err != nil { 253 return hash.Hash{}, hash.Hash{}, err 254 } 255 if conflicts.Size() > 0 { 256 return hash.Hash{}, hash.Hash{}, CheckoutWouldOverwrite{conflicts.AsSlice()} 257 } 258 259 wrkHash, err = writeRoot(ctx, dbData.Ddb, newRoot, wrkTblHashes) 260 if err != nil { 261 return hash.Hash{}, hash.Hash{}, err 262 } 263 264 stgHash, err = writeRoot(ctx, dbData.Ddb, newRoot, stgTblHashes) 265 if err != nil { 266 return hash.Hash{}, hash.Hash{}, err 267 } 268 269 return wrkHash, stgHash, nil 270 } 271 272 func CheckoutBranch(ctx context.Context, dEnv *env.DoltEnv, brName string) error { 273 dbData := dEnv.DbData() 274 dref := ref.NewBranchRef(brName) 275 276 wrkHash, stgHash, err := updateRootsForBranch(ctx, dbData, dref, brName) 277 if err != nil { 278 return err 279 } 280 281 unstagedDocs, err := GetUnstagedDocs(ctx, dbData) 282 if err != nil { 283 return err 284 } 285 286 err = dbData.Rsw.SetWorkingHash(ctx, wrkHash) 287 if err != nil { 288 return err 289 } 290 291 err = dbData.Rsw.SetStagedHash(ctx, stgHash) 292 if err != nil { 293 return err 294 } 295 296 err = dbData.Rsw.SetCWBHeadRef(ctx, ref.MarshalableRef{Ref: dref}) 297 if err != nil { 298 return err 299 } 300 301 return SaveDocsFromWorkingExcludingFSChanges(ctx, dEnv, unstagedDocs) 302 } 303 304 // CheckoutBranchWithoutDocs checkouts a branch without considering any working changes to the local docs. Used 305 // with DOLT_CHECKOUT. 306 func CheckoutBranchWithoutDocs(ctx context.Context, dbData env.DbData, brName string) error { 307 dref := ref.NewBranchRef(brName) 308 309 wrkHash, stgHash, err := updateRootsForBranch(ctx, dbData, dref, brName) 310 if err != nil { 311 return err 312 } 313 314 err = dbData.Rsw.SetWorkingHash(ctx, wrkHash) 315 if err != nil { 316 return err 317 } 318 319 err = dbData.Rsw.SetStagedHash(ctx, stgHash) 320 if err != nil { 321 return err 322 } 323 324 return dbData.Rsw.SetCWBHeadRef(ctx, ref.MarshalableRef{Ref: dref}) 325 } 326 327 var emptyHash = hash.Hash{} 328 329 // moveModifiedTables handles working set changes during a branch change. 330 // When moving between branches, changes in the working set should travel with you. 331 // Working set changes cannot be moved if the table differs between the old and new head, 332 // in this case, we throw a conflict and error (as per Git). 333 func moveModifiedTables(ctx context.Context, oldRoot, newRoot, changedRoot *doltdb.RootValue, conflicts *set.StrSet) (map[string]hash.Hash, error) { 334 resultMap := make(map[string]hash.Hash) 335 tblNames, err := newRoot.GetTableNames(ctx) 336 if err != nil { 337 return nil, err 338 } 339 340 for _, tblName := range tblNames { 341 oldHash, _, err := oldRoot.GetTableHash(ctx, tblName) 342 if err != nil { 343 return nil, err 344 } 345 346 newHash, _, err := newRoot.GetTableHash(ctx, tblName) 347 if err != nil { 348 return nil, err 349 } 350 351 changedHash, _, err := changedRoot.GetTableHash(ctx, tblName) 352 if err != nil { 353 return nil, err 354 } 355 356 if oldHash == changedHash { 357 resultMap[tblName] = newHash 358 } else if oldHash == newHash { 359 resultMap[tblName] = changedHash 360 } else if newHash == changedHash { 361 resultMap[tblName] = oldHash 362 } else { 363 conflicts.Add(tblName) 364 } 365 } 366 367 tblNames, err = changedRoot.GetTableNames(ctx) 368 if err != nil { 369 return nil, err 370 } 371 372 for _, tblName := range tblNames { 373 if _, exists := resultMap[tblName]; !exists { 374 oldHash, _, err := oldRoot.GetTableHash(ctx, tblName) 375 if err != nil { 376 return nil, err 377 } 378 379 changedHash, _, err := changedRoot.GetTableHash(ctx, tblName) 380 if err != nil { 381 return nil, err 382 } 383 384 if oldHash == emptyHash { 385 resultMap[tblName] = changedHash 386 } else if oldHash != changedHash { 387 conflicts.Add(tblName) 388 } 389 } 390 } 391 392 return resultMap, nil 393 } 394 395 func writeRoot(ctx context.Context, ddb *doltdb.DoltDB, head *doltdb.RootValue, tblHashes map[string]hash.Hash) (hash.Hash, error) { 396 names, err := head.GetTableNames(ctx) 397 if err != nil { 398 return hash.Hash{}, err 399 } 400 401 var toDrop []string 402 for _, name := range names { 403 if _, ok := tblHashes[name]; !ok { 404 toDrop = append(toDrop, name) 405 } 406 } 407 408 head, err = head.RemoveTables(ctx, toDrop...) 409 if err != nil { 410 return hash.Hash{}, err 411 } 412 413 for k, v := range tblHashes { 414 if v == emptyHash { 415 continue 416 } 417 418 head, err = head.SetTableHash(ctx, k, v) 419 if err != nil { 420 return hash.Hash{}, err 421 } 422 } 423 424 return ddb.WriteRootValue(ctx, head) 425 } 426 427 func IsBranch(ctx context.Context, ddb *doltdb.DoltDB, str string) (bool, error) { 428 return IsBranchOnDB(ctx, ddb, str) 429 } 430 431 func IsBranchOnDB(ctx context.Context, ddb *doltdb.DoltDB, str string) (bool, error) { 432 dref := ref.NewBranchRef(str) 433 return ddb.HasRef(ctx, dref) 434 } 435 436 func MaybeGetCommit(ctx context.Context, dEnv *env.DoltEnv, str string) (*doltdb.Commit, error) { 437 cs, err := doltdb.NewCommitSpec(str) 438 439 if err == nil { 440 cm, err := dEnv.DoltDB.Resolve(ctx, cs, dEnv.RepoState.CWBHeadRef()) 441 442 switch err { 443 case nil: 444 return cm, nil 445 446 case doltdb.ErrHashNotFound, doltdb.ErrBranchNotFound: 447 return nil, nil 448 449 default: 450 return nil, err 451 } 452 } 453 454 return nil, nil 455 }