Skip to content

Commit 94a11de

Browse files
authored
Merge pull request #9142 from dolthub/nicktobey/create_commit
Add plumbing command for manually creating commit
2 parents 2b096a7 + bab5647 commit 94a11de

File tree

10 files changed

+348
-3
lines changed

10 files changed

+348
-3
lines changed

go/cmd/dolt/cli/arg_parser_helpers.go

+13
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,19 @@ func CreateReflogArgParser() *argparser.ArgParser {
313313
return ap
314314
}
315315

316+
func CreateCreateCommitParser() *argparser.ArgParser {
317+
ap := argparser.NewArgParserWithMaxArgs("createchunk commit", 0)
318+
ap.SupportsString(AuthorParam, "", "author", "Specify an explicit author using the standard A U Thor {{.LessThan}}author@example.com{{.GreaterThan}} format.")
319+
ap.SupportsString("desc", "", "commit description", "the description in the commit")
320+
321+
ap.SupportsFlag(ForceFlag, "", "when used alongside --branch, allows overwriting an existing branch")
322+
ap.SupportsRequiredString("root", "", "database root", "the root hash of the database at this commit")
323+
ap.SupportsStringList("parents", "", "parent commits", "a list of the commit hashes of the parent commit")
324+
ap.SupportsString(BranchParam, "", "ref to assign to", "if set, the new commit will be reachable at this ref")
325+
326+
return ap
327+
}
328+
316329
func CreateGlobalArgParser(name string) *argparser.ArgParser {
317330
ap := argparser.NewArgParserWithVariableArgs(name)
318331
if name == "dolt" {

go/cmd/dolt/commands/admin/admin.go

+2
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ package admin
1616

1717
import (
1818
"github.com/dolthub/dolt/go/cmd/dolt/cli"
19+
"github.com/dolthub/dolt/go/cmd/dolt/commands/admin/createchunk"
1920
)
2021

2122
var Commands = cli.NewHiddenSubCommandHandler("admin", "Commands for directly working with Dolt storage for purposes of testing or database recovery", []cli.Command{
2223
SetRefCmd{},
2324
ShowRootCmd{},
2425
ZstdCmd{},
2526
StorageCmd{},
27+
createchunk.Commands,
2628
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2025 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 createchunk
16+
17+
import (
18+
"github.com/dolthub/dolt/go/cmd/dolt/cli"
19+
)
20+
21+
var Commands = cli.NewHiddenSubCommandHandler("createchunk", "Low-level commands for creating chunks", []cli.Command{
22+
CreateCommitCmd{},
23+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// Copyright 2025 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 createchunk
16+
17+
import (
18+
"context"
19+
"errors"
20+
21+
"github.com/dolthub/dolt/go/cmd/dolt/cli"
22+
"github.com/dolthub/dolt/go/cmd/dolt/errhand"
23+
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
24+
"github.com/dolthub/dolt/go/libraries/doltcore/env"
25+
"github.com/dolthub/dolt/go/libraries/doltcore/ref"
26+
"github.com/dolthub/dolt/go/libraries/utils/argparser"
27+
"github.com/dolthub/dolt/go/store/datas"
28+
"github.com/dolthub/dolt/go/store/hash"
29+
)
30+
31+
// CreateCommitCmd creates a new commit chunk, printing the new chunk's hash on success.
32+
// The user must supply a branch name, which will be set to this new commit.
33+
// This is only required for the CLI command, and is optional when invoking the equivalent stored procedure.
34+
// This is because the journal must end with a root hash, and is only flushed when there is a new root hash.
35+
// Thus, we must update the root hash before the command finishes, or else changes will not be persisted.
36+
type CreateCommitCmd struct{}
37+
38+
// Name is returns the name of the Dolt cli command. This is what is used on the command line to invoke the command
39+
func (cmd CreateCommitCmd) Name() string {
40+
return "commit"
41+
}
42+
43+
// Description returns a description of the command
44+
func (cmd CreateCommitCmd) Description() string {
45+
return "Creates a new commit chunk in the dolt storage"
46+
}
47+
48+
// RequiresRepo should return false if this interface is implemented, and the command does not have the requirement
49+
// that it be run from within a data repository directory
50+
func (cmd CreateCommitCmd) RequiresRepo() bool {
51+
return true
52+
}
53+
54+
func (cmd CreateCommitCmd) Docs() *cli.CommandDocumentation {
55+
// Admin commands are undocumented
56+
return nil
57+
}
58+
59+
func (cmd CreateCommitCmd) ArgParser() *argparser.ArgParser {
60+
return cli.CreateCreateCommitParser()
61+
}
62+
63+
func (cmd CreateCommitCmd) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv, cliCtx cli.CliContext) int {
64+
ap := cmd.ArgParser()
65+
usage, _ := cli.HelpAndUsagePrinters(cli.CommandDocsForCommandString(commandStr, cli.CommandDocumentationContent{}, ap))
66+
67+
// Ensure that the CLI args parse, but only check that a branch was supplied.
68+
// All other args will be validated in the system procedure, but the branch is only required in the CLI.
69+
apr := cli.ParseArgsOrDie(ap, args, usage)
70+
if !apr.Contains(cli.BranchParam) {
71+
cli.PrintErrf("the --%s flag is required when creating a chunk using the CLI", cli.BranchParam)
72+
return 1
73+
}
74+
75+
desc, _ := apr.GetValue("desc")
76+
root, _ := apr.GetValue("root")
77+
parents, _ := apr.GetValueList("parents")
78+
branch, isBranchSet := apr.GetValue(cli.BranchParam)
79+
force := apr.Contains(cli.ForceFlag)
80+
81+
var name, email string
82+
var err error
83+
if authorStr, ok := apr.GetValue(cli.AuthorParam); ok {
84+
name, email, err = cli.ParseAuthor(authorStr)
85+
if err != nil {
86+
cli.PrintErrln(errhand.VerboseErrorFromError(err))
87+
return 1
88+
}
89+
} else {
90+
name, email, err = env.GetNameAndEmail(cliCtx.Config())
91+
if err != nil {
92+
cli.PrintErrln(errhand.VerboseErrorFromError(err))
93+
return 1
94+
}
95+
}
96+
97+
db := dEnv.DbData(ctx).Ddb
98+
commitRootHash, ok := hash.MaybeParse(root)
99+
if !ok {
100+
cli.PrintErrf("invalid root value hash")
101+
return 1
102+
}
103+
104+
var parentCommits []hash.Hash
105+
for _, parent := range parents {
106+
commitSpec, err := doltdb.NewCommitSpec(parent)
107+
if err != nil {
108+
cli.PrintErrln(errhand.VerboseErrorFromError(err))
109+
return 1
110+
}
111+
112+
headRef := dEnv.RepoState.CWBHeadRef()
113+
114+
optionalCommit, err := db.Resolve(ctx, commitSpec, headRef)
115+
if err != nil {
116+
cli.PrintErrln(errhand.VerboseErrorFromError(err))
117+
return 1
118+
}
119+
parentCommits = append(parentCommits, optionalCommit.Addr)
120+
}
121+
122+
commitMeta, err := datas.NewCommitMeta(name, email, desc)
123+
if err != nil {
124+
cli.PrintErrln(errhand.VerboseErrorFromError(err))
125+
return 1
126+
}
127+
128+
// 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)
129+
commitOpts := datas.CommitOptions{
130+
Parents: parentCommits,
131+
Meta: commitMeta,
132+
Amend: force,
133+
}
134+
135+
rootVal, err := db.ValueReadWriter().ReadValue(ctx, commitRootHash)
136+
if err != nil {
137+
cli.PrintErrln(errhand.VerboseErrorFromError(err))
138+
return 1
139+
}
140+
141+
var commit *doltdb.Commit
142+
if isBranchSet {
143+
commit, err = db.CommitValue(ctx, ref.NewBranchRef(branch), rootVal, commitOpts)
144+
if errors.Is(err, datas.ErrMergeNeeded) {
145+
cli.PrintErrf("branch %s already exists. If you wish to overwrite it, add the --force flag", branch)
146+
return 1
147+
}
148+
} else {
149+
commit, err = db.CommitDangling(ctx, rootVal, commitOpts)
150+
}
151+
if err != nil {
152+
cli.PrintErrln(errhand.VerboseErrorFromError(err))
153+
return 1
154+
}
155+
156+
commitHash, err := commit.HashOf()
157+
if err != nil {
158+
cli.PrintErrln(errhand.VerboseErrorFromError(err))
159+
return 1
160+
}
161+
162+
cli.Println(commitHash.String())
163+
164+
return 0
165+
}

go/cmd/dolt/dolt.go

-2
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ import (
4242

4343
"github.com/dolthub/dolt/go/cmd/dolt/cli"
4444
"github.com/dolthub/dolt/go/cmd/dolt/commands"
45-
"github.com/dolthub/dolt/go/cmd/dolt/commands/admin"
4645
"github.com/dolthub/dolt/go/cmd/dolt/commands/ci"
4746
"github.com/dolthub/dolt/go/cmd/dolt/commands/credcmds"
4847
"github.com/dolthub/dolt/go/cmd/dolt/commands/cvcmds"
@@ -71,7 +70,6 @@ var dumpDocsCommand = &commands.DumpDocsCmd{}
7170
var dumpZshCommand = &commands.GenZshCompCmd{}
7271

7372
var commandsWithoutCliCtx = []cli.Command{
74-
admin.Commands,
7573
commands.CloneCmd{},
7674
commands.BackupCmd{},
7775
commands.LoginCmd{},

go/libraries/utils/argparser/option.go

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const (
2525
OptionalFlag OptionType = iota
2626
OptionalValue
2727
OptionalEmptyValue
28+
RequiredValue
2829
)
2930

3031
type ValidationFunc func(string) error

go/libraries/utils/argparser/parser.go

+17-1
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,14 @@ func (ap *ArgParser) SupportsString(name, abbrev, valDesc, desc string) *ArgPars
141141
return ap
142142
}
143143

144+
// SupportsRequiredString adds support for a new required string argument with the description given. See SupportOpt for details on params.
145+
func (ap *ArgParser) SupportsRequiredString(name, abbrev, valDesc, desc string) *ArgParser {
146+
opt := &Option{name, abbrev, valDesc, RequiredValue, desc, nil, false}
147+
ap.SupportOption(opt)
148+
149+
return ap
150+
}
151+
144152
// SupportsStringList adds support for a new string list argument with the description given. See SupportOpt for details on params.
145153
func (ap *ArgParser) SupportsStringList(name, abbrev, valDesc, desc string) *ArgParser {
146154
opt := &Option{name, abbrev, valDesc, OptionalValue, desc, nil, true}
@@ -240,7 +248,7 @@ func (ap *ArgParser) matchModalOptions(arg string) (matches []*Option, rest stri
240248
func (ap *ArgParser) sortedValueOptions() []string {
241249
vos := make([]string, 0, len(ap.Supported))
242250
for s, opt := range ap.nameOrAbbrevToOpt {
243-
if (opt.OptType == OptionalValue || opt.OptType == OptionalEmptyValue) && s != "" {
251+
if (opt.OptType == OptionalValue || opt.OptType == OptionalEmptyValue || opt.OptType == RequiredValue) && s != "" {
244252
vos = append(vos, s)
245253
}
246254
}
@@ -341,6 +349,14 @@ func (ap *ArgParser) Parse(args []string) (*ArgParseResults, error) {
341349
return nil, ap.TooManyArgsErrorFunc(positionalArgs)
342350
}
343351

352+
for _, option := range ap.Supported {
353+
if option.OptType == RequiredValue {
354+
if _, ok := namedArgs[option.Name]; !ok {
355+
return nil, fmt.Errorf("option '%s' is required", option.Name)
356+
}
357+
}
358+
}
359+
344360
return &ArgParseResults{namedArgs, positionalArgs, ap, positionalArgsSeparatorIndex}, nil
345361
}
346362

go/libraries/utils/argparser/results.go

+3
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ func (res *ArgParseResults) GetValue(name string) (string, bool) {
112112

113113
func (res *ArgParseResults) GetValueList(name string) ([]string, bool) {
114114
val, ok := res.options[name]
115+
if !ok {
116+
return nil, false
117+
}
115118
return strings.Split(val, ","), ok
116119
}
117120

0 commit comments

Comments
 (0)