项目文件夹

文件
wehub-resource-sync 5357c39144
Fuzzer / Run Fuzzer (push) Has been cancelled
Race tests / Go race tests (ubuntu-22.04) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:01:40 +08:00

676 行
22 KiB
Go

// Copyright 2019 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"sort"
"strings"
"github.com/dolthub/go-mysql-server/sql"
"github.com/fatih/color"
"github.com/gocraft/dbr/v2"
"github.com/gocraft/dbr/v2/dialect"
"github.com/dolthub/dolt/go/cmd/dolt/cli"
"github.com/dolthub/dolt/go/cmd/dolt/errhand"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
"github.com/dolthub/dolt/go/libraries/doltcore/ref"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlfmt"
"github.com/dolthub/dolt/go/libraries/utils/argparser"
"github.com/dolthub/dolt/go/libraries/utils/set"
eventsapi "github.com/dolthub/eventsapi_schema/dolt/services/eventsapi/v1alpha1"
)
var branchDocs = cli.CommandDocumentationContent{
ShortDesc: `List, create, or delete branches`,
LongDesc: `If {{.EmphasisLeft}}--list{{.EmphasisRight}} is given, or if there are no non-option arguments, existing branches are listed. The current branch will be highlighted with an asterisk. With no options, only local branches are listed. With {{.EmphasisLeft}}-r{{.EmphasisRight}}, only remote branches are listed. With {{.EmphasisLeft}}-a{{.EmphasisRight}} both local and remote branches are listed. {{.EmphasisLeft}}-v{{.EmphasisRight}} causes the hash of the commit that the branches are at to be printed as well.
The command's second form creates a new branch head named {{.LessThan}}branchname{{.GreaterThan}} which points to the current {{.EmphasisLeft}}HEAD{{.EmphasisRight}}, or {{.LessThan}}start-point{{.GreaterThan}} if given.
Note that this will create the new branch, but it will not switch the working tree to it; use {{.EmphasisLeft}}dolt checkout <newbranch>{{.EmphasisRight}} to switch to the new branch.
With a {{.EmphasisLeft}}-m{{.EmphasisRight}}, {{.LessThan}}oldbranch{{.GreaterThan}} will be renamed to {{.LessThan}}newbranch{{.GreaterThan}}. If {{.LessThan}}newbranch{{.GreaterThan}} exists, -f must be used to force the rename to happen.
The {{.EmphasisLeft}}-c{{.EmphasisRight}} options have the exact same semantics as {{.EmphasisLeft}}-m{{.EmphasisRight}}, except instead of the branch being renamed it will be copied to a new name.
With a {{.EmphasisLeft}}-d{{.EmphasisRight}}, {{.LessThan}}branchname{{.GreaterThan}} will be deleted. You may specify more than one branch for deletion.`,
Synopsis: []string{
`[--list] [-v] [-a] [-r]`,
`[-f] {{.LessThan}}branchname{{.GreaterThan}} [{{.LessThan}}start-point{{.GreaterThan}}]`,
`-m [-f] [{{.LessThan}}oldbranch{{.GreaterThan}}] {{.LessThan}}newbranch{{.GreaterThan}}`,
`-c [-f] [{{.LessThan}}oldbranch{{.GreaterThan}}] {{.LessThan}}newbranch{{.GreaterThan}}`,
`-d [-f] [-r] {{.LessThan}}branchname{{.GreaterThan}}...`,
},
}
const (
datasetsFlag = "datasets"
showCurrentFlag = "show-current"
)
type BranchCmd struct{}
// Name is returns the name of the Dolt cli command. This is what is used on the command line to invoke the command
func (cmd BranchCmd) Name() string {
return "branch"
}
// Description returns a description of the command
func (cmd BranchCmd) Description() string {
return "Create, list, edit, delete branches."
}
func (cmd BranchCmd) Docs() *cli.CommandDocumentation {
ap := cmd.ArgParser()
return cli.NewCommandDocumentation(branchDocs, ap)
}
func (cmd BranchCmd) ArgParser() *argparser.ArgParser {
// CreateBranchArgParser has the common flags for the command line and the stored procedure.
// But only the command line has flags for printing branches. We define those here.
ap := cli.CreateBranchArgParser()
ap.ArgListHelp = append(ap.ArgListHelp, [2]string{"start-point", "A commit that a new branch should point at."})
ap.SupportsFlag(cli.ListFlag, "", "List branches")
ap.SupportsRepeatableFlag(cli.VerboseFlag, "v", "When in list mode, show the hash and commit subject line for each head, along with relationship to upstream branch (if any). If given twice, print the name of the upstream branch as well")
ap.SupportsFlag(cli.AllFlag, "a", "When in list mode, shows remote tracked branches")
ap.SupportsFlag(datasetsFlag, "", "List all datasets in the database")
ap.SupportsFlag(cli.RemoteParam, "r", "When in list mode, show only remote tracked branches. When with -d, delete a remote tracking branch.")
ap.SupportsFlag(showCurrentFlag, "", "Print the name of the current branch")
return ap
}
// EventType returns the type of the event to log
func (cmd BranchCmd) EventType() eventsapi.ClientEventType {
return eventsapi.ClientEventType_BRANCH
}
// Exec executes the command
func (cmd BranchCmd) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv, cliCtx cli.CliContext) int {
ap := cmd.ArgParser()
apr, usage, terminate, status := ParseArgsOrPrintHelp(ap, commandStr, args, branchDocs)
if terminate {
return status
}
errorBuilder := errhand.BuildDError("error: failed to create query engine")
queryist, err := cliCtx.QueryEngine(ctx)
if err != nil {
return HandleVErrAndExitCode(errorBuilder.AddCause(err).Build(), nil)
}
if len(apr.ContainsMany(cli.MoveFlag, cli.CopyFlag, cli.DeleteFlag, cli.DeleteForceFlag, cli.ListFlag, showCurrentFlag)) > 1 {
cli.PrintErrln("Must specify exactly one of --move/-m, --copy/-c, --delete/-d, -D, --show-current, or --list.")
return 1
}
switch {
case apr.Contains(cli.MoveFlag):
return moveBranch(queryist.Context, queryist.Queryist, apr, args, usage)
case apr.Contains(cli.CopyFlag):
return copyBranch(queryist.Context, queryist.Queryist, apr, args, usage)
case apr.Contains(cli.DeleteFlag):
return deleteBranches(queryist.Context, queryist.Queryist, apr, args, usage)
case apr.Contains(cli.DeleteForceFlag):
return deleteBranches(queryist.Context, queryist.Queryist, apr, args, usage)
case apr.Contains(cli.ListFlag):
return printBranches(queryist.Context, queryist.Queryist, apr, usage)
case apr.Contains(showCurrentFlag):
return printCurrentBranch(queryist.Context, queryist.Queryist)
case apr.Contains(datasetsFlag):
return printAllDatasets(queryist.Context, dEnv)
case apr.ContainsAny(cli.SetUpstreamToFlag, cli.TrackFlag):
return updateUpstream(queryist.Context, queryist.Queryist, apr, args)
case apr.NArg() > 0:
return createBranch(queryist.Context, queryist.Queryist, apr, args, usage)
default:
return printBranches(queryist.Context, queryist.Queryist, apr, usage)
}
}
type branchMeta struct {
name string
hash string
remote bool
upstream string
aheadCount uint
behindCount uint
message string
}
func getBranches(sqlCtx *sql.Context, queryEngine cli.Queryist, printRemote, printAll bool) ([]branchMeta, error) {
qry := "SELECT name, hash, remote, branch, latest_commit_message FROM dolt_branches"
schema, rowIter, _, err := queryEngine.Query(sqlCtx, qry)
if err != nil {
return nil, err
}
var branches []branchMeta
if printAll || !printRemote {
for {
row, err := rowIter.Next(sqlCtx)
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
if len(row) != 5 {
return nil, fmt.Errorf("unexpectedly received wrong column count '%s': %s", qry, row)
}
rowStrings, err := sqlfmt.SqlRowAsStrings(sqlCtx, row, schema)
if err != nil {
return nil, err
}
name := rowStrings[0]
hash := rowStrings[1]
remote := rowStrings[2]
branch := rowStrings[3]
commitMessage := rowStrings[4]
upstream := ""
if remote != "" && branch != "" {
upstream = remote + "/" + branch
}
branchM := branchMeta{
name: name,
hash: hash,
remote: false,
upstream: upstream,
message: commitMessage,
}
branches = append(branches, branchM)
}
}
if printRemote || printAll {
qry = "SELECT name, hash, latest_commit_message from dolt_remote_branches"
schema, rowIter, _, err = queryEngine.Query(sqlCtx, qry)
if err != nil {
return nil, err
}
for {
row, err := rowIter.Next(sqlCtx)
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
if len(row) != 3 {
return nil, fmt.Errorf("unexpectedly received wrong column count '%s': %s", qry, row)
}
rowStrings, err := sqlfmt.SqlRowAsStrings(sqlCtx, row, schema)
if err != nil {
return nil, err
}
name := rowStrings[0]
hash := rowStrings[1]
commitMessage := rowStrings[2]
branch := branchMeta{
name: name,
hash: hash,
remote: true,
message: commitMessage,
}
branches = append(branches, branch)
}
}
return branches, nil
}
func getBranchStatus(sqlCtx *sql.Context, queryEngine cli.Queryist, branches []branchMeta) ([]branchMeta, error) {
// Calculate ahead/behind for local branches that have upstream tracking
var updatedBranches []branchMeta
for _, branch := range branches {
if branch.remote || branch.upstream == "" {
updatedBranches = append(updatedBranches, branch)
continue
}
// Query dolt_branch_status to get ahead/behind counts
// The function shows upstream commits_ahead/behind relative to local branch
// So upstream commits_behind means local is ahead
query := fmt.Sprintf("SELECT commits_behind, commits_ahead FROM dolt_branch_status('%s', '%s')", branch.name, branch.upstream)
_, rowIter, _, err := queryEngine.Query(sqlCtx, query)
if err != nil {
// If branch status fails, just continue without status info
updatedBranches = append(updatedBranches, branch)
continue
}
row, err := rowIter.Next(sqlCtx)
if err != nil {
// If no results, just continue without status info
updatedBranches = append(updatedBranches, branch)
continue
}
if len(row) >= 2 {
if aheadVal, err := getUint64ColAsUint64(row[0]); err == nil {
branch.aheadCount = uint(aheadVal)
}
if behindVal, err := getUint64ColAsUint64(row[1]); err == nil {
branch.behindCount = uint(behindVal)
}
}
updatedBranches = append(updatedBranches, branch)
}
return updatedBranches, nil
}
func printBranches(sqlCtx *sql.Context, queryEngine cli.Queryist, apr *argparser.ArgParseResults, _ cli.UsagePrinter) int {
branchSet := set.NewStrSet(apr.Args)
verbosity, _ := apr.GetFlagCount(cli.VerboseFlag)
printRemote := apr.Contains(cli.RemoteParam)
printAll := apr.Contains(cli.AllFlag)
branches, err := getBranches(sqlCtx, queryEngine, printRemote, printAll)
if err != nil {
return HandleVErrAndExitCode(errhand.BuildDError("error: failed to read branches from db").AddCause(err).Build(), nil)
}
// Get branch status information for -v and -vv (both need ahead/behind)
if verbosity >= 1 {
// This will make a sql call for each branch, so could be slow for many branches. Only do it when verbose.
updatedBranches, err := getBranchStatus(sqlCtx, queryEngine, branches)
if err != nil {
return HandleVErrAndExitCode(errhand.BuildDError("error: failed to get branch status").AddCause(err).Build(), nil)
}
branches = updatedBranches
}
currentBranch, err := getActiveBranchName(sqlCtx, queryEngine)
if err != nil {
return HandleVErrAndExitCode(errhand.BuildDError("error: failed to read current branch from db").AddCause(err).Build(), nil)
}
sort.Slice(branches, func(i, j int) bool {
return branches[i].name < branches[j].name
})
// Filter branches first
filteredBranches := []branchMeta{}
for _, branch := range branches {
if branchSet.Size() > 0 && !branchSet.Contains(branch.name) {
continue
}
filteredBranches = append(filteredBranches, branch)
}
maxWidth := 0 // only used for verbosity > 0.
if verbosity > 0 {
for _, branch := range filteredBranches {
// Calculate display width (2 chars for "* " or " " plus branch name)
width := 2 + len(branch.name)
if width > maxWidth {
maxWidth = width
}
}
}
for _, branch := range filteredBranches {
var branchName string
if branch.name == currentBranch {
branchName = "* " + color.GreenString(branch.name)
} else if branch.remote {
branchName = " " + color.RedString(branch.name)
} else {
branchName = " " + branch.name
}
verboseStr := branch.hash
if verbosity > 0 {
// Add status brackets if there's upstream tracking
if branch.upstream != "" && (branch.aheadCount > 0 || branch.behindCount > 0) {
// For -vv, include upstream name; for -v, use empty string
upstreamPrefix := ""
if verbosity >= 2 {
upstreamPrefix = branch.upstream + ": "
}
if branch.aheadCount > 0 && branch.behindCount > 0 {
verboseStr += fmt.Sprintf(" [%sahead %d, behind %d]", upstreamPrefix, branch.aheadCount, branch.behindCount)
} else if branch.aheadCount > 0 {
verboseStr += fmt.Sprintf(" [%sahead %d]", upstreamPrefix, branch.aheadCount)
} else if branch.behindCount > 0 {
verboseStr += fmt.Sprintf(" [%sbehind %d]", upstreamPrefix, branch.behindCount)
}
} else if verbosity >= 2 && branch.upstream != "" {
// No ahead/behind, but -vv should still show upstream
verboseStr += fmt.Sprintf(" [%s]", branch.upstream)
}
// Add commit message for both -v and -vv
if branch.message != "" {
verboseStr += " " + branch.message
}
// All branch names have exactly 2 chars prefix, so calculate padding from that
displayLen := 2 + len(branch.name)
padding := maxWidth - displayLen + 1
if padding < 1 {
padding = 1
}
// This silliness is required to properly support color characters in branch names.
fmtStr := fmt.Sprintf("%%s%%%ds%%s", padding)
cli.Println(fmt.Sprintf(fmtStr, branchName, "", verboseStr))
} else {
cli.Println(branchName)
}
}
return 0
}
func printCurrentBranch(sqlCtx *sql.Context, queryEngine cli.Queryist) int {
currentBranchName, err := getActiveBranchName(sqlCtx, queryEngine)
if err != nil {
return HandleVErrAndExitCode(errhand.BuildDError("error: failed to read current branch from db").AddCause(err).Build(), nil)
}
cli.Println(currentBranchName)
return 0
}
func printAllDatasets(ctx context.Context, dEnv *env.DoltEnv) int {
refs, err := dEnv.DoltDB(ctx).GetHeadRefs(ctx)
if err != nil {
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), nil)
}
sort.Slice(refs, func(i, j int) bool {
return refs[i].String() < refs[j].String()
})
for _, r := range refs {
cli.Println(" " + r.String())
}
branches, err := dEnv.DoltDB(ctx).GetBranches(ctx)
if err != nil {
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), nil)
}
sort.Slice(branches, func(i, j int) bool {
return branches[i].String() < branches[j].String()
})
for _, b := range branches {
var w ref.WorkingSetRef
w, err = ref.WorkingSetRefForHead(b)
if err != nil {
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), nil)
}
_, err = dEnv.DoltDB(ctx).ResolveWorkingSet(ctx, w)
if errors.Is(err, doltdb.ErrWorkingSetNotFound) {
continue
} else if err != nil {
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), nil)
}
cli.Println(" " + w.String())
}
return 0
}
// generateBranchSql returns the query that will call the `DOLT_BRANCH` stored procedure.
func generateBranchSql(args []string) (string, error) {
var buffer bytes.Buffer
var first bool
queryValues := make([]interface{}, 0, len(args))
first = true
buffer.WriteString("CALL DOLT_BRANCH(")
for _, arg := range args {
if !first {
buffer.WriteString(", ")
}
first = false
buffer.WriteString("?")
queryValues = append(queryValues, arg)
}
buffer.WriteString(")")
return dbr.InterpolateForDialect(buffer.String(), queryValues, dialect.MySQL)
}
func updateUpstream(sqlCtx *sql.Context, queryEngine cli.Queryist, apr *argparser.ArgParseResults, args []string) int {
var branchName, upstreamBranch string
var err error
if apr.NArg() == 0 {
if apr.Contains(cli.TrackFlag) {
cli.PrintErrln("error: must specify branch to be created when using --track")
}
branchName, err = getActiveBranchName(sqlCtx, queryEngine)
if err != nil {
cli.PrintErrln("error: failed to get current branch from database")
return 1
}
} else {
branchName = apr.Arg(0)
}
if apr.Contains(cli.TrackFlag) && apr.Contains(cli.SetUpstreamToFlag) {
cli.PrintErrln(fmt.Sprintf("error: --%s and --%s are mutually exclusive options.", cli.SetUpstreamToFlag, cli.TrackFlag))
return 1
} else if apr.Contains(cli.TrackFlag) {
if apr.NArg() == 1 { // If we're only given the branch, we'll presume the current branch will be the upstream
upstreamBranch, err = getActiveBranchName(sqlCtx, queryEngine)
if err != nil {
cli.PrintErrln("error: If --track is used, you must specify the branch to modify, or a new branch.")
return 1
}
} else if apr.NArg() == 2 {
upstreamBranch = apr.Arg(1)
} else {
cli.PrintErrln("error: If --track is used, you must specify the branch to modify, or a new branch.")
return 1
}
} else if apr.Contains(cli.SetUpstreamToFlag) {
if apr.NArg() > 2 {
cli.PrintErrln("error: If --set-upstream-to is used, you must specify the branch to be modified, then upstream branch.")
return 1
}
upstreamBranch, _ = apr.GetValue(cli.SetUpstreamToFlag)
}
res := callStoredProcedure(sqlCtx, queryEngine, args)
if res != 0 {
return res
}
cli.Printf("branch '%s' set up to track '%s'\n", branchName, upstreamBranch)
return 0
}
func createBranch(sqlCtx *sql.Context, queryEngine cli.Queryist, apr *argparser.ArgParseResults, args []string, usage cli.UsagePrinter) int {
remoteName, useUpstream := apr.GetValue(cli.SetUpstreamToFlag)
if apr.NArg() != 1 && apr.NArg() != 2 {
usage()
return 1
}
if apr.Contains(cli.AllFlag) {
cli.PrintErrln("--all/-a can only be supplied when listing branches, not when creating branches")
return 1
}
if apr.Contains(cli.VerboseFlag) {
cli.PrintErrln("--verbose/-v can only be supplied when listing branches, not when creating branches")
return 1
}
if apr.Contains(cli.RemoteParam) {
cli.PrintErrln("--remote/-r can only be supplied when listing or deleting branches, not when creating branches")
return 1
}
var branchName = apr.Arg(0)
if !doltdb.IsValidUserBranchName(branchName) {
cli.PrintErrf("%s is an invalid branch name", branchName)
return 1
}
result := callStoredProcedure(sqlCtx, queryEngine, args)
if result != 0 {
return result
}
if useUpstream {
cli.Printf("branch '%s' set up to track '%s'\n", apr.Arg(0), remoteName)
}
return 0
}
func moveBranch(sqlCtx *sql.Context, queryEngine cli.Queryist, apr *argparser.ArgParseResults, args []string, usage cli.UsagePrinter) int {
if apr.NArg() != 1 && apr.NArg() != 2 {
usage()
return 1
}
if apr.Contains(cli.AllFlag) {
cli.PrintErrln("--all/-a can only be supplied when listing branches, not when moving branches")
return 1
}
if apr.Contains(cli.VerboseFlag) {
cli.PrintErrln("--verbose/-v can only be supplied when listing branches, not when moving branches")
return 1
}
if apr.Contains(cli.RemoteParam) {
cli.PrintErrln("--remote/-r can only be supplied when listing or deleting branches, not when moving branches")
return 1
}
var newName = apr.Arg(0)
if apr.NArg() == 2 {
newName = apr.Arg(1)
}
if !doltdb.IsValidUserBranchName(newName) {
cli.PrintErrf("%s is an invalid branch name", newName)
return 1
}
return callStoredProcedure(sqlCtx, queryEngine, args)
}
func copyBranch(sqlCtx *sql.Context, queryEngine cli.Queryist, apr *argparser.ArgParseResults, args []string, usage cli.UsagePrinter) int {
if apr.NArg() != 1 && apr.NArg() != 2 {
usage()
return 1
}
if apr.Contains(cli.AllFlag) {
cli.PrintErrln("--all/-a can only be supplied when listing branches, not when copying branches")
return 1
}
if apr.Contains(cli.VerboseFlag) {
cli.PrintErrln("--verbose/-v can only be supplied when listing branches, not when copying branches")
return 1
}
if apr.Contains(cli.RemoteParam) {
cli.PrintErrln("--remote/-r can only be supplied when listing or deleting branches, not when copying branches")
return 1
}
var toName = apr.Arg(0)
if apr.NArg() == 2 {
toName = apr.Arg(1)
}
if !doltdb.IsValidUserBranchName(toName) {
cli.PrintErrf("%s is an invalid branch name", toName)
return 1
}
return callStoredProcedure(sqlCtx, queryEngine, args)
}
func deleteBranches(sqlCtx *sql.Context, queryEngine cli.Queryist, apr *argparser.ArgParseResults, args []string, usage cli.UsagePrinter) int {
if apr.NArg() == 0 {
usage()
return 1
}
if apr.Contains(cli.AllFlag) {
cli.PrintErrln("--all/-a can only be supplied when listing branches, not when deleting branches")
return 1
}
if apr.Contains(cli.VerboseFlag) {
cli.PrintErrln("--verbose/-v can only be supplied when listing branches, not when deleting branches")
return 1
}
return callStoredProcedure(sqlCtx, queryEngine, args)
}
func generateForceDeleteMessage(args []string) string {
newArgs := ""
for _, arg := range args {
if arg != "--force" && arg != "-f" && arg != "-D" && arg != "-d" {
newArgs = newArgs + " " + arg
}
}
return newArgs
}
// callStoredProcedure generates and executes the SQL query for calling the DOLT_BRANCH stored procedure.
// All actions that modify branches delegate to this after they validate their arguments.
// Actions that don't modify branches, such as `dolt branch --list` and `dolt branch --show-current`, don't call
// this method.
func callStoredProcedure(sqlCtx *sql.Context, queryEngine cli.Queryist, args []string) int {
query, err := generateBranchSql(args)
if err != nil {
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), nil)
}
_, rowIter, _, err := queryEngine.Query(sqlCtx, query)
if err != nil {
if strings.Contains(err.Error(), "is not fully merged") {
newErrorMessage := fmt.Sprintf("%s. If you are sure you want to delete it, run 'dolt branch -D%s'", err.Error(), generateForceDeleteMessage(args))
return HandleVErrAndExitCode(errhand.BuildDError("%s", newErrorMessage).Build(), nil)
}
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(fmt.Errorf("error: %s", err.Error())), nil)
}
_, err = sql.RowIterToRows(sqlCtx, rowIter)
if err != nil {
return HandleVErrAndExitCode(errhand.BuildDError("error: failed to get result rows for query %s", query).AddCause(err).Build(), nil)
}
return 0
}
// BuildVerrAndExit is a shortcut for building a verbose error and calling HandleVerrAndExitCode with it
func BuildVerrAndExit(errMsg string, cause error) int {
return HandleVErrAndExitCode(errhand.BuildDError("%s", errMsg).AddCause(cause).Build(), nil)
}