github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/doltdb/commit.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 doltdb 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 22 "github.com/dolthub/dolt/go/store/datas" 23 "github.com/dolthub/dolt/go/store/hash" 24 "github.com/dolthub/dolt/go/store/prolly" 25 "github.com/dolthub/dolt/go/store/prolly/tree" 26 "github.com/dolthub/dolt/go/store/types" 27 ) 28 29 var errCommitHasNoMeta = errors.New("commit has no metadata") 30 var errHasNoRootValue = errors.New("no root value") 31 32 // TODO: Include the commit id in the error. Unfortunately, this message is passed through the SQL layer. The only way we currently 33 // have on the client side to match an error is with string matching. We possibly need error codes as a prefix to the error message, but 34 // currently there is not standard for doing this in Dolt. 35 var ErrGhostCommitEncountered = errors.New("Commit not found. You are using a shallow clone which does not contain the requested commit. Please do a full clone.") 36 var ErrGhostCommitRuntimeFailure = errors.New("runtime failure: Ghost commit encountered unexpectedly. Please report bug to: https://github.com/dolthub/dolt/issues") 37 38 // Rootish is an object resolvable to a RootValue. 39 type Rootish interface { 40 // ResolveRootValue resolves a Rootish to a RootValue. 41 ResolveRootValue(ctx context.Context) (RootValue, error) 42 43 // HashOf returns the hash.Hash of the Rootish. 44 HashOf() (hash.Hash, error) 45 } 46 47 // Commit contains information on a commit that was written to noms 48 type Commit struct { 49 vrw types.ValueReadWriter 50 ns tree.NodeStore 51 parents []*datas.Commit 52 dCommit *datas.Commit 53 } 54 55 type OptionalCommit struct { 56 Commit *Commit 57 Addr hash.Hash 58 } 59 60 // ToCommit unwraps the *Commit contained by the OptionalCommit. If the commit is invalid, it returns (nil, false). 61 // Otherwise, it returns (commit, true). 62 func (cmt *OptionalCommit) ToCommit() (*Commit, bool) { 63 if cmt.Commit == nil { 64 return nil, false 65 } 66 return cmt.Commit, true 67 } 68 69 var _ Rootish = &Commit{} 70 71 // NewCommit generates a new Commit object that wraps a supplied datas.Commit. 72 func NewCommit(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, commit *datas.Commit) (*Commit, error) { 73 if commit.IsGhost() { 74 return nil, ErrGhostCommitRuntimeFailure 75 } 76 77 parents, err := datas.GetCommitParents(ctx, vrw, commit.NomsValue()) 78 if err != nil { 79 return nil, err 80 } 81 return &Commit{vrw, ns, parents, commit}, nil 82 } 83 84 // NewCommitFromValue generates a new Commit object that wraps a supplied types.Value. 85 func NewCommitFromValue(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, value types.Value) (*Commit, error) { 86 commit, err := datas.CommitFromValue(vrw.Format(), value) 87 if err != nil { 88 return nil, err 89 } 90 return NewCommit(ctx, vrw, ns, commit) 91 } 92 93 // HashOf returns the hash of the commit 94 func (c *Commit) HashOf() (hash.Hash, error) { 95 return c.dCommit.Addr(), nil 96 } 97 98 // Value returns the types.Value that backs the commit. 99 func (c *Commit) Value() types.Value { 100 return c.dCommit.NomsValue() 101 } 102 103 // GetCommitMeta gets the metadata associated with the commit 104 func (c *Commit) GetCommitMeta(ctx context.Context) (*datas.CommitMeta, error) { 105 return datas.GetCommitMeta(ctx, c.dCommit.NomsValue()) 106 } 107 108 // DatasParents returns the []*datas.Commit of the commit parents. 109 func (c *Commit) DatasParents() []*datas.Commit { 110 return c.parents 111 } 112 113 // ParentHashes returns the commit hashes for all parent commits. 114 func (c *Commit) ParentHashes(ctx context.Context) ([]hash.Hash, error) { 115 hashes := make([]hash.Hash, len(c.parents)) 116 for i, pr := range c.parents { 117 hashes[i] = pr.Addr() 118 } 119 return hashes, nil 120 } 121 122 // NumParents gets the number of parents a commit has. 123 func (c *Commit) NumParents() int { 124 return len(c.parents) 125 } 126 127 func (c *Commit) Height() (uint64, error) { 128 return c.dCommit.Height(), nil 129 } 130 131 // GetRootValue gets the RootValue of the commit. 132 func (c *Commit) GetRootValue(ctx context.Context) (RootValue, error) { 133 rootV, err := datas.GetCommittedValue(ctx, c.vrw, c.dCommit.NomsValue()) 134 if err != nil { 135 return nil, err 136 } 137 if rootV == nil { 138 return nil, errHasNoRootValue 139 } 140 return NewRootValue(ctx, c.vrw, c.ns, rootV) 141 } 142 143 func (c *Commit) GetParent(ctx context.Context, idx int) (*OptionalCommit, error) { 144 parent := c.parents[idx] 145 if parent.IsGhost() { 146 return &OptionalCommit{nil, parent.Addr()}, nil 147 } 148 149 cmt, err := NewCommit(ctx, c.vrw, c.ns, parent) 150 if err != nil { 151 return nil, err 152 } 153 return &OptionalCommit{cmt, parent.Addr()}, nil 154 } 155 156 func (c *Commit) GetCommitClosure(ctx context.Context) (prolly.CommitClosure, error) { 157 return getCommitClosure(ctx, c.dCommit, c.vrw, c.ns) 158 } 159 160 func getCommitClosure(ctx context.Context, cmt *datas.Commit, vrw types.ValueReadWriter, ns tree.NodeStore) (prolly.CommitClosure, error) { 161 switch v := cmt.NomsValue().(type) { 162 case types.SerialMessage: 163 return datas.NewParentsClosure(ctx, cmt, v, vrw, ns) 164 default: 165 return prolly.CommitClosure{}, fmt.Errorf("old format lacks commit closure") 166 } 167 } 168 169 var ErrNoCommonAncestor = errors.New("no common ancestor") 170 171 func GetCommitAncestor(ctx context.Context, cm1, cm2 *Commit) (*OptionalCommit, error) { 172 addr, err := getCommitAncestorAddr(ctx, cm1.dCommit, cm2.dCommit, cm1.vrw, cm2.vrw, cm1.ns, cm2.ns) 173 if err != nil { 174 return nil, err 175 } 176 177 targetCommit, err := datas.LoadCommitAddr(ctx, cm1.vrw, addr) 178 if err != nil { 179 return nil, err 180 } 181 182 if targetCommit.IsGhost() { 183 return &OptionalCommit{nil, addr}, nil 184 } 185 186 cmt, err := NewCommit(ctx, cm1.vrw, cm1.ns, targetCommit) 187 if err != nil { 188 return nil, err 189 } 190 return &OptionalCommit{cmt, addr}, nil 191 } 192 193 func getCommitAncestorAddr(ctx context.Context, c1, c2 *datas.Commit, vrw1, vrw2 types.ValueReadWriter, ns1, ns2 tree.NodeStore) (hash.Hash, error) { 194 ancestorAddr, ok, err := datas.FindCommonAncestor(ctx, c1, c2, vrw1, vrw2, ns1, ns2) 195 if err != nil { 196 return hash.Hash{}, err 197 } 198 199 if !ok { 200 return hash.Hash{}, ErrNoCommonAncestor 201 } 202 203 return ancestorAddr, nil 204 } 205 206 func (c *Commit) CanFastForwardTo(ctx context.Context, new *Commit) (bool, error) { 207 optAnc, err := GetCommitAncestor(ctx, c, new) 208 if err != nil { 209 return false, err 210 } 211 212 ancestor, ok := optAnc.ToCommit() 213 if !ok { 214 return false, fmt.Errorf("Unexpected Ghost Commit") 215 } 216 if ancestor == nil { 217 return false, errors.New("cannot perform fast forward merge; commits have no common ancestor") 218 } else if ancestor.dCommit.Addr() == c.dCommit.Addr() { 219 if ancestor.dCommit.Addr() == new.dCommit.Addr() { 220 return true, ErrUpToDate 221 } 222 return true, nil 223 } else if ancestor.dCommit.Addr() == new.dCommit.Addr() { 224 return false, ErrIsAhead 225 } 226 227 return false, nil 228 } 229 230 func (c *Commit) CanFastReverseTo(ctx context.Context, new *Commit) (bool, error) { 231 optAnc, err := GetCommitAncestor(ctx, c, new) 232 if err != nil { 233 return false, err 234 } 235 236 ancestor, ok := optAnc.ToCommit() 237 if !ok { 238 return false, ErrGhostCommitEncountered 239 } 240 if ancestor == nil { 241 return false, errors.New("cannot perform fast forward merge; commits have no common ancestor") 242 } else if ancestor.dCommit.Addr() == new.dCommit.Addr() { 243 if ancestor.dCommit.Addr() == c.dCommit.Addr() { 244 return true, ErrUpToDate 245 } 246 return true, nil 247 } else if ancestor.dCommit.Addr() == c.dCommit.Addr() { 248 return false, ErrIsBehind 249 } 250 251 return false, nil 252 } 253 254 func (c *Commit) GetAncestor(ctx context.Context, as *AncestorSpec) (*OptionalCommit, error) { 255 addr, err := c.HashOf() 256 if err != nil { 257 return nil, err 258 } 259 optInst := &OptionalCommit{c, addr} 260 if as == nil || len(as.Instructions) == 0 { 261 return optInst, nil 262 } 263 264 hardInst := c 265 instructions := as.Instructions 266 for _, inst := range instructions { 267 if inst >= hardInst.NumParents() { 268 return nil, ErrInvalidAncestorSpec 269 } 270 271 var err error 272 optInst, err = hardInst.GetParent(ctx, inst) 273 if err != nil { 274 return nil, err 275 } 276 277 var ok bool 278 hardInst, ok = optInst.ToCommit() 279 if !ok { 280 break 281 } 282 } 283 284 return optInst, nil 285 } 286 287 // ResolveRootValue implements Rootish. 288 func (c *Commit) ResolveRootValue(ctx context.Context) (RootValue, error) { 289 return c.GetRootValue(ctx) 290 } 291 292 // PendingCommit represents a commit that hasn't yet been written to storage. It contains a root value and options to 293 // use when committing it. Use a PendingCommit when it's important to update the working set and HEAD together 294 // atomically, via doltdb.CommitWithWorkingSet 295 type PendingCommit struct { 296 Roots Roots 297 Val types.Value 298 CommitOptions datas.CommitOptions 299 } 300 301 // NewPendingCommit returns a new PendingCommit object to be written with doltdb.CommitWithWorkingSet. 302 // |roots| are the current roots to include in the PendingCommit. roots.Staged is used as the new root to package in the 303 // commit, once written. 304 // |headRef| is the ref of the HEAD the commit will update 305 // |mergeParentCommits| are any merge parents for this commit 306 // |cm| is the metadata for the commit 307 // The current branch head will be automatically filled in as the first parent at commit time. 308 func (ddb *DoltDB) NewPendingCommit( 309 ctx context.Context, 310 roots Roots, 311 mergeParentCommits []*Commit, 312 cm *datas.CommitMeta, 313 ) (*PendingCommit, error) { 314 newstaged, val, err := ddb.writeRootValue(ctx, roots.Staged) 315 if err != nil { 316 return nil, err 317 } 318 roots.Staged = newstaged 319 320 var parents []hash.Hash 321 for _, pc := range mergeParentCommits { 322 parents = append(parents, pc.dCommit.Addr()) 323 } 324 325 commitOpts := datas.CommitOptions{Parents: parents, Meta: cm} 326 return &PendingCommit{ 327 Roots: roots, 328 Val: val, 329 CommitOptions: commitOpts, 330 }, nil 331 }