github.com/ndau/noms@v1.0.5/cmd/noms/noms_merge.go (about)

     1  // Copyright 2016 Attic Labs, Inc. All rights reserved.
     2  // Licensed under the Apache License, version 2.0:
     3  // http://www.apache.org/licenses/LICENSE-2.0
     4  
     5  package main
     6  
     7  import (
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"regexp"
    12  
    13  	"github.com/attic-labs/kingpin"
    14  
    15  	"github.com/ndau/noms/cmd/util"
    16  	"github.com/ndau/noms/go/config"
    17  	"github.com/ndau/noms/go/d"
    18  	"github.com/ndau/noms/go/datas"
    19  	"github.com/ndau/noms/go/merge"
    20  	"github.com/ndau/noms/go/types"
    21  	"github.com/ndau/noms/go/util/status"
    22  	"github.com/ndau/noms/go/util/verbose"
    23  )
    24  
    25  var (
    26  	datasetRe = regexp.MustCompile("^" + datas.DatasetRe.String() + "$")
    27  )
    28  
    29  func nomsMerge(noms *kingpin.Application) (*kingpin.CmdClause, util.KingpinHandler) {
    30  	cmd := noms.Command("merge", "Merges two datasets.")
    31  	resolver := cmd.Flag("policy", "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.").Default("n").String()
    32  	db := cmd.Arg("db", "database to work with - see Spelling Databases at https://github.com/ndau/noms/blob/master/doc/spelling.md").Required().String()
    33  	left := cmd.Arg("left", "left dataset name").Required().String()
    34  	right := cmd.Arg("right", "right dataset name").Required().String()
    35  
    36  	return cmd, func(input string) int {
    37  		cfg := config.NewResolver()
    38  
    39  		db, err := cfg.GetDatabase(*db)
    40  		d.CheckError(err)
    41  		defer db.Close()
    42  
    43  		leftDS, rightDS := resolveDatasets(db, *left, *right)
    44  		left, right, ancestor := getMergeCandidates(db, leftDS, rightDS)
    45  		policy := decidePolicy(*resolver)
    46  		pc := newMergeProgressChan()
    47  		merged, err := policy(left, right, ancestor, db, pc)
    48  		d.CheckErrorNoUsage(err)
    49  		close(pc)
    50  
    51  		r := db.WriteValue(datas.NewCommit(merged, types.NewSet(db, leftDS.HeadRef(), rightDS.HeadRef()), types.EmptyStruct))
    52  		db.Flush()
    53  		fmt.Println(r.TargetHash())
    54  		return 0
    55  	}
    56  }
    57  
    58  func checkIfTrue(b bool, format string, args ...interface{}) {
    59  	if b {
    60  		d.CheckErrorNoUsage(fmt.Errorf(format, args...))
    61  	}
    62  }
    63  
    64  func resolveDatasets(db datas.Database, leftName, rightName string) (leftDS, rightDS datas.Dataset) {
    65  	makeDS := func(dsName string) datas.Dataset {
    66  		if !datasetRe.MatchString(dsName) {
    67  			d.CheckErrorNoUsage(fmt.Errorf("Invalid dataset %s, must match %s", dsName, datas.DatasetRe.String()))
    68  		}
    69  		return db.GetDataset(dsName)
    70  	}
    71  	leftDS = makeDS(leftName)
    72  	rightDS = makeDS(rightName)
    73  	return
    74  }
    75  
    76  func getMergeCandidates(db datas.Database, leftDS, rightDS datas.Dataset) (left, right, ancestor types.Value) {
    77  	leftRef, ok := leftDS.MaybeHeadRef()
    78  	checkIfTrue(!ok, "Dataset %s has no data", leftDS.ID())
    79  	rightRef, ok := rightDS.MaybeHeadRef()
    80  	checkIfTrue(!ok, "Dataset %s has no data", rightDS.ID())
    81  	ancestorCommit, ok := getCommonAncestor(leftRef, rightRef, db)
    82  	checkIfTrue(!ok, "Datasets %s and %s have no common ancestor", leftDS.ID(), rightDS.ID())
    83  
    84  	return leftDS.HeadValue(), rightDS.HeadValue(), ancestorCommit.Get(datas.ValueField)
    85  }
    86  
    87  func getCommonAncestor(r1, r2 types.Ref, vr types.ValueReader) (a types.Struct, found bool) {
    88  	aRef, found := datas.FindCommonAncestor(r1, r2, vr)
    89  	if !found {
    90  		return
    91  	}
    92  	v := vr.ReadValue(aRef.TargetHash())
    93  	if v == nil {
    94  		panic(aRef.TargetHash().String() + " not found")
    95  	}
    96  	if !datas.IsCommit(v) {
    97  		panic("Not a commit: " + types.EncodedValueMaxLines(v, 10) + "  ...")
    98  	}
    99  	return v.(types.Struct), true
   100  }
   101  
   102  func newMergeProgressChan() chan struct{} {
   103  	pc := make(chan struct{}, 128)
   104  	go func() {
   105  		count := 0
   106  		for range pc {
   107  			if !verbose.Quiet() {
   108  				count++
   109  				status.Printf("Applied %d changes...", count)
   110  			}
   111  		}
   112  	}()
   113  	return pc
   114  }
   115  
   116  func decidePolicy(policy string) merge.Policy {
   117  	var resolve merge.ResolveFunc
   118  	switch policy {
   119  	case "n", "N":
   120  		resolve = merge.None
   121  	case "l", "L":
   122  		resolve = merge.Ours
   123  	case "r", "R":
   124  		resolve = merge.Theirs
   125  	case "p", "P":
   126  		resolve = func(aType, bType types.DiffChangeType, a, b types.Value, path types.Path) (change types.DiffChangeType, merged types.Value, ok bool) {
   127  			return cliResolve(os.Stdin, os.Stdout, aType, bType, a, b, path)
   128  		}
   129  	default:
   130  		d.CheckErrorNoUsage(fmt.Errorf("Unsupported merge policy: %s. Choices are n, l, r and a.", policy))
   131  	}
   132  	return merge.NewThreeWay(resolve)
   133  }
   134  
   135  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) {
   136  	stringer := func(v types.Value) (s string, success bool) {
   137  		switch v := v.(type) {
   138  		case types.Bool, types.Number, types.String:
   139  			return fmt.Sprintf("%v", v), true
   140  		}
   141  		return "", false
   142  	}
   143  	left, lOk := stringer(a)
   144  	right, rOk := stringer(b)
   145  	if !lOk || !rOk {
   146  		return change, merged, false
   147  	}
   148  
   149  	// TODO: Handle removes as well.
   150  	fmt.Fprintf(out, "\nConflict at: %s\n", path.String())
   151  	fmt.Fprintf(out, "Left:  %s\nRight: %s\n\n", left, right)
   152  	var choice rune
   153  	for {
   154  		fmt.Fprintln(out, "Enter 'l' to accept the left value, 'r' to accept the right value")
   155  		_, err := fmt.Fscanf(in, "%c\n", &choice)
   156  		d.PanicIfError(err)
   157  		switch choice {
   158  		case 'l', 'L':
   159  			return aType, a, true
   160  		case 'r', 'R':
   161  			return bType, b, true
   162  		}
   163  	}
   164  }