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  }