github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/merge/action.go (about)

     1  // Copyright 2021 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 merge
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"time"
    22  
    23  	"github.com/dolthub/go-mysql-server/sql"
    24  
    25  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    26  	"github.com/dolthub/dolt/go/libraries/doltcore/env"
    27  	"github.com/dolthub/dolt/go/libraries/doltcore/env/actions"
    28  	"github.com/dolthub/dolt/go/store/hash"
    29  )
    30  
    31  var ErrFailedToDetermineMergeability = errors.New("failed to determine mergeability")
    32  
    33  type MergeSpec struct {
    34  	HeadH           hash.Hash
    35  	MergeH          hash.Hash
    36  	HeadC           *doltdb.Commit
    37  	MergeC          *doltdb.Commit
    38  	MergeCSpecStr   string
    39  	StompedTblNames []string
    40  	WorkingDiffs    map[string]hash.Hash
    41  	Squash          bool
    42  	NoFF            bool
    43  	NoCommit        bool
    44  	NoEdit          bool
    45  	Force           bool
    46  	Email           string
    47  	Name            string
    48  	Date            time.Time
    49  }
    50  
    51  type MergeSpecOpt func(*MergeSpec)
    52  
    53  func WithNoFF(noFF bool) MergeSpecOpt {
    54  	return func(ms *MergeSpec) {
    55  		ms.NoFF = noFF
    56  	}
    57  }
    58  
    59  func WithNoCommit(noCommit bool) MergeSpecOpt {
    60  	return func(ms *MergeSpec) {
    61  		ms.NoCommit = noCommit
    62  	}
    63  }
    64  
    65  func WithNoEdit(noEdit bool) MergeSpecOpt {
    66  	return func(ms *MergeSpec) {
    67  		ms.NoEdit = noEdit
    68  	}
    69  }
    70  
    71  func WithForce(force bool) MergeSpecOpt {
    72  	return func(ms *MergeSpec) {
    73  		ms.Force = force
    74  	}
    75  }
    76  
    77  func WithSquash(squash bool) MergeSpecOpt {
    78  	return func(ms *MergeSpec) {
    79  		ms.Squash = squash
    80  	}
    81  }
    82  
    83  // NewMergeSpec returns a MergeSpec with the arguments provided.
    84  func NewMergeSpec(
    85  	ctx context.Context,
    86  	rsr env.RepoStateReader,
    87  	ddb *doltdb.DoltDB,
    88  	roots doltdb.Roots,
    89  	name, email, commitSpecStr string,
    90  	date time.Time,
    91  	opts ...MergeSpecOpt,
    92  ) (*MergeSpec, error) {
    93  	headCS, err := doltdb.NewCommitSpec("HEAD")
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	headRef, err := rsr.CWBHeadRef()
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	optCmt, err := ddb.Resolve(ctx, headCS, headRef)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	headCM, ok := optCmt.ToCommit()
   108  	if !ok {
   109  		// HEAD should always resolve to a commit, so this should never happen.
   110  		return nil, doltdb.ErrGhostCommitRuntimeFailure
   111  	}
   112  
   113  	mergeCS, err := doltdb.NewCommitSpec(commitSpecStr)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  
   118  	optCmt, err = ddb.Resolve(ctx, mergeCS, headRef)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  	mergeCM, ok := optCmt.ToCommit()
   123  	if !ok {
   124  		return nil, doltdb.ErrGhostCommitEncountered
   125  	}
   126  
   127  	headH, err := headCM.HashOf()
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  
   132  	mergeH, err := mergeCM.HashOf()
   133  	if err != nil {
   134  		return nil, err
   135  
   136  	}
   137  
   138  	stompedTblNames, workingDiffs, err := MergeWouldStompChanges(ctx, roots, mergeCM)
   139  	if err != nil {
   140  		return nil, fmt.Errorf("%w; %s", ErrFailedToDetermineMergeability, err.Error())
   141  	}
   142  
   143  	spec := &MergeSpec{
   144  		HeadH:           headH,
   145  		MergeH:          mergeH,
   146  		HeadC:           headCM,
   147  		MergeCSpecStr:   commitSpecStr,
   148  		MergeC:          mergeCM,
   149  		StompedTblNames: stompedTblNames,
   150  		WorkingDiffs:    workingDiffs,
   151  		Email:           email,
   152  		Name:            name,
   153  		Date:            date,
   154  	}
   155  
   156  	for _, opt := range opts {
   157  		opt(spec)
   158  	}
   159  
   160  	return spec, nil
   161  }
   162  
   163  // AbortMerge returns a new WorkingSet instance, with the active merge aborted, by clearing and
   164  // resetting the merge state in |workingSet| and using |roots| to identify the existing tables
   165  // and reset them, excluding any ignored tables. The caller must then set the new WorkingSet in
   166  // the session before the aborted merge is finalized. If no merge is in progress, this function
   167  // returns an error.
   168  func AbortMerge(ctx *sql.Context, workingSet *doltdb.WorkingSet, roots doltdb.Roots) (*doltdb.WorkingSet, error) {
   169  	if !workingSet.MergeActive() {
   170  		return nil, fmt.Errorf("there is no merge to abort")
   171  	}
   172  
   173  	tbls, err := doltdb.UnionTableNames(ctx, roots.Working, roots.Staged, roots.Head)
   174  	if err != nil {
   175  		return nil, err
   176  	}
   177  	tbls, err = doltdb.ExcludeIgnoredTables(ctx, roots, tbls)
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  
   182  	roots, err = actions.MoveTablesFromHeadToWorking(ctx, roots, tbls)
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  
   187  	preMergeWorkingRoot := workingSet.MergeState().PreMergeWorkingRoot()
   188  	preMergeWorkingTables, err := preMergeWorkingRoot.GetTableNames(ctx, doltdb.DefaultSchemaName)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  	nonIgnoredTables, err := doltdb.ExcludeIgnoredTables(ctx, roots, preMergeWorkingTables)
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  	someTablesAreIgnored := len(nonIgnoredTables) != len(preMergeWorkingTables)
   197  
   198  	if someTablesAreIgnored {
   199  		newWorking, err := actions.MoveTablesBetweenRoots(ctx, nonIgnoredTables, preMergeWorkingRoot, roots.Working)
   200  		if err != nil {
   201  			return nil, err
   202  		}
   203  		workingSet = workingSet.WithWorkingRoot(newWorking)
   204  	} else {
   205  		workingSet = workingSet.WithWorkingRoot(preMergeWorkingRoot)
   206  	}
   207  	// Unstage everything by making Staged match Head
   208  	workingSet = workingSet.WithStagedRoot(roots.Head)
   209  	workingSet = workingSet.ClearMerge()
   210  
   211  	return workingSet, nil
   212  }