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 }