From 2d3105eec016e971824a676058f4dd5f8fce3fef Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Mon, 27 Jan 2025 10:10:49 -0800 Subject: [PATCH 1/5] Add the dirty column to the dolt_branches system table --- .../doltcore/sqle/dtables/branches_table.go | 59 ++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/go/libraries/doltcore/sqle/dtables/branches_table.go b/go/libraries/doltcore/sqle/dtables/branches_table.go index a343d825d0b..291f97d60cf 100644 --- a/go/libraries/doltcore/sqle/dtables/branches_table.go +++ b/go/libraries/doltcore/sqle/dtables/branches_table.go @@ -18,6 +18,7 @@ import ( "fmt" "io" + "github.com/dolthub/dolt/go/store/hash" "github.com/dolthub/go-mysql-server/sql" "github.com/dolthub/go-mysql-server/sql/types" @@ -90,6 +91,7 @@ func (bt *BranchesTable) Schema() sql.Schema { if !bt.remote { columns = append(columns, &sql.Column{Name: "remote", Type: types.Text, Source: bt.tableName, PrimaryKey: false, Nullable: true}) columns = append(columns, &sql.Column{Name: "branch", Type: types.Text, Source: bt.tableName, PrimaryKey: false, Nullable: true}) + columns = append(columns, &sql.Column{Name: "dirty", Type: types.Boolean, Source: bt.tableName, PrimaryKey: false, Nullable: true}) } return columns } @@ -114,6 +116,7 @@ type BranchItr struct { table *BranchesTable branches []string commits []*doltdb.Commit + dirty []bool idx int } @@ -145,19 +148,28 @@ func NewBranchItr(ctx *sql.Context, table *BranchesTable) (*BranchItr, error) { branchNames := make([]string, len(branchRefs)) commits := make([]*doltdb.Commit, len(branchRefs)) + dirtyBits := make([]bool, len(branchRefs)) for i, branch := range branchRefs { commit, err := ddb.ResolveCommitRefAtRoot(ctx, branch, txRoot) - if err != nil { return nil, err } + var dirty bool + if !remote { + dirty, err = isDirty(ctx, ddb, commit, branch, txRoot) + if err != nil { + return nil, err + } + } + if branch.GetType() == ref.RemoteRefType { branchNames[i] = "remotes/" + branch.GetPath() } else { branchNames[i] = branch.GetPath() } + dirtyBits[i] = dirty commits[i] = commit } @@ -165,6 +177,7 @@ func NewBranchItr(ctx *sql.Context, table *BranchesTable) (*BranchItr, error) { table: table, branches: branchNames, commits: commits, + dirty: dirtyBits, idx: 0, }, nil } @@ -182,6 +195,7 @@ func (itr *BranchItr) Next(ctx *sql.Context) (sql.Row, error) { name := itr.branches[itr.idx] cm := itr.commits[itr.idx] + dirty := itr.dirty[itr.idx] meta, err := cm.GetCommitMeta(ctx) if err != nil { @@ -211,8 +225,49 @@ func (itr *BranchItr) Next(ctx *sql.Context) (sql.Row, error) { remoteName = branch.Remote branchName = branch.Merge.Ref.GetPath() } - return sql.NewRow(name, h.String(), meta.Name, meta.Email, meta.Time(), meta.Description, remoteName, branchName), nil + return sql.NewRow(name, h.String(), meta.Name, meta.Email, meta.Time(), meta.Description, remoteName, branchName, dirty), nil + } +} + +// isDirty returns true if the working ref points to a dirty branch. +func isDirty(ctx *sql.Context, ddb *doltdb.DoltDB, commit *doltdb.Commit, branch ref.DoltRef, txRoot hash.Hash) (bool, error) { + wsRef, err := ref.WorkingSetRefForHead(branch) + if err != nil { + return false, err + } + ws, err := ddb.ResolveWorkingSetAtRoot(ctx, wsRef, txRoot) + if err != nil { + return false, err + } + + workingRoot := ws.WorkingRoot() + workingRootHash, err := workingRoot.HashOf() + if err != nil { + return false, err + } + stagedRoot := ws.StagedRoot() + stagedRootHash, err := stagedRoot.HashOf() + if err != nil { + return false, err + } + + dirty := false + if workingRootHash != stagedRootHash { + dirty = true + } else { + cmRt, err := commit.GetRootValue(ctx) + if err != nil { + return false, err + } + cmRtHash, err := cmRt.HashOf() + if err != nil { + return false, err + } + if cmRtHash != workingRootHash { + dirty = true + } } + return dirty, nil } // Close closes the iterator. From 809be27d65db315ebacec0f4a8c3488a796c841e Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Mon, 27 Jan 2025 11:22:02 -0800 Subject: [PATCH 2/5] Tests for the dolt_branches dirty column --- go/libraries/doltcore/sqle/sqlselect_test.go | 2 + go/libraries/doltcore/sqle/testutil.go | 2 +- integration-tests/bats/sql-server.bats | 42 +++++++++++++++++++- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/go/libraries/doltcore/sqle/sqlselect_test.go b/go/libraries/doltcore/sqle/sqlselect_test.go index 7f8fc08464d..d065a84f0bb 100644 --- a/go/libraries/doltcore/sqle/sqlselect_test.go +++ b/go/libraries/doltcore/sqle/sqlselect_test.go @@ -784,6 +784,7 @@ func BasicSelectTests() []SelectTest { "Initialize data repository", "", "", + true, // Test setup has a dirty workspace. }, }, ExpectedSqlSchema: sql.Schema{ @@ -795,6 +796,7 @@ func BasicSelectTests() []SelectTest { &sql.Column{Name: "latest_commit_message", Type: gmstypes.Text}, &sql.Column{Name: "remote", Type: gmstypes.Text}, &sql.Column{Name: "branch", Type: gmstypes.Text}, + &sql.Column{Name: "dirty", Type: gmstypes.Boolean}, }, }, } diff --git a/go/libraries/doltcore/sqle/testutil.go b/go/libraries/doltcore/sqle/testutil.go index ee4ad010714..5379fbc77f3 100644 --- a/go/libraries/doltcore/sqle/testutil.go +++ b/go/libraries/doltcore/sqle/testutil.go @@ -452,7 +452,7 @@ func CreateEmptyTestTable(dEnv *env.DoltEnv, tableName string, sch schema.Schema return dEnv.UpdateWorkingRoot(ctx, newRoot) } -// CreateTestDatabase creates a test database with the test data set in it. +// CreateTestDatabase creates a test database with the test data set in it. Has a dirty workspace as well. func CreateTestDatabase() (*env.DoltEnv, error) { ctx := context.Background() dEnv, err := CreateEmptyTestDatabase() diff --git a/integration-tests/bats/sql-server.bats b/integration-tests/bats/sql-server.bats index ce6373a6b30..2f06c100ff5 100644 --- a/integration-tests/bats/sql-server.bats +++ b/integration-tests/bats/sql-server.bats @@ -1996,4 +1996,44 @@ EOF run dolt --data-dir datadir1 sql-server --data-dir datadir2 [ $status -eq 1 ] [[ "$output" =~ "cannot specify both global --data-dir argument and --data-dir in sql-server config" ]] || false -} \ No newline at end of file +} + +# This is really a test of the dolt_Branches system table, but due to needing a server with multiple dirty branches +# it was easier to test it with a sql-server. +@test "sql-server: dirty branches listed properly in dolt_branches table" { + skiponwindows "Missing dependencies" + + cd repo1 + dolt checkout main + dolt branch br1 # Will be a clean commit, ahead of main. + dolt branch br2 # will be a dirty branch, on main. + dolt branch br3 # will be a dirty branch, on br1 + start_sql_server repo1 + + dolt --use-db "repo1" --branch br1 sql -q "CREATE TABLE tbl (i int primary key)" + dolt --use-db "repo1" --branch br1 sql -q "CALL DOLT_COMMIT('-Am', 'commit it')" + + dolt --use-db "repo1" --branch br2 sql -q "CREATE TABLE tbl (j int primary key)" + + # Fast forward br3 to br1, then make it dirty. + dolt --use-db "repo1" --branch br3 sql -q "CALL DOLT_MERGE('br1')" + dolt --use-db "repo1" --branch br3 sql -q "CREATE TABLE othertbl (k int primary key)" + + stop_sql_server 1 && sleep 0.5 + + run dolt sql -q "SELECT name,dirty FROM dolt_branches" + [ "$status" -eq 0 ] + [[ "$output" =~ "br1 | false" ]] || false + [[ "$output" =~ "br2 | true " ]] || false + [[ "$output" =~ "br3 | true" ]] || false + [[ "$output" =~ "main | false" ]] || false + + # Verify that the dolt_branches table show the same output, regardless of the checked out branch. + dolt checkout br1 + run dolt sql -q "SELECT name,dirty FROM dolt_branches" + [ "$status" -eq 0 ] + [[ "$output" =~ "br1 | false" ]] || false + [[ "$output" =~ "br2 | true " ]] || false + [[ "$output" =~ "br3 | true" ]] || false + [[ "$output" =~ "main | false" ]] || false +} From 9da62ebc289041bbc0675c53a71c6f8e9171f383 Mon Sep 17 00:00:00 2001 From: macneale4 Date: Mon, 27 Jan 2025 19:31:31 +0000 Subject: [PATCH 3/5] [ga-format-pr] Run go/utils/repofmt/format_repo.sh and go/Godeps/update.sh --- go/libraries/doltcore/sqle/dtables/branches_table.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/libraries/doltcore/sqle/dtables/branches_table.go b/go/libraries/doltcore/sqle/dtables/branches_table.go index 291f97d60cf..e332f50510d 100644 --- a/go/libraries/doltcore/sqle/dtables/branches_table.go +++ b/go/libraries/doltcore/sqle/dtables/branches_table.go @@ -18,7 +18,6 @@ import ( "fmt" "io" - "github.com/dolthub/dolt/go/store/hash" "github.com/dolthub/go-mysql-server/sql" "github.com/dolthub/go-mysql-server/sql/types" @@ -27,6 +26,7 @@ import ( "github.com/dolthub/dolt/go/libraries/doltcore/schema" "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess" "github.com/dolthub/dolt/go/libraries/doltcore/sqle/index" + "github.com/dolthub/dolt/go/store/hash" ) const branchesDefaultRowCount = 10 From 5bf564220c66990b337dbde1220f30ac1de0c821 Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Mon, 27 Jan 2025 12:15:40 -0800 Subject: [PATCH 4/5] Fix workbench tests to expect dirty column --- .../mysql-client-tests/node/workbenchTests/branches.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/integration-tests/mysql-client-tests/node/workbenchTests/branches.js b/integration-tests/mysql-client-tests/node/workbenchTests/branches.js index 1f0b63b575e..09fb1bdd51c 100644 --- a/integration-tests/mysql-client-tests/node/workbenchTests/branches.js +++ b/integration-tests/mysql-client-tests/node/workbenchTests/branches.js @@ -58,6 +58,7 @@ export const branchTests = [ latest_commit_message: "Initialize data repository", remote: "", branch: "", + dirty: 0, }, { name: "mybranch", @@ -68,6 +69,7 @@ export const branchTests = [ latest_commit_message: "Create table test", remote: "", branch: "", + dirty: 0, }, ], matcher: branchesMatcher, From da7bb5c934c9436a7e1757d3808e547be821e7dd Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Mon, 27 Jan 2025 14:27:25 -0800 Subject: [PATCH 5/5] Don't error out when no workingset is found --- go/libraries/doltcore/sqle/dtables/branches_table.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/go/libraries/doltcore/sqle/dtables/branches_table.go b/go/libraries/doltcore/sqle/dtables/branches_table.go index e332f50510d..1a32fd0e764 100644 --- a/go/libraries/doltcore/sqle/dtables/branches_table.go +++ b/go/libraries/doltcore/sqle/dtables/branches_table.go @@ -15,6 +15,7 @@ package dtables import ( + "errors" "fmt" "io" @@ -237,6 +238,10 @@ func isDirty(ctx *sql.Context, ddb *doltdb.DoltDB, commit *doltdb.Commit, branch } ws, err := ddb.ResolveWorkingSetAtRoot(ctx, wsRef, txRoot) if err != nil { + if errors.Is(err, doltdb.ErrWorkingSetNotFound) { + // If there is no working set for this branch, then it is never dirty. This happens on servers commonly. + return false, nil + } return false, err }