github.com/jbendotnet/noms@v0.0.0-20190904222105-c43e4293ea92/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/attic-labs/noms/cmd/util" 16 "github.com/attic-labs/noms/go/config" 17 "github.com/attic-labs/noms/go/d" 18 "github.com/attic-labs/noms/go/datas" 19 "github.com/attic-labs/noms/go/merge" 20 "github.com/attic-labs/noms/go/types" 21 "github.com/attic-labs/noms/go/util/status" 22 "github.com/attic-labs/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/attic-labs/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 }