(ctx context.Context, create constructor)
| 353 | type constructor func(parent *branches.Config, retries int) (*commits.Object, error) |
| 354 | |
| 355 | func (b *Branch) commit(ctx context.Context, create constructor) (ksuid.KSUID, error) { |
| 356 | // A commit must append new state to the tip of the branch while simultaneously |
| 357 | // upating the branch pointer in a trasactionally consistent fashion. |
| 358 | // For example, if we compute a commit object based on a certain tip commit, |
| 359 | // then commit that object after another writer commits in between, |
| 360 | // the commit object may be inconsistent against the intervening commit. |
| 361 | // |
| 362 | // We do this update optimistically and ensure this consistency with |
| 363 | // a loop that builds the commit object based on the presumed parent, |
| 364 | // then moves the branch pointer to the new commit but, using a constraint, |
| 365 | // only succeeds when the presumed parent is atomically consistent |
| 366 | // with the branch update. If the contraint, fails will loop a number |
| 367 | // of times till it succeeds, or we give up. |
| 368 | for retries := range maxCommitRetries { |
| 369 | config, err := b.pool.branches.LookupByName(ctx, b.Name) |
| 370 | if err != nil { |
| 371 | return ksuid.Nil, err |
| 372 | } |
| 373 | object, err := create(config, retries) |
| 374 | if err != nil { |
| 375 | return ksuid.Nil, err |
| 376 | } |
| 377 | if err := b.pool.commits.Put(ctx, object); err != nil { |
| 378 | return ksuid.Nil, fmt.Errorf("branch %q failed to write commit object: %w", b.Name, err) |
| 379 | } |
| 380 | // Set the branch pointer to point to this commit object |
| 381 | // and stash the current commit (that will become the parent) |
| 382 | // in a local for the constraint check closure. |
| 383 | parent := config.Commit |
| 384 | config.Commit = object.Commit |
| 385 | parentCheck := func(e journal.Entry) bool { |
| 386 | if entry, ok := e.(*branches.Config); ok { |
| 387 | return entry.Commit == parent |
| 388 | } |
| 389 | return false |
| 390 | } |
| 391 | if err := b.pool.branches.Update(ctx, config, parentCheck); err != nil { |
| 392 | // Branch update failed so remove commit. |
| 393 | rmerr := b.pool.commits.Remove(ctx, object) |
| 394 | if err == journal.ErrConstraint { |
| 395 | // Parent check failed so try again. |
| 396 | if rmerr != nil { |
| 397 | return ksuid.Nil, rmerr |
| 398 | } |
| 399 | continue |
| 400 | } |
| 401 | return ksuid.Nil, err |
| 402 | } |
| 403 | return object.Commit, nil |
| 404 | } |
| 405 | return ksuid.Nil, fmt.Errorf("branch %q: %w", b.Name, ErrCommitFailed) |
| 406 | } |
| 407 | |
| 408 | func (b *Branch) LookupTags(ctx context.Context, tags []ksuid.KSUID) ([]ksuid.KSUID, error) { |
| 409 | var ids []ksuid.KSUID |
no test coverage detected