github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/store/cmd/noms/noms_merge.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  // This file incorporates work covered by the following copyright and
    16  // permission notice:
    17  //
    18  // Copyright 2016 Attic Labs, Inc. All rights reserved.
    19  // Licensed under the Apache License, version 2.0:
    20  // http://www.apache.org/licenses/LICENSE-2.0
    21  
    22  package main
    23  
    24  import (
    25  	"context"
    26  	"fmt"
    27  	"io"
    28  	"os"
    29  	"regexp"
    30  	"sync"
    31  
    32  	flag "github.com/juju/gnuflag"
    33  
    34  	"github.com/dolthub/dolt/go/store/cmd/noms/util"
    35  	"github.com/dolthub/dolt/go/store/config"
    36  	"github.com/dolthub/dolt/go/store/d"
    37  	"github.com/dolthub/dolt/go/store/datas"
    38  	"github.com/dolthub/dolt/go/store/merge"
    39  	"github.com/dolthub/dolt/go/store/types"
    40  	"github.com/dolthub/dolt/go/store/util/status"
    41  	"github.com/dolthub/dolt/go/store/util/verbose"
    42  )
    43  
    44  var (
    45  	resolver string
    46  
    47  	nomsMerge = &util.Command{
    48  		Run:       runMerge,
    49  		UsageLine: "merge [options] <database> <left-dataset-name> <right-dataset-name> <output-dataset-name>",
    50  		Short:     "Merges and commits the head values of two named datasets",
    51  		Long:      "See Spelling Objects at https://github.com/attic-labs/noms/blob/master/doc/spelling.md for details on the database argument.\nYu must provide a working database and the names of two Datasets you want to merge. The values at the heads of these Datasets will be merged, put into a new Commit object, and set as the Head of the third provided Dataset name.",
    52  		Flags:     setupMergeFlags,
    53  		Nargs:     1, // if absolute-path not present we read it from stdin
    54  	}
    55  	datasetRe = regexp.MustCompile("^" + datas.DatasetRe.String() + "$")
    56  )
    57  
    58  func setupMergeFlags() *flag.FlagSet {
    59  	commitFlagSet := flag.NewFlagSet("merge", flag.ExitOnError)
    60  	commitFlagSet.StringVar(&resolver, "policy", "n", "conflict resolution policy for merging. Defaults to 'n', which means no resolution strategy will be applied. Supported values are 'l' (left), 'r' (right) and 'p' (prompt). 'prompt' will bring up a simple command-line prompt allowing you to resolve conflicts by choosing between 'l' or 'r' on a case-by-case basis.")
    61  	verbose.RegisterVerboseFlags(commitFlagSet)
    62  	return commitFlagSet
    63  }
    64  
    65  func checkIfTrue(b bool, format string, args ...interface{}) {
    66  	if b {
    67  		util.CheckErrorNoUsage(fmt.Errorf(format, args...))
    68  	}
    69  }
    70  
    71  func runMerge(ctx context.Context, args []string) int {
    72  	cfg := config.NewResolver()
    73  
    74  	if len(args) != 4 {
    75  		util.CheckErrorNoUsage(fmt.Errorf("incorrect number of arguments"))
    76  	}
    77  	db, err := cfg.GetDatabase(ctx, args[0])
    78  	util.CheckError(err)
    79  	defer db.Close()
    80  
    81  	leftDS, rightDS, outDS, err := resolveDatasets(ctx, db, args[1], args[2], args[3])
    82  
    83  	if err != nil {
    84  		fmt.Fprintln(os.Stderr, err.Error())
    85  		return 1
    86  	}
    87  
    88  	left, right, ancestor, err := getMergeCandidates(ctx, db, leftDS, rightDS)
    89  
    90  	if err != nil {
    91  		fmt.Fprintln(os.Stderr, err.Error())
    92  		return 1
    93  	}
    94  
    95  	policy := decidePolicy(resolver)
    96  	pc, closer := newMergeProgressChan()
    97  	merged, err := policy(ctx, left, right, ancestor, db, pc)
    98  	closer()
    99  	util.CheckErrorNoUsage(err)
   100  
   101  	leftHeadRef, ok, err := leftDS.MaybeHeadRef()
   102  	d.PanicIfError(err)
   103  
   104  	if !ok {
   105  		fmt.Fprintln(os.Stderr, args[1]+" has no head value.")
   106  		return 1
   107  	}
   108  
   109  	rightHeadRef, ok, err := rightDS.MaybeHeadRef()
   110  	d.PanicIfError(err)
   111  
   112  	if !ok {
   113  		fmt.Fprintln(os.Stderr, args[2]+" has no head value.")
   114  		return 1
   115  	}
   116  
   117  	p, err := types.NewList(ctx, db, leftHeadRef, rightHeadRef)
   118  	d.PanicIfError(err)
   119  
   120  	cm, err := datas.NewCommit(ctx, merged, p, types.EmptyStruct(db.Format()))
   121  	d.PanicIfError(err)
   122  
   123  	ref, err := db.WriteValue(ctx, cm)
   124  	d.PanicIfError(err)
   125  
   126  	_, err = db.SetHead(ctx, outDS, ref)
   127  	d.PanicIfError(err)
   128  
   129  	status.Printf("Done")
   130  	status.Done()
   131  
   132  	return 0
   133  }
   134  
   135  func resolveDatasets(ctx context.Context, db datas.Database, leftName, rightName, outName string) (leftDS, rightDS, outDS datas.Dataset, err error) {
   136  	makeDS := func(dsName string) (datas.Dataset, error) {
   137  		if !datasetRe.MatchString(dsName) {
   138  			util.CheckErrorNoUsage(fmt.Errorf("Invalid dataset %s, must match %s", dsName, datas.DatasetRe.String()))
   139  		}
   140  		return db.GetDataset(ctx, dsName)
   141  	}
   142  
   143  	leftDS, err = makeDS(leftName)
   144  
   145  	if err != nil {
   146  		return datas.Dataset{}, datas.Dataset{}, datas.Dataset{}, err
   147  	}
   148  
   149  	rightDS, err = makeDS(rightName)
   150  
   151  	if err != nil {
   152  		return datas.Dataset{}, datas.Dataset{}, datas.Dataset{}, err
   153  	}
   154  
   155  	outDS, err = makeDS(outName)
   156  
   157  	if err != nil {
   158  		return datas.Dataset{}, datas.Dataset{}, datas.Dataset{}, err
   159  	}
   160  
   161  	return
   162  }
   163  
   164  func getMergeCandidates(ctx context.Context, db datas.Database, leftDS, rightDS datas.Dataset) (left, right, ancestor types.Value, err error) {
   165  	leftRef, ok, err := leftDS.MaybeHeadRef()
   166  	d.PanicIfError(err)
   167  	checkIfTrue(!ok, "Dataset %s has no data", leftDS.ID())
   168  	rightRef, ok, err := rightDS.MaybeHeadRef()
   169  	d.PanicIfError(err)
   170  	checkIfTrue(!ok, "Dataset %s has no data", rightDS.ID())
   171  	ancestorCommit, ok := getCommonAncestor(ctx, leftRef, rightRef, db)
   172  	checkIfTrue(!ok, "Datasets %s and %s have no common ancestor", leftDS.ID(), rightDS.ID())
   173  
   174  	leftHead, ok, err := leftDS.MaybeHeadValue()
   175  	d.PanicIfError(err)
   176  
   177  	if !ok {
   178  		return nil, nil, nil, err
   179  	}
   180  
   181  	rightHead, ok, err := rightDS.MaybeHeadValue()
   182  	d.PanicIfError(err)
   183  
   184  	if !ok {
   185  		return nil, nil, nil, err
   186  	}
   187  
   188  	vfld, ok, err := ancestorCommit.MaybeGet(datas.ValueField)
   189  	d.PanicIfError(err)
   190  	d.PanicIfFalse(ok)
   191  	return leftHead, rightHead, vfld, nil
   192  
   193  }
   194  
   195  func getCommonAncestor(ctx context.Context, r1, r2 types.Ref, vr types.ValueReader) (a types.Struct, found bool) {
   196  	aRef, found, err := datas.FindCommonAncestor(ctx, r1, r2, vr, vr)
   197  	d.PanicIfError(err)
   198  	if !found {
   199  		return
   200  	}
   201  	v, err := vr.ReadValue(ctx, aRef.TargetHash())
   202  	d.PanicIfError(err)
   203  	if v == nil {
   204  		panic(aRef.TargetHash().String() + " not found")
   205  	}
   206  
   207  	isCm, err := datas.IsCommit(v)
   208  	d.PanicIfError(err)
   209  
   210  	if !isCm {
   211  		str, err := types.EncodedValueMaxLines(ctx, v, 10)
   212  		d.PanicIfError(err)
   213  		panic("Not a commit: " + str + "  ...")
   214  	}
   215  	return v.(types.Struct), true
   216  }
   217  
   218  func newMergeProgressChan() (chan struct{}, func()) {
   219  	pc := make(chan struct{}, 128)
   220  	wg := new(sync.WaitGroup)
   221  	wg.Add(1)
   222  	go func() {
   223  		count := 0
   224  		for range pc {
   225  			count++
   226  			status.Printf("Applied %d changes...", count)
   227  		}
   228  		wg.Done()
   229  	}()
   230  	return pc, func() { close(pc); wg.Wait() }
   231  }
   232  
   233  func decidePolicy(policy string) merge.Policy {
   234  	var resolve merge.ResolveFunc
   235  	switch policy {
   236  	case "n", "N":
   237  		resolve = merge.None
   238  	case "l", "L":
   239  		resolve = merge.Ours
   240  	case "r", "R":
   241  		resolve = merge.Theirs
   242  	case "p", "P":
   243  		resolve = func(aType, bType types.DiffChangeType, a, b types.Value, path types.Path) (change types.DiffChangeType, merged types.Value, ok bool) {
   244  			return cliResolve(os.Stdin, os.Stdout, aType, bType, a, b, path)
   245  		}
   246  	default:
   247  		util.CheckErrorNoUsage(fmt.Errorf("Unsupported merge policy: %s. Choices are n, l, r and a.", policy))
   248  	}
   249  	return merge.NewThreeWay(resolve)
   250  }
   251  
   252  func cliResolve(in io.Reader, out io.Writer, aType, bType types.DiffChangeType, a, b types.Value, path types.Path) (change types.DiffChangeType, merged types.Value, ok bool) {
   253  	stringer := func(v types.Value) (s string, success bool) {
   254  		switch v := v.(type) {
   255  		case types.Bool, types.Float, types.String:
   256  			return fmt.Sprintf("%v", v), true
   257  		}
   258  		return "", false
   259  	}
   260  	left, lOk := stringer(a)
   261  	right, rOk := stringer(b)
   262  	if !lOk || !rOk {
   263  		return change, merged, false
   264  	}
   265  
   266  	// TODO: Handle removes as well.
   267  	fmt.Fprintf(out, "\nConflict at: %s\n", path.String())
   268  	fmt.Fprintf(out, "Left:  %s\nRight: %s\n\n", left, right)
   269  	var choice rune
   270  	for {
   271  		fmt.Fprintln(out, "Enter 'l' to accept the left value, 'r' to accept the right value")
   272  		_, err := fmt.Fscanf(in, "%c\n", &choice)
   273  		d.PanicIfError(err)
   274  		switch choice {
   275  		case 'l', 'L':
   276  			return aType, a, true
   277  		case 'r', 'R':
   278  			return bType, b, true
   279  		}
   280  	}
   281  }