github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/env/repo_state.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 package env 16 17 import ( 18 "context" 19 "encoding/json" 20 "fmt" 21 22 "github.com/dolthub/dolt/go/cmd/dolt/errhand" 23 "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" 24 "github.com/dolthub/dolt/go/libraries/doltcore/doltdocs" 25 "github.com/dolthub/dolt/go/libraries/doltcore/ref" 26 "github.com/dolthub/dolt/go/libraries/utils/filesys" 27 "github.com/dolthub/dolt/go/store/hash" 28 ) 29 30 type RepoStateReader interface { 31 CWBHeadRef() ref.DoltRef 32 CWBHeadSpec() *doltdb.CommitSpec 33 CWBHeadHash(ctx context.Context) (hash.Hash, error) 34 WorkingHash() hash.Hash 35 StagedHash() hash.Hash 36 IsMergeActive() bool 37 GetMergeCommit() string 38 GetPreMergeWorking() string 39 } 40 41 type RepoStateWriter interface { 42 // SetCWBHeadSpec(context.Context, *doltdb.CommitSpec) error 43 SetStagedHash(context.Context, hash.Hash) error 44 SetWorkingHash(context.Context, hash.Hash) error 45 SetCWBHeadRef(context.Context, ref.MarshalableRef) error 46 AbortMerge() error 47 ClearMerge() error 48 StartMerge(commitStr string) error 49 } 50 51 type DocsReadWriter interface { 52 // GetDocsOnDisk returns the docs in the filesytem optionally filtered by docNames. 53 GetDocsOnDisk(docNames ...string) (doltdocs.Docs, error) 54 // WriteDocsToDisk updates the documents stored in the filesystem with the contents in docs. 55 WriteDocsToDisk(docs doltdocs.Docs) error 56 } 57 58 type DbData struct { 59 Ddb *doltdb.DoltDB 60 Rsw RepoStateWriter 61 Rsr RepoStateReader 62 Drw DocsReadWriter 63 } 64 65 type BranchConfig struct { 66 Merge ref.MarshalableRef `json:"head"` 67 Remote string `json:"remote"` 68 } 69 70 type MergeState struct { 71 Commit string `json:"commit"` 72 PreMergeWorking string `json:"working_pre_merge"` 73 } 74 75 type RepoState struct { 76 Head ref.MarshalableRef `json:"head"` 77 Staged string `json:"staged"` 78 Working string `json:"working"` 79 Merge *MergeState `json:"merge"` 80 Remotes map[string]Remote `json:"remotes"` 81 Branches map[string]BranchConfig `json:"branches"` 82 } 83 84 func LoadRepoState(fs filesys.ReadWriteFS) (*RepoState, error) { 85 path := getRepoStateFile() 86 data, err := fs.ReadFile(path) 87 88 if err != nil { 89 return nil, err 90 } 91 92 var repoState RepoState 93 err = json.Unmarshal(data, &repoState) 94 95 if err != nil { 96 return nil, err 97 } 98 99 return &repoState, nil 100 } 101 102 func CloneRepoState(fs filesys.ReadWriteFS, r Remote) (*RepoState, error) { 103 h := hash.Hash{} 104 hashStr := h.String() 105 rs := &RepoState{ref.MarshalableRef{ 106 Ref: ref.NewBranchRef("master")}, 107 hashStr, 108 hashStr, 109 nil, 110 map[string]Remote{r.Name: r}, 111 make(map[string]BranchConfig), 112 } 113 114 err := rs.Save(fs) 115 116 if err != nil { 117 return nil, err 118 } 119 120 return rs, nil 121 } 122 123 func CreateRepoState(fs filesys.ReadWriteFS, br string, rootHash hash.Hash) (*RepoState, error) { 124 hashStr := rootHash.String() 125 headRef, err := ref.Parse(br) 126 127 if err != nil { 128 return nil, err 129 } 130 131 rs := &RepoState{ 132 ref.MarshalableRef{Ref: headRef}, 133 hashStr, 134 hashStr, 135 nil, 136 make(map[string]Remote), 137 make(map[string]BranchConfig), 138 } 139 140 err = rs.Save(fs) 141 142 if err != nil { 143 return nil, err 144 } 145 146 return rs, nil 147 } 148 149 func (rs *RepoState) Save(fs filesys.ReadWriteFS) error { 150 data, err := json.MarshalIndent(rs, "", " ") 151 152 if err != nil { 153 return err 154 } 155 156 path := getRepoStateFile() 157 158 return fs.WriteFile(path, data) 159 } 160 161 func (rs *RepoState) CWBHeadRef() ref.DoltRef { 162 return rs.Head.Ref 163 } 164 165 func (rs *RepoState) CWBHeadSpec() *doltdb.CommitSpec { 166 spec, _ := doltdb.NewCommitSpec("HEAD") 167 return spec 168 } 169 170 func (rs *RepoState) StartMerge(commit string, fs filesys.Filesys) error { 171 rs.Merge = &MergeState{commit, rs.Working} 172 return rs.Save(fs) 173 } 174 175 func (rs *RepoState) AbortMerge(fs filesys.Filesys) error { 176 rs.Working = rs.Merge.PreMergeWorking 177 return rs.ClearMerge(fs) 178 } 179 180 func (rs *RepoState) ClearMerge(fs filesys.Filesys) error { 181 rs.Merge = nil 182 return rs.Save(fs) 183 } 184 185 func (rs *RepoState) AddRemote(r Remote) { 186 rs.Remotes[r.Name] = r 187 } 188 189 func (rs *RepoState) WorkingHash() hash.Hash { 190 return hash.Parse(rs.Working) 191 } 192 193 func (rs *RepoState) StagedHash() hash.Hash { 194 return hash.Parse(rs.Staged) 195 } 196 197 func (rs *RepoState) IsMergeActive() bool { 198 return rs.Merge != nil 199 } 200 201 func (rs *RepoState) GetMergeCommit() string { 202 return rs.Merge.Commit 203 } 204 205 // Returns the working root. 206 func WorkingRoot(ctx context.Context, ddb *doltdb.DoltDB, rsr RepoStateReader) (*doltdb.RootValue, error) { 207 return ddb.ReadRootValue(ctx, rsr.WorkingHash()) 208 } 209 210 // Updates the working root. 211 func UpdateWorkingRoot(ctx context.Context, ddb *doltdb.DoltDB, rsw RepoStateWriter, newRoot *doltdb.RootValue) (hash.Hash, error) { 212 h, err := ddb.WriteRootValue(ctx, newRoot) 213 214 if err != nil { 215 return hash.Hash{}, doltdb.ErrNomsIO 216 } 217 218 err = rsw.SetWorkingHash(ctx, h) 219 220 if err != nil { 221 return hash.Hash{}, ErrStateUpdate 222 } 223 224 return h, nil 225 } 226 227 // Returns the head root. 228 func HeadRoot(ctx context.Context, ddb *doltdb.DoltDB, rsr RepoStateReader) (*doltdb.RootValue, error) { 229 commit, err := ddb.ResolveCommitRef(ctx, rsr.CWBHeadRef()) 230 231 if err != nil { 232 return nil, err 233 } 234 235 return commit.GetRootValue() 236 } 237 238 // Returns the staged root. 239 func StagedRoot(ctx context.Context, ddb *doltdb.DoltDB, rsr RepoStateReader) (*doltdb.RootValue, error) { 240 return ddb.ReadRootValue(ctx, rsr.StagedHash()) 241 } 242 243 // Updates the staged root. 244 func UpdateStagedRoot(ctx context.Context, ddb *doltdb.DoltDB, rsw RepoStateWriter, newRoot *doltdb.RootValue) (hash.Hash, error) { 245 h, err := ddb.WriteRootValue(ctx, newRoot) 246 247 if err != nil { 248 return hash.Hash{}, doltdb.ErrNomsIO 249 } 250 251 err = rsw.SetStagedHash(ctx, h) 252 253 if err != nil { 254 return hash.Hash{}, ErrStateUpdate 255 } 256 257 return h, nil 258 } 259 260 func UpdateStagedRootWithVErr(ddb *doltdb.DoltDB, rsw RepoStateWriter, updatedRoot *doltdb.RootValue) errhand.VerboseError { 261 _, err := UpdateStagedRoot(context.Background(), ddb, rsw, updatedRoot) 262 263 switch err { 264 case doltdb.ErrNomsIO: 265 return errhand.BuildDError("fatal: failed to write value").Build() 266 case ErrStateUpdate: 267 return errhand.BuildDError("fatal: failed to update the staged root state").Build() 268 } 269 270 return nil 271 } 272 273 func GetRoots(ctx context.Context, ddb *doltdb.DoltDB, rsr RepoStateReader) (working *doltdb.RootValue, staged *doltdb.RootValue, head *doltdb.RootValue, err error) { 274 working, err = WorkingRoot(ctx, ddb, rsr) 275 276 if err != nil { 277 return nil, nil, nil, err 278 } 279 280 staged, err = StagedRoot(ctx, ddb, rsr) 281 282 if err != nil { 283 return nil, nil, nil, err 284 } 285 286 head, err = HeadRoot(ctx, ddb, rsr) 287 288 if err != nil { 289 return nil, nil, nil, err 290 } 291 292 return working, staged, head, nil 293 } 294 295 func MergeWouldStompChanges(ctx context.Context, mergeCommit *doltdb.Commit, dbData DbData) ([]string, map[string]hash.Hash, error) { 296 headRoot, err := HeadRoot(ctx, dbData.Ddb, dbData.Rsr) 297 298 if err != nil { 299 return nil, nil, err 300 } 301 302 workingRoot, err := WorkingRoot(ctx, dbData.Ddb, dbData.Rsr) 303 304 if err != nil { 305 return nil, nil, err 306 } 307 308 mergeRoot, err := mergeCommit.GetRootValue() 309 310 if err != nil { 311 return nil, nil, err 312 } 313 314 headTableHashes, err := mapTableHashes(ctx, headRoot) 315 316 if err != nil { 317 return nil, nil, err 318 } 319 320 workingTableHashes, err := mapTableHashes(ctx, workingRoot) 321 322 if err != nil { 323 return nil, nil, err 324 } 325 326 mergeTableHashes, err := mapTableHashes(ctx, mergeRoot) 327 328 if err != nil { 329 return nil, nil, err 330 } 331 332 headWorkingDiffs := diffTableHashes(headTableHashes, workingTableHashes) 333 mergeWorkingDiffs := diffTableHashes(headTableHashes, mergeTableHashes) 334 335 stompedTables := make([]string, 0, len(headWorkingDiffs)) 336 for tName, _ := range headWorkingDiffs { 337 if _, ok := mergeWorkingDiffs[tName]; ok { 338 // even if the working changes match the merge changes, don't allow (matches git behavior). 339 stompedTables = append(stompedTables, tName) 340 } 341 } 342 343 return stompedTables, headWorkingDiffs, nil 344 } 345 346 // GetGCKeepers queries |rsr| to find a list of values that need to be temporarily saved during GC. 347 func GetGCKeepers(ctx context.Context, rsr RepoStateReader, ddb *doltdb.DoltDB) ([]hash.Hash, error) { 348 keepers := []hash.Hash{ 349 rsr.WorkingHash(), 350 rsr.StagedHash(), 351 } 352 353 if rsr.IsMergeActive() { 354 spec, err := doltdb.NewCommitSpec(rsr.GetMergeCommit()) 355 if err != nil { 356 return nil, err 357 } 358 359 cm, err := ddb.Resolve(ctx, spec, nil) 360 if err != nil { 361 return nil, err 362 } 363 364 ch, err := cm.HashOf() 365 if err != nil { 366 return nil, err 367 } 368 369 pmw := hash.Parse(rsr.GetPreMergeWorking()) 370 val, err := ddb.ValueReadWriter().ReadValue(ctx, pmw) 371 if err != nil { 372 return nil, err 373 } 374 if val == nil { 375 return nil, fmt.Errorf("MergeState.PreMergeWorking is a dangling hash") 376 } 377 378 keepers = append(keepers, ch, pmw) 379 } 380 381 return keepers, nil 382 } 383 384 func mapTableHashes(ctx context.Context, root *doltdb.RootValue) (map[string]hash.Hash, error) { 385 names, err := root.GetTableNames(ctx) 386 387 if err != nil { 388 return nil, err 389 } 390 391 nameToHash := make(map[string]hash.Hash) 392 for _, name := range names { 393 h, ok, err := root.GetTableHash(ctx, name) 394 395 if err != nil { 396 return nil, err 397 } else if !ok { 398 panic("GetTableNames returned a table that GetTableHash says isn't there.") 399 } else { 400 nameToHash[name] = h 401 } 402 } 403 404 return nameToHash, nil 405 } 406 407 func diffTableHashes(headTableHashes, otherTableHashes map[string]hash.Hash) map[string]hash.Hash { 408 diffs := make(map[string]hash.Hash) 409 for tName, hh := range headTableHashes { 410 if h, ok := otherTableHashes[tName]; ok { 411 if h != hh { 412 // modification 413 diffs[tName] = h 414 } 415 } else { 416 // deletion 417 diffs[tName] = hash.Hash{} 418 } 419 } 420 421 for tName, h := range otherTableHashes { 422 if _, ok := headTableHashes[tName]; !ok { 423 // addition 424 diffs[tName] = h 425 } 426 } 427 428 return diffs 429 }