Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions go/cmd/dolt/commands/admin/createchunk/createcommit.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,11 @@ func (cmd CreateCommitCmd) Exec(ctx context.Context, commandStr string, args []s
return 1
}

// This isn't technically an amend, but the Amend field controls whether the commit must be a child of the ref's current commit (if any)
// The Force field controls whether the commit must be a child of the ref's current commit (if any).
commitOpts := datas.CommitOptions{
Parents: parentCommits,
Meta: commitMeta,
Amend: force,
Force: force,
}

rootVal, err := db.ValueReadWriter().ReadValue(ctx, commitRootHash)
Expand Down
3 changes: 2 additions & 1 deletion go/cmd/dolt/commands/schcmds/copy-tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
"github.com/dolthub/dolt/go/libraries/utils/argparser"
"github.com/dolthub/dolt/go/store/datas"
"github.com/dolthub/dolt/go/store/hash"
)

var copyTagsDocs = cli.CommandDocumentationContent{
Expand Down Expand Up @@ -277,7 +278,7 @@ func doltCommitUpdatedTags(ctx *sql.Context, tableResolver doltdb.TableResolver,
return err
}

pendingCommit, err := actions.GetCommitStaged(ctx, tableResolver, roots, workingSet, nil, doltDB, commitStagedProps)
pendingCommit, err := actions.GetCommitStaged(ctx, tableResolver, roots, workingSet, nil, hash.Hash{}, doltDB, commitStagedProps)
if err != nil {
return err
}
Expand Down
6 changes: 3 additions & 3 deletions go/libraries/doltcore/doltdb/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,14 +294,14 @@ type PendingCommit struct {
// commit, once written.
// |headRef| is the ref of the HEAD the commit will update
// |mergeParentCommits| are any merge parents for this commit
// |amend| is a flag which indicates that additional parents should not be added to the provided |mergeParentCommits|.
// |amendedCommit|, when not empty, is the address of an existing commit that this commit amends and replaces.
// |cm| is the metadata for the commit
// The current branch head will be automatically filled in as the first parent at commit time.
func (ddb *DoltDB) NewPendingCommit(
ctx context.Context,
roots Roots,
mergeParentCommits []*Commit,
amend bool,
amendedCommit hash.Hash,
cm *datas.CommitMeta,
) (*PendingCommit, error) {
newstaged, val, err := ddb.writeRootValue(ctx, roots.Staged)
Expand All @@ -315,7 +315,7 @@ func (ddb *DoltDB) NewPendingCommit(
parents = append(parents, pc.dCommit.Addr())
}

commitOpts := datas.CommitOptions{Parents: parents, Meta: cm, Amend: amend}
commitOpts := datas.CommitOptions{Parents: parents, Meta: cm, AmendedCommit: amendedCommit}
return &PendingCommit{
Roots: roots,
Val: val,
Expand Down
13 changes: 13 additions & 0 deletions go/libraries/doltcore/doltdb/workingset.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,19 @@ func (m MergeState) IsRevert() bool {
return m.isRevert
}

// OperationName returns the user facing name of the operation this merge state belongs to, one of "merge",
// "cherry-pick", or "revert".
func (m MergeState) OperationName() string {
switch {
case m.isCherryPick:
return "cherry-pick"
case m.isRevert:
return "revert"
default:
return "merge"
}
}

func (m MergeState) PreMergeWorkingRoot() RootValue {
return m.preMergeWorking
}
Expand Down
3 changes: 2 additions & 1 deletion go/libraries/doltcore/dtestutils/testcommands/multienv.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/dolthub/dolt/go/libraries/utils/filesys"
"github.com/dolthub/dolt/go/store/datas"
"github.com/dolthub/dolt/go/store/datas/pull"
"github.com/dolthub/dolt/go/store/hash"
"github.com/dolthub/dolt/go/store/types"
)

Expand Down Expand Up @@ -276,7 +277,7 @@ func (mr *MultiRepoTestSetup) CommitWithWorkingSet(dbName string) *doltdb.Commit
Author: ident,
Committer: ident,
}
pendingCommit, err := actions.GetCommitStaged(ctx, doltdb.SimpleTableResolver{}, roots, ws, mergeParentCommits, dEnv.DbData(ctx).Ddb, commitStagedProps)
pendingCommit, err := actions.GetCommitStaged(ctx, doltdb.SimpleTableResolver{}, roots, ws, mergeParentCommits, hash.Hash{}, dEnv.DbData(ctx).Ddb, commitStagedProps)
if err != nil {
panic("pending commit error: " + err.Error())
}
Expand Down
7 changes: 5 additions & 2 deletions go/libraries/doltcore/env/actions/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/dolthub/dolt/go/libraries/doltcore/diff"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/store/datas"
"github.com/dolthub/dolt/go/store/hash"
)

const CommitVerificationFailedPrefix = "commit verification failed:"
Expand Down Expand Up @@ -73,13 +74,15 @@ func getCommitRunTestGroups() []string {
return nil
}

// GetCommitStaged returns a new pending commit with the roots and commit properties given.
// GetCommitStaged returns a new pending commit with the roots and commit properties given. When props.Amend is
// set, |amendedCommit| must be the address of the commit being amended.
func GetCommitStaged(
ctx *sql.Context,
tableResolver doltdb.TableResolver,
roots doltdb.Roots,
ws *doltdb.WorkingSet,
mergeParents []*doltdb.Commit,
amendedCommit hash.Hash,
db *doltdb.DoltDB,
props CommitStagedProps,
) (*doltdb.PendingCommit, error) {
Expand Down Expand Up @@ -187,7 +190,7 @@ func GetCommitStaged(
return nil, err
}

return db.NewPendingCommit(ctx, roots, mergeParents, props.Amend, commitMeta)
return db.NewPendingCommit(ctx, roots, mergeParents, amendedCommit, commitMeta)
}

// runCommitVerification runs the commit verification tests for the given test groups.
Expand Down
9 changes: 9 additions & 0 deletions go/libraries/doltcore/sqle/dprocedures/dolt_commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ func doDoltCommit(ctx *sql.Context, args []string) (string, bool, error) {

msg, msgOk := apr.GetValue(cli.MessageArg)
amend := apr.Contains(cli.AmendFlag)
if amend {
ws, err := dSess.WorkingSet(ctx, dbName)
if err != nil {
return "", false, err
}
if ws.MergeActive() {
return "", false, fmt.Errorf("you are in the middle of a %s -- cannot amend", ws.MergeState().OperationName())
Comment thread
elianddb marked this conversation as resolved.
}
}
if !msgOk {
if amend {
commit, err := dSess.GetHeadCommit(ctx, dbName)
Expand Down
11 changes: 10 additions & 1 deletion go/libraries/doltcore/sqle/dsess/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -773,11 +773,20 @@ func (d *DoltSession) newPendingCommit(ctx *sql.Context, dbName string, branchSt
}
}

var amendedCommit hash.Hash
if props.Amend {
var err error
amendedCommit, err = headCommit.HashOf()
if err != nil {
return nil, err
}
}

tableResolver, err := GetTableResolver(ctx, dbName)
if err != nil {
return nil, err
}
pendingCommit, err := actions.GetCommitStaged(ctx, tableResolver, roots, branchState.WorkingSet(), mergeParentCommits, branchState.dbData.Ddb, props)
pendingCommit, err := actions.GetCommitStaged(ctx, tableResolver, roots, branchState.WorkingSet(), mergeParentCommits, amendedCommit, branchState.dbData.Ddb, props)
if err != nil {
// Special case for nothing staged, which is not an error
if _, ok := err.(actions.NothingStaged); !ok {
Expand Down
70 changes: 58 additions & 12 deletions go/libraries/doltcore/sqle/dsess/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,11 @@ func doltCommit(ctx *sql.Context,
var rsc doltdb.ReplicationStatusController
newCommit, err := doltDb.CommitWithWorkingSet(ctx, headRef, workingSet.Ref(), &pending, workingSet, currHash, tx.WorkingSetMeta(name, email), &rsc)
WaitForReplicationController(ctx, rsc)
// The check in doCommit can go stale before the ref update, so the storage layer compares the head against
// AmendedCommit once more, atomically with the update. A failure there surfaces the same way.
if err != nil && !pending.CommitOptions.AmendedCommit.IsEmpty() && errors.Is(err, datas.ErrMergeNeeded) {
return nil, nil, tx.rollbackAndErr(ctx, retryTransactionError(err.Error()))
}
return workingSet, newCommit, err
}

Expand Down Expand Up @@ -451,6 +456,12 @@ func (tx *DoltTransaction) doCommit(
return nil, nil, err
}

// Checked before the working set merge so that a stale amend reports the moved head rather
// than a data conflict.
if err := tx.validateAmendedHead(ctx, startPoint.db, workingSet, commit); err != nil {
return nil, nil, err
}

if newWorkingSet || workingAndStagedEqual(existingWs, startState) {
// ff merge
err = tx.validateWorkingSetForCommit(ctx, workingSet, isFfMerge)
Expand Down Expand Up @@ -558,6 +569,51 @@ func (tx *DoltTransaction) rollback(ctx *sql.Context) error {
return nil
}

// rollbackAndErr rolls the transaction back and returns |err|, or the rollback error when rolling back fails.
func (tx *DoltTransaction) rollbackAndErr(ctx *sql.Context, err error) error {
if rollbackErr := tx.rollback(ctx); rollbackErr != nil {
return rollbackErr
}
return err
}

// retryTransactionError returns the client facing serialization failure for a transaction that conflicts with a
// committed transaction. A non empty |detail| is placed before the retry advice.
func retryTransactionError(detail string) error {
if detail == "" {
return sql.ErrLockDeadlock.New(ErrRetryTransaction.Error())
}
return sql.ErrLockDeadlock.New(fmt.Sprintf("%s: %s", detail, ErrRetryTransaction.Error()))
}

// validateAmendedHead returns a retryable error when |pending| amends a commit that is no longer the head of the
// branch that |workingSet| belongs to. The transaction is rolled back before the error is returned. A nil
// |pending| or an ordinary commit passes without any check.
func (tx *DoltTransaction) validateAmendedHead(ctx *sql.Context, doltDb *doltdb.DoltDB, workingSet *doltdb.WorkingSet, pending *doltdb.PendingCommit) error {
if pending == nil {
return nil
}
amended := pending.CommitOptions.AmendedCommit
if amended.IsEmpty() {
return nil
}

headRef, err := workingSet.Ref().ToHeadRef()
if err != nil {
return err
}
curHeadAddr, err := doltDb.GetHashForRefStr(ctx, headRef.String())
if err != nil {
return err
}
if *curHeadAddr != amended {
Comment thread
elianddb marked this conversation as resolved.
return tx.rollbackAndErr(ctx, retryTransactionError(fmt.Sprintf(
"cannot amend head of branch '%s': is at %s but expected %s",
headRef.GetPath(), *curHeadAddr, amended)))
}
return nil
}

type ffMerge bool

const (
Expand Down Expand Up @@ -624,12 +680,7 @@ func (tx *DoltTransaction) validateWorkingSetForCommit(ctx *sql.Context, working
// Conflicts are never acceptable when they resulted from a merge with the existing working set -- it's equivalent
// to hitting a write lock (which we didn't take). Always roll back and return an error in this case.
if !isFf {
rollbackErr := tx.rollback(ctx)
if rollbackErr != nil {
return rollbackErr
}

return sql.ErrLockDeadlock.New(ErrRetryTransaction.Error())
return tx.rollbackAndErr(ctx, retryTransactionError(""))
}

// If there were conflicts before merge with the persisted working set, whether we allow it to be committed is a
Expand Down Expand Up @@ -769,12 +820,7 @@ func (tx *DoltTransaction) validateWorkingSetForCommit(ctx *sql.Context, working
}
}

rollbackErr := tx.rollback(ctx)
if rollbackErr != nil {
return rollbackErr
}

return fmt.Errorf("%s%s%s", ErrUnresolvedConstraintViolationsCommit, ConstraintViolationsListPrefix, messageBuilder.String())
return tx.rollbackAndErr(ctx, fmt.Errorf("%s%s%s", ErrUnresolvedConstraintViolationsCommit, ConstraintViolationsListPrefix, messageBuilder.String()))
}
}

Expand Down
48 changes: 48 additions & 0 deletions go/libraries/doltcore/sqle/enginetest/dolt_queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -8262,6 +8262,54 @@ var DoltCommitTests = []queries.ScriptTest{
},
},
},
{
// See https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---amend
Name: "CALL DOLT_COMMIT with --amend fails while a merge or cherry-pick is in progress",
SetUpScript: []string{
"drop table if exists invalidFK",
"set @@dolt_allow_commit_conflicts = 1",
"create table amend_merge_t (pk int primary key, c int)",
"insert into amend_merge_t values (1, 1)",
"call dolt_commit('-Am', 'base')",
"call dolt_checkout('-b', 'other')",
"update amend_merge_t set c = 2 where pk = 1",
"call dolt_commit('-am', 'other change')",
"call dolt_checkout('main')",
"update amend_merge_t set c = 3 where pk = 1",
"call dolt_commit('-am', 'main change')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "call dolt_merge('other')",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
},
{
Query: "call dolt_commit('-a', '--amend', '-m', 'amend during merge')",
ExpectedErrStr: "you are in the middle of a merge -- cannot amend",
},
{
Query: "select count(*) from dolt_conflicts_amend_merge_t",
// The merge state and its conflicts are untouched by the rejected amend
Expected: []sql.Row{{1}},
},
{
Query: "call dolt_merge('--abort')",
Expected: []sql.Row{{"", 0, 0, "merge aborted"}},
},
{
Query: "call dolt_cherry_pick(dolt_hashof('other'))",
Expected: []sql.Row{{"", 1, 0, 0}},
},
{
Query: "call dolt_commit('-a', '--amend', '-m', 'amend during cherry-pick')",
ExpectedErrStr: "you are in the middle of a cherry-pick -- cannot amend",
},
{
Query: "call dolt_cherry_pick('--abort')",
Expected: []sql.Row{{"", 0, 0, 0}},
},
},
},
}

var DoltIndexPrefixScripts = []queries.ScriptTest{
Expand Down
Loading
Loading