Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the dirty column to the dolt_branches table #8793

Merged
merged 5 commits into from
Jan 27, 2025
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
64 changes: 62 additions & 2 deletions go/libraries/doltcore/sqle/dtables/branches_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package dtables

import (
"errors"
"fmt"
"io"

Expand All @@ -26,6 +27,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
Expand Down Expand Up @@ -90,6 +92,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
}
Expand All @@ -114,6 +117,7 @@ type BranchItr struct {
table *BranchesTable
branches []string
commits []*doltdb.Commit
dirty []bool
idx int
}

Expand Down Expand Up @@ -145,26 +149,36 @@ 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
}

return &BranchItr{
table: table,
branches: branchNames,
commits: commits,
dirty: dirtyBits,
idx: 0,
}, nil
}
Expand All @@ -182,6 +196,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 {
Expand Down Expand Up @@ -211,8 +226,53 @@ 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 {
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
}

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.
Expand Down
2 changes: 2 additions & 0 deletions go/libraries/doltcore/sqle/sqlselect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,7 @@ func BasicSelectTests() []SelectTest {
"Initialize data repository",
"",
"",
true, // Test setup has a dirty workspace.
},
},
ExpectedSqlSchema: sql.Schema{
Expand All @@ -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},
},
},
}
Expand Down
2 changes: 1 addition & 1 deletion go/libraries/doltcore/sqle/testutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
42 changes: 41 additions & 1 deletion integration-tests/bats/sql-server.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

# 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
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const branchTests = [
latest_commit_message: "Initialize data repository",
remote: "",
branch: "",
dirty: 0,
},
{
name: "mybranch",
Expand All @@ -68,6 +69,7 @@ export const branchTests = [
latest_commit_message: "Create table test",
remote: "",
branch: "",
dirty: 0,
},
],
matcher: branchesMatcher,
Expand Down
Loading