Merge pull request #4681 from dolthub/taylor/log-args

Allow supplying multiple start/excluding refs to dolt log
This commit is contained in:
Taylor Bantle
2022-11-07 11:58:18 -08:00
committed by GitHub
18 changed files with 604 additions and 365 deletions

View File

@@ -302,7 +302,7 @@ func CreateLogArgParser() *argparser.ArgParser {
ap.SupportsFlag(ParentsFlag, "", "Shows all parents of each commit in the log.")
ap.SupportsString(DecorateFlag, "", "decorate_fmt", "Shows refs next to commits. Valid options are short, full, no, and auto")
ap.SupportsFlag(OneLineFlag, "", "Shows logs in a compact format.")
ap.SupportsString(NotFlag, "", "revision", "Excludes commits from revision.")
ap.SupportsStringList(NotFlag, "", "revision", "Excludes commits from revision.")
return ap
}

View File

@@ -40,7 +40,7 @@ import (
)
var commitDocs = cli.CommandDocumentationContent{
ShortDesc: "Record changes to the repository",
ShortDesc: "Record changes to the database",
LongDesc: `
Stores the current contents of the staged tables in a new commit along with a log message from the user describing the changes.

View File

@@ -36,14 +36,14 @@ import (
)
type logOpts struct {
numLines int
showParents bool
minParents int
decoration string
oneLine bool
excludingCommitSpec *doltdb.CommitSpec
commitSpec *doltdb.CommitSpec
tableName string
numLines int
showParents bool
minParents int
decoration string
oneLine bool
excludingCommitSpecs []*doltdb.CommitSpec
commitSpecs []*doltdb.CommitSpec
tableName string
}
type logNode struct {
@@ -63,16 +63,20 @@ The command takes options to control what is shown and how.
{{.EmphasisLeft}}dolt log{{.EmphasisRight}}
Lists commit logs from current HEAD when no options provided.
{{.EmphasisLeft}}dolt log <revision>{{.EmphasisRight}}
Lists commit logs starting from revision.
{{.EmphasisLeft}}dolt log [<revisions>...]{{.EmphasisRight}}
Lists commit logs starting from revision. If multiple revisions provided, lists logs reachable by all revisions.
{{.EmphasisLeft}}dolt log <revision> <table>{{.EmphasisRight}}
Lists commit logs starting from revision, only including commits with changes to table.
{{.EmphasisLeft}}dolt log [<revisions>...] <table>{{.EmphasisRight}}
Lists commit logs starting from revisions, only including commits with changes to table.
{{.EmphasisLeft}}dolt log <revisionB>..<revisionA>{{.EmphasisRight}}
{{.EmphasisLeft}}dolt log <revisionA> --not <revisionB>{{.EmphasisRight}}
{{.EmphasisLeft}}dolt log ^<revisionB> <revisionA>{{.EmphasisRight}}
Different ways to list two dot logs. These will list commit logs for revisionA, while excluding commits from revisionB. The table option is not supported for two dot log.`,
Different ways to list two dot logs. These will list commit logs for revisionA, while excluding commits from revisionB. The table option is not supported for two dot log.
{{.EmphasisLeft}}dolt log <revisionB>...<revisionA>{{.EmphasisRight}}
{{.EmphasisLeft}}dolt log <revisionA> <revisionB> --not $(dolt merge-base <revisionA> <revisionB>){{.EmphasisRight}}
Different ways to list three dot logs. These will list commit logs reachable by revisionA OR revisionB, while excluding commits reachable by BOTH revisionA AND revisionB.`,
Synopsis: []string{
`[-n {{.LessThan}}num_commits{{.GreaterThan}}] [{{.LessThan}}revision-range{{.GreaterThan}}] [[--] {{.LessThan}}table{{.GreaterThan}}]`,
},
@@ -114,17 +118,12 @@ func (cmd LogCmd) logWithLoggerFunc(ctx context.Context, commandStr string, args
help, usage := cli.HelpAndUsagePrinters(cli.CommandDocsForCommandString(commandStr, logDocs, ap))
apr := cli.ParseArgsOrDie(ap, args, help)
if apr.NArg() > 2 {
usage()
return 1
}
opts, err := parseLogArgs(ctx, dEnv, apr)
if err != nil {
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
}
if opts.commitSpec == nil {
opts.commitSpec = dEnv.RepoStateReader().CWBHeadSpec()
if len(opts.commitSpecs) == 0 {
opts.commitSpecs = append(opts.commitSpecs, dEnv.RepoStateReader().CWBHeadSpec())
}
if len(opts.tableName) > 0 {
return handleErrAndExit(logTableCommits(ctx, dEnv, opts))
@@ -152,146 +151,121 @@ func parseLogArgs(ctx context.Context, dEnv *env.DoltEnv, apr *argparser.ArgPars
oneLine: apr.Contains(cli.OneLineFlag),
decoration: decorateOption,
}
cs, notCs, tableName, err := parseRefsAndTable(ctx, apr, dEnv)
err := opts.parseRefsAndTable(ctx, apr, dEnv)
if err != nil {
return nil, err
}
opts.commitSpec = cs
opts.excludingCommitSpec = notCs
opts.tableName = tableName
excludingRef, ok := apr.GetValue(cli.NotFlag)
excludingRefs, ok := apr.GetValueList(cli.NotFlag)
if ok {
if opts.excludingCommitSpec != nil {
if len(opts.excludingCommitSpecs) > 0 {
return nil, fmt.Errorf("cannot use --not argument with two dots or ref with ^")
}
if len(opts.tableName) > 0 {
return nil, fmt.Errorf("cannot use --not argument with table")
}
cs, err := doltdb.NewCommitSpec(excludingRef)
if err != nil {
return nil, fmt.Errorf("invalid commit %s\n", excludingRef)
for _, excludingRef := range excludingRefs {
notCs, err := doltdb.NewCommitSpec(excludingRef)
if err != nil {
return nil, fmt.Errorf("invalid commit %s\n", excludingRef)
}
opts.excludingCommitSpecs = append(opts.excludingCommitSpecs, notCs)
}
opts.excludingCommitSpec = cs
}
return opts, nil
}
func parseRefsAndTable(ctx context.Context, apr *argparser.ArgParseResults, dEnv *env.DoltEnv) (*doltdb.CommitSpec, *doltdb.CommitSpec, string, error) {
switch apr.NArg() {
// dolt log
case 0:
return nil, nil, "", nil
// dolt log <ref/^ref/revision-range/table>
case 1:
return getCommitOrTableFromString(ctx, apr.Arg(0), dEnv, true)
// dolt log <ref/^ref> <ref/^ref/table>
case 2:
firstCs, firstExNotCs, _, err := getCommitOrTableFromString(ctx, apr.Arg(0), dEnv, false)
if err != nil {
return nil, nil, "", err
}
secondCs, secondExNotCs, tableName, err := getCommitOrTableFromString(ctx, apr.Arg(1), dEnv, false)
if err != nil {
return nil, nil, "", err
}
if len(tableName) > 0 {
if firstExNotCs != nil {
return nil, nil, "", fmt.Errorf("Providing tableName for two dot log not yet supported")
}
// dolt log <ref> <table>
return firstCs, nil, tableName, nil
}
if firstCs != nil && secondCs != nil {
commit, err := dEnv.DoltDB.Resolve(ctx, firstCs, dEnv.RepoStateReader().CWBHeadRef())
if err != nil {
return nil, nil, "", err
}
// Handles table name matching branch name (dolt log <ref> <table>)
exists, err := tableExists(ctx, commit, apr.Arg(1))
if err != nil {
return nil, nil, "", err
}
if exists {
return firstCs, nil, apr.Arg(1), nil
}
return nil, nil, "", fmt.Errorf("Cannot provide two commit refs") // dolt log <ref> <ref>
}
if firstExNotCs != nil && secondExNotCs != nil {
return nil, nil, "", fmt.Errorf("Cannot exclude two commit refs") // dolt log ^<ref> ^<ref>
}
// dolt log ^<ref> <ref>
if firstExNotCs != nil && secondCs != nil {
return secondCs, firstExNotCs, "", nil
}
// dolt log <ref> ^<ref>
if firstCs != nil && secondExNotCs != nil {
return firstCs, secondExNotCs, "", nil
}
return nil, nil, "", nil
default:
return nil, nil, "", fmt.Errorf("Cannot provide more than 3 arguments")
}
}
func getCommitOrTableFromString(ctx context.Context, str string, dEnv *env.DoltEnv, canDot bool) (*doltdb.CommitSpec, *doltdb.CommitSpec, string, error) {
// <ref>...<ref>
if strings.Contains(str, "...") {
return nil, nil, "", fmt.Errorf("Three dot dolt log not supported")
func (opts *logOpts) parseRefsAndTable(ctx context.Context, apr *argparser.ArgParseResults, dEnv *env.DoltEnv) error {
// `dolt log`
if apr.NArg() == 0 {
return nil
}
// <ref>..<ref>
if strings.Contains(str, "..") {
if !canDot {
return nil, nil, "", fmt.Errorf("Cannot use two dot when 2 arguments provided")
if strings.Contains(apr.Arg(0), "..") {
if apr.NArg() > 1 {
return fmt.Errorf("Cannot use two or three dot syntax when 2 or more arguments provided")
}
refs := strings.Split(str, "..")
// `dolt log <ref>...<ref>`
if strings.Contains(apr.Arg(0), "...") {
refs := strings.Split(apr.Arg(0), "...")
for _, ref := range refs {
cs, err := getCommitSpec(ref)
if err != nil {
return err
}
opts.commitSpecs = append(opts.commitSpecs, cs)
}
mergeBase, verr := getMergeBaseFromStrings(ctx, dEnv, refs[0], refs[1])
if verr != nil {
return verr
}
notCs, err := getCommitSpec(mergeBase)
if err != nil {
return err
}
opts.excludingCommitSpecs = append(opts.excludingCommitSpecs, notCs)
return nil
}
// `dolt log <ref>..<ref>`
refs := strings.Split(apr.Arg(0), "..")
notCs, err := getCommitSpec(refs[0])
if err != nil {
return nil, nil, "", err
return err
}
cs, err := getCommitSpec(refs[1])
if err != nil {
return nil, nil, "", err
return err
}
return cs, notCs, "", nil
opts.commitSpecs = append(opts.commitSpecs, cs)
opts.excludingCommitSpecs = append(opts.excludingCommitSpecs, notCs)
return nil
}
// ^<ref>
if strings.HasPrefix(str, "^") {
commit := strings.TrimPrefix(str, "^")
notCs, err := getCommitSpec(commit)
if err != nil {
return nil, nil, "", err
seenRefs := make(map[string]bool)
for _, arg := range apr.Args {
// ^<ref>
if strings.HasPrefix(arg, "^") {
commit := strings.TrimPrefix(arg, "^")
notCs, err := getCommitSpec(commit)
if err != nil {
return err
}
opts.excludingCommitSpecs = append(opts.excludingCommitSpecs, notCs)
} else {
argIsRef := actions.ValidateIsRef(ctx, arg, dEnv.DoltDB, dEnv.RepoStateReader())
// <ref>
if argIsRef && !seenRefs[arg] {
cs, err := getCommitSpec(arg)
if err != nil {
return err
}
seenRefs[arg] = true
opts.commitSpecs = append(opts.commitSpecs, cs)
} else {
// <table>
opts.tableName = arg
}
}
return nil, notCs, "", err
}
argIsRef := actions.ValidateIsRef(ctx, str, dEnv.DoltDB, dEnv.RepoStateReader())
// <ref>
if argIsRef {
cs, err := getCommitSpec(str)
if err != nil {
return nil, nil, "", err
}
return cs, nil, "", nil
if len(opts.tableName) > 0 && len(opts.excludingCommitSpecs) > 0 {
return fmt.Errorf("Cannot provide table name with excluding refs")
}
// <table>
return nil, nil, str, nil
return nil
}
func getCommitSpec(commit string) (*doltdb.CommitSpec, error) {
@@ -303,10 +277,22 @@ func getCommitSpec(commit string) (*doltdb.CommitSpec, error) {
}
func logCommits(ctx context.Context, dEnv *env.DoltEnv, opts *logOpts) int {
commit, err := dEnv.DoltDB.Resolve(ctx, opts.commitSpec, dEnv.RepoStateReader().CWBHeadRef())
if err != nil {
cli.PrintErrln(color.HiRedString("Fatal error: cannot get HEAD commit for current branch."))
return 1
hashes := make([]hash.Hash, len(opts.commitSpecs))
for i, cs := range opts.commitSpecs {
commit, err := dEnv.DoltDB.Resolve(ctx, cs, dEnv.RepoStateReader().CWBHeadRef())
if err != nil {
cli.PrintErrln(color.HiRedString("Fatal error: cannot get HEAD commit for current branch."))
return 1
}
h, err := commit.HashOf()
if err != nil {
cli.PrintErrln(color.HiRedString("Fatal error: failed to get commit hash"))
return 1
}
hashes[i] = h
}
cHashToRefs := map[hash.Hash][]string{}
@@ -356,39 +342,37 @@ func logCommits(ctx context.Context, dEnv *env.DoltEnv, opts *logOpts) int {
cHashToRefs[t.Hash] = append(cHashToRefs[t.Hash], tagName)
}
h, err := commit.HashOf()
if err != nil {
cli.PrintErrln(color.HiRedString("Fatal error: failed to get commit hash"))
return 1
}
matchFunc := func(commit *doltdb.Commit) (bool, error) {
return commit.NumParents() >= opts.minParents, nil
matchFunc := func(c *doltdb.Commit) (bool, error) {
return c.NumParents() >= opts.minParents, nil
}
var commits []*doltdb.Commit
if opts.excludingCommitSpec == nil {
commits, err = commitwalk.GetTopNTopoOrderedCommitsMatching(ctx, dEnv.DoltDB, h, opts.numLines, matchFunc)
if len(opts.excludingCommitSpecs) == 0 {
commits, err = commitwalk.GetTopNTopoOrderedCommitsMatching(ctx, dEnv.DoltDB, hashes, opts.numLines, matchFunc)
} else {
excludingCommit, err := dEnv.DoltDB.Resolve(ctx, opts.excludingCommitSpec, dEnv.RepoStateReader().CWBHeadRef())
if err != nil {
cli.PrintErrln(color.HiRedString("Fatal error: cannot get excluding commit for current branch."))
return 1
excludingHashes := make([]hash.Hash, len(opts.excludingCommitSpecs))
for i, excludingSpec := range opts.excludingCommitSpecs {
excludingCommit, err := dEnv.DoltDB.Resolve(ctx, excludingSpec, dEnv.RepoStateReader().CWBHeadRef())
if err != nil {
cli.PrintErrln(color.HiRedString("Fatal error: cannot get excluding commit for current branch."))
return 1
}
excludingHash, err := excludingCommit.HashOf()
if err != nil {
cli.PrintErrln(color.HiRedString("Fatal error: failed to get commit hash"))
return 1
}
excludingHashes[i] = excludingHash
}
excludingHash, err := excludingCommit.HashOf()
if err != nil {
cli.PrintErrln(color.HiRedString("Fatal error: failed to get commit hash"))
return 1
}
commits, err = commitwalk.GetDotDotRevisions(ctx, dEnv.DoltDB, h, dEnv.DoltDB, excludingHash, opts.numLines)
commits, err = commitwalk.GetDotDotRevisions(ctx, dEnv.DoltDB, hashes, dEnv.DoltDB, excludingHashes, opts.numLines)
}
if err != nil {
cli.PrintErrln("Error retrieving commit.")
cli.PrintErrln(err)
return 1
}
@@ -417,7 +401,7 @@ func logCommits(ctx context.Context, dEnv *env.DoltEnv, opts *logOpts) int {
commitHash: cmHash,
parentHashes: pHashes,
branchNames: cHashToRefs[cmHash],
isHead: cmHash == h})
isHead: hashIsHead(cmHash, hashes)})
}
logToStdOut(opts, commitsInfo)
@@ -425,6 +409,13 @@ func logCommits(ctx context.Context, dEnv *env.DoltEnv, opts *logOpts) int {
return 0
}
func hashIsHead(cmHash hash.Hash, hashes []hash.Hash) bool {
if len(hashes) > 1 || len(hashes) == 0 {
return false
}
return cmHash == hashes[0]
}
func tableExists(ctx context.Context, commit *doltdb.Commit, tableName string) (bool, error) {
rv, err := commit.GetRootValue(ctx)
if err != nil {
@@ -440,31 +431,37 @@ func tableExists(ctx context.Context, commit *doltdb.Commit, tableName string) (
}
func logTableCommits(ctx context.Context, dEnv *env.DoltEnv, opts *logOpts) error {
commit, err := dEnv.DoltDB.Resolve(ctx, opts.commitSpec, dEnv.RepoStateReader().CWBHeadRef())
if err != nil {
return err
}
hashes := make([]hash.Hash, len(opts.commitSpecs))
// Check that the table exists in the head commit
exists, err := tableExists(ctx, commit, opts.tableName)
if err != nil {
return err
}
for i, cs := range opts.commitSpecs {
commit, err := dEnv.DoltDB.Resolve(ctx, cs, dEnv.RepoStateReader().CWBHeadRef())
if err != nil {
return err
}
if !exists {
return fmt.Errorf("error: table %s does not exist", opts.tableName)
}
h, err := commit.HashOf()
if err != nil {
return err
}
h, err := commit.HashOf()
if err != nil {
return err
// Check that the table exists in the head commits
exists, err := tableExists(ctx, commit, opts.tableName)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("error: table %s does not exist", opts.tableName)
}
hashes[i] = h
}
matchFunc := func(commit *doltdb.Commit) (bool, error) {
return commit.NumParents() >= opts.minParents, nil
}
itr, err := commitwalk.GetTopologicalOrderIterator(ctx, dEnv.DoltDB, h, matchFunc)
itr, err := commitwalk.GetTopologicalOrderIterator(ctx, dEnv.DoltDB, hashes, matchFunc)
if err != nil && err != io.EOF {
return err
}

View File

@@ -86,6 +86,7 @@ func (cmd MergeBaseCmd) Exec(ctx context.Context, commandStr string, args []stri
}
cli.Println(mergeBaseStr)
return 0
}

View File

@@ -219,7 +219,7 @@ func printRemoteRefTrackingInfo(ctx context.Context, dEnv *env.DoltEnv) error {
// countCommitsInRange returns the number of commits between the given starting point to trace back to the given target point.
// The starting commit must be a descendant of the target commit. Target commit must be a common ancestor commit.
func countCommitsInRange(ctx context.Context, ddb *doltdb.DoltDB, startCommitHash, targetCommitHash hash.Hash) (int, error) {
itr, iErr := commitwalk.GetTopologicalOrderIterator(ctx, ddb, startCommitHash, nil)
itr, iErr := commitwalk.GetTopologicalOrderIterator(ctx, ddb, []hash.Hash{startCommitHash}, nil)
if iErr != nil {
return 0, iErr
}

View File

@@ -149,80 +149,64 @@ func newQueue() *q {
return &q{loaded: make(map[hash.Hash]*c)}
}
// GetDotDotRevisions returns the commits reachable from commit at hash
// `includedHead` that are not reachable from hash `excludedHead`.
// `includedHead` and `excludedHead` must be commits in `ddb`. Returns up
// to `num` commits, in reverse topological order starting at `includedHead`,
// GetDotDotRevisions returns the commits reachable from commit at hashes
// `includedHeads` that are not reachable from hashes `excludedHeads`.
// `includedHeads` and `excludedHeads` must be commits in `ddb`. Returns up
// to `num` commits, in reverse topological order starting at `includedHeads`,
// with tie breaking based on the height of commit graph between
// concurrent commits --- higher commits appear first. Remaining
// ties are broken by timestamp; newer commits appear first.
//
// Roughly mimics `git log main..feature`.
func GetDotDotRevisions(ctx context.Context, includedDB *doltdb.DoltDB, includedHead hash.Hash, excludedDB *doltdb.DoltDB, excludedHead hash.Hash, num int) ([]*doltdb.Commit, error) {
// Roughly mimics `git log main..feature` or `git log main...feature` (if
// more than one `includedHead` is provided).
func GetDotDotRevisions(ctx context.Context, includedDB *doltdb.DoltDB, includedHeads []hash.Hash, excludedDB *doltdb.DoltDB, excludedHeads []hash.Hash, num int) ([]*doltdb.Commit, error) {
itr, err := GetDotDotRevisionsIterator(ctx, includedDB, includedHeads, excludedDB, excludedHeads, nil)
if err != nil {
return nil, err
}
var commitList []*doltdb.Commit
q := newQueue()
if err := q.SetInvisible(ctx, excludedDB, excludedHead); err != nil {
return nil, err
}
if err := q.AddPendingIfUnseen(ctx, excludedDB, excludedHead); err != nil {
return nil, err
}
if err := q.AddPendingIfUnseen(ctx, includedDB, includedHead); err != nil {
return nil, err
}
for q.NumVisiblePending() > 0 {
nextC := q.PopPending()
parents, err := nextC.commit.ParentHashes(ctx)
if err != nil {
for num < 0 || len(commitList) < num {
_, commit, err := itr.Next(ctx)
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
for _, parentID := range parents {
if nextC.invisible {
if err := q.SetInvisible(ctx, nextC.ddb, parentID); err != nil {
return nil, err
}
}
if err := q.AddPendingIfUnseen(ctx, nextC.ddb, parentID); err != nil {
return nil, err
}
}
if !nextC.invisible {
commitList = append(commitList, nextC.commit)
if len(commitList) == num {
return commitList, nil
}
}
commitList = append(commitList, commit)
}
return commitList, nil
}
// GetTopologicalOrderCommits returns the commits reachable from the commit at hash `startCommitHash`
// GetTopologicalOrderCommits returns the commits reachable from the commits in `startCommitHashes`
// in reverse topological order, with tiebreaking done by the height of the commit graph -- higher commits
// appear first. Remaining ties are broken by timestamp; newer commits appear first.
func GetTopologicalOrderCommits(ctx context.Context, ddb *doltdb.DoltDB, startCommitHash hash.Hash) ([]*doltdb.Commit, error) {
return GetTopNTopoOrderedCommitsMatching(ctx, ddb, startCommitHash, -1, nil)
func GetTopologicalOrderCommits(ctx context.Context, ddb *doltdb.DoltDB, startCommitHashes []hash.Hash) ([]*doltdb.Commit, error) {
return GetTopNTopoOrderedCommitsMatching(ctx, ddb, startCommitHashes, -1, nil)
}
// GetTopologicalOrderCommitIterator returns an iterator for commits generated with the same semantics as
// GetTopologicalOrderCommits
func GetTopologicalOrderIterator(ctx context.Context, ddb *doltdb.DoltDB, startCommitHash hash.Hash, matchFn func(*doltdb.Commit) (bool, error)) (doltdb.CommitItr, error) {
return newCommiterator(ctx, ddb, startCommitHash, matchFn)
func GetTopologicalOrderIterator(ctx context.Context, ddb *doltdb.DoltDB, startCommitHashes []hash.Hash, matchFn func(*doltdb.Commit) (bool, error)) (doltdb.CommitItr, error) {
return newCommiterator(ctx, ddb, startCommitHashes, matchFn)
}
type commiterator struct {
ddb *doltdb.DoltDB
startCommitHash hash.Hash
matchFn func(*doltdb.Commit) (bool, error)
q *q
ddb *doltdb.DoltDB
startCommitHashes []hash.Hash
matchFn func(*doltdb.Commit) (bool, error)
q *q
}
var _ doltdb.CommitItr = (*commiterator)(nil)
func newCommiterator(ctx context.Context, ddb *doltdb.DoltDB, startCommitHash hash.Hash, matchFn func(*doltdb.Commit) (bool, error)) (*commiterator, error) {
func newCommiterator(ctx context.Context, ddb *doltdb.DoltDB, startCommitHashes []hash.Hash, matchFn func(*doltdb.Commit) (bool, error)) (*commiterator, error) {
itr := &commiterator{
ddb: ddb,
startCommitHash: startCommitHash,
matchFn: matchFn,
ddb: ddb,
startCommitHashes: startCommitHashes,
matchFn: matchFn,
}
err := itr.Reset(ctx)
@@ -270,17 +254,19 @@ func (i *commiterator) Next(ctx context.Context) (hash.Hash, *doltdb.Commit, err
// Reset implements doltdb.CommitItr
func (i *commiterator) Reset(ctx context.Context) error {
i.q = newQueue()
if err := i.q.AddPendingIfUnseen(ctx, i.ddb, i.startCommitHash); err != nil {
return err
for _, startCommitHash := range i.startCommitHashes {
if err := i.q.AddPendingIfUnseen(ctx, i.ddb, startCommitHash); err != nil {
return err
}
}
return nil
}
// GetTopNTopoOrderedCommitsMatching returns the first N commits (If N <= 0 then all commits) reachable from the commit at hash
// `startCommitHash` in reverse topological order, with tiebreaking done by the height of the commit graph -- higher
// GetTopNTopoOrderedCommitsMatching returns the first N commits (If N <= 0 then all commits) reachable from the commits in
// `startCommitHashes` in reverse topological order, with tiebreaking done by the height of the commit graph -- higher
// commits appear first. Remaining ties are broken by timestamp; newer commits appear first.
func GetTopNTopoOrderedCommitsMatching(ctx context.Context, ddb *doltdb.DoltDB, startCommitHash hash.Hash, n int, matchFn func(*doltdb.Commit) (bool, error)) ([]*doltdb.Commit, error) {
itr, err := GetTopologicalOrderIterator(ctx, ddb, startCommitHash, matchFn)
func GetTopNTopoOrderedCommitsMatching(ctx context.Context, ddb *doltdb.DoltDB, startCommitHashes []hash.Hash, n int, matchFn func(*doltdb.Commit) (bool, error)) ([]*doltdb.Commit, error) {
itr, err := GetTopologicalOrderIterator(ctx, ddb, startCommitHashes, matchFn)
if err != nil {
return nil, err
}
@@ -302,26 +288,28 @@ func GetTopNTopoOrderedCommitsMatching(ctx context.Context, ddb *doltdb.DoltDB,
// GetDotDotRevisionsIterator returns an iterator for commits generated with the same semantics as
// GetDotDotRevisions
func GetDotDotRevisionsIterator(ctx context.Context, ddb *doltdb.DoltDB, startCommitHash, excludingCommitHash hash.Hash, matchFn func(*doltdb.Commit) (bool, error)) (doltdb.CommitItr, error) {
return newDotDotCommiterator(ctx, ddb, startCommitHash, excludingCommitHash, matchFn)
func GetDotDotRevisionsIterator(ctx context.Context, includedDdb *doltdb.DoltDB, startCommitHashes []hash.Hash, excludedDdb *doltdb.DoltDB, excludingCommitHashes []hash.Hash, matchFn func(*doltdb.Commit) (bool, error)) (doltdb.CommitItr, error) {
return newDotDotCommiterator(ctx, includedDdb, startCommitHashes, excludedDdb, excludingCommitHashes, matchFn)
}
type dotDotCommiterator struct {
ddb *doltdb.DoltDB
startCommitHash hash.Hash
excludingCommitHash hash.Hash
matchFn func(*doltdb.Commit) (bool, error)
q *q
includedDdb *doltdb.DoltDB
excludedDdb *doltdb.DoltDB
startCommitHashes []hash.Hash
excludingCommitHashes []hash.Hash
matchFn func(*doltdb.Commit) (bool, error)
q *q
}
var _ doltdb.CommitItr = (*dotDotCommiterator)(nil)
func newDotDotCommiterator(ctx context.Context, ddb *doltdb.DoltDB, startCommitHash, excludingCommitHash hash.Hash, matchFn func(*doltdb.Commit) (bool, error)) (*dotDotCommiterator, error) {
func newDotDotCommiterator(ctx context.Context, includedDdb *doltdb.DoltDB, startCommitHashes []hash.Hash, excludedDdb *doltdb.DoltDB, excludingCommitHashes []hash.Hash, matchFn func(*doltdb.Commit) (bool, error)) (*dotDotCommiterator, error) {
itr := &dotDotCommiterator{
ddb: ddb,
startCommitHash: startCommitHash,
excludingCommitHash: excludingCommitHash,
matchFn: matchFn,
includedDdb: includedDdb,
excludedDdb: excludedDdb,
startCommitHashes: startCommitHashes,
excludingCommitHashes: excludingCommitHashes,
matchFn: matchFn,
}
err := itr.Reset(ctx)
@@ -373,14 +361,18 @@ func (i *dotDotCommiterator) Next(ctx context.Context) (hash.Hash, *doltdb.Commi
// Reset implements doltdb.CommitItr
func (i *dotDotCommiterator) Reset(ctx context.Context) error {
i.q = newQueue()
if err := i.q.SetInvisible(ctx, i.ddb, i.excludingCommitHash); err != nil {
return err
for _, excludingCommitHash := range i.excludingCommitHashes {
if err := i.q.SetInvisible(ctx, i.excludedDdb, excludingCommitHash); err != nil {
return err
}
if err := i.q.AddPendingIfUnseen(ctx, i.excludedDdb, excludingCommitHash); err != nil {
return err
}
}
if err := i.q.AddPendingIfUnseen(ctx, i.ddb, i.excludingCommitHash); err != nil {
return err
}
if err := i.q.AddPendingIfUnseen(ctx, i.ddb, i.startCommitHash); err != nil {
return err
for _, startCommitHash := range i.startCommitHashes {
if err := i.q.AddPendingIfUnseen(ctx, i.includedDdb, startCommitHash); err != nil {
return err
}
}
return nil
}

View File

@@ -24,6 +24,7 @@ import (
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
"github.com/dolthub/dolt/go/libraries/doltcore/ref"
"github.com/dolthub/dolt/go/libraries/utils/filesys"
"github.com/dolthub/dolt/go/store/datas"
@@ -113,15 +114,15 @@ func TestGetDotDotRevisions(t *testing.T) {
// Branches look like this:
//
// feature: *--*--*--*--*--*--*
// / /
// main: --*--*--*--*--*--------*--*--*--*
// feature: F1--F2--F3--F4--F5--F6--F7
// / /
// main: M0--M1--M2--M3--M4--M5/F0------------M6--M7--M8--M9
featureHash := mustGetHash(t, featureCommits[7])
mainHash := mustGetHash(t, mainCommits[6])
featurePreMergeHash := mustGetHash(t, featureCommits[3])
res, err := GetDotDotRevisions(context.Background(), dEnv.DoltDB, featureHash, dEnv.DoltDB, mainHash, 100)
res, err := GetDotDotRevisions(context.Background(), dEnv.DoltDB, []hash.Hash{featureHash}, dEnv.DoltDB, []hash.Hash{mainHash}, 100)
require.NoError(t, err)
assert.Len(t, res, 7)
@@ -133,31 +134,57 @@ func TestGetDotDotRevisions(t *testing.T) {
assertEqualHashes(t, featureCommits[2], res[5])
assertEqualHashes(t, featureCommits[1], res[6])
res, err = GetDotDotRevisions(context.Background(), dEnv.DoltDB, mainHash, dEnv.DoltDB, featureHash, 100)
res, err = GetDotDotRevisions(context.Background(), dEnv.DoltDB, []hash.Hash{mainHash}, dEnv.DoltDB, []hash.Hash{featureHash}, 100)
require.NoError(t, err)
assert.Len(t, res, 0)
res, err = GetDotDotRevisions(context.Background(), dEnv.DoltDB, featureHash, dEnv.DoltDB, mainHash, 3)
res, err = GetDotDotRevisions(context.Background(), dEnv.DoltDB, []hash.Hash{featureHash}, dEnv.DoltDB, []hash.Hash{mainHash}, 3)
require.NoError(t, err)
assert.Len(t, res, 3)
assertEqualHashes(t, featureCommits[7], res[0])
assertEqualHashes(t, featureCommits[6], res[1])
assertEqualHashes(t, featureCommits[5], res[2])
res, err = GetDotDotRevisions(context.Background(), dEnv.DoltDB, featurePreMergeHash, dEnv.DoltDB, mainHash, 3)
res, err = GetDotDotRevisions(context.Background(), dEnv.DoltDB, []hash.Hash{featurePreMergeHash}, dEnv.DoltDB, []hash.Hash{mainHash}, 3)
require.NoError(t, err)
assert.Len(t, res, 3)
assertEqualHashes(t, featureCommits[3], res[0])
assertEqualHashes(t, featureCommits[2], res[1])
assertEqualHashes(t, featureCommits[1], res[2])
res, err = GetDotDotRevisions(context.Background(), dEnv.DoltDB, featurePreMergeHash, dEnv.DoltDB, mainHash, 3)
res, err = GetDotDotRevisions(context.Background(), dEnv.DoltDB, []hash.Hash{featurePreMergeHash}, dEnv.DoltDB, []hash.Hash{mainHash}, 3)
require.NoError(t, err)
assert.Len(t, res, 3)
assertEqualHashes(t, featureCommits[3], res[0])
assertEqualHashes(t, featureCommits[2], res[1])
assertEqualHashes(t, featureCommits[1], res[2])
// Three dot
mergeBaseHash, err := merge.MergeBase(context.Background(), mainCommits[6], featureCommits[7])
require.NoError(t, err)
res, err = GetDotDotRevisions(context.Background(), dEnv.DoltDB, []hash.Hash{featureHash, mainHash}, dEnv.DoltDB, []hash.Hash{mergeBaseHash}, -1)
require.NoError(t, err)
assert.Len(t, res, 7)
assertEqualHashes(t, featureCommits[7], res[0])
assertEqualHashes(t, featureCommits[6], res[1])
assertEqualHashes(t, featureCommits[5], res[2])
assertEqualHashes(t, featureCommits[4], res[3])
assertEqualHashes(t, featureCommits[3], res[4])
assertEqualHashes(t, featureCommits[2], res[5])
assertEqualHashes(t, featureCommits[1], res[6])
mergeBaseHash, err = merge.MergeBase(context.Background(), mainCommits[6], featureCommits[3])
require.NoError(t, err)
res, err = GetDotDotRevisions(context.Background(), dEnv.DoltDB, []hash.Hash{featurePreMergeHash, mainHash}, dEnv.DoltDB, []hash.Hash{mergeBaseHash}, -1)
require.NoError(t, err)
assert.Len(t, res, 4)
assertEqualHashes(t, featureCommits[3], res[0])
assertEqualHashes(t, featureCommits[2], res[1])
assertEqualHashes(t, mainCommits[6], res[2])
assertEqualHashes(t, featureCommits[1], res[3])
// Create a similar branch to "feature" on a forked repository and GetDotDotRevisions using that as well.
forkEnv := mustForkDB(t, dEnv.DoltDB, "feature", featureCommits[4])
@@ -170,9 +197,9 @@ func TestGetDotDotRevisions(t *testing.T) {
mainHash = mustGetHash(t, mainCommits[6])
featurePreMergeHash = mustGetHash(t, featureCommits[3])
res, err = GetDotDotRevisions(context.Background(), dEnv.DoltDB, featureHash, dEnv.DoltDB, mainHash, 100)
res, err = GetDotDotRevisions(context.Background(), dEnv.DoltDB, []hash.Hash{featureHash}, dEnv.DoltDB, []hash.Hash{mainHash}, 100)
require.Error(t, err)
res, err = GetDotDotRevisions(context.Background(), forkEnv.DoltDB, featureHash, dEnv.DoltDB, mainHash, 100)
res, err = GetDotDotRevisions(context.Background(), forkEnv.DoltDB, []hash.Hash{featureHash}, dEnv.DoltDB, []hash.Hash{mainHash}, 100)
require.NoError(t, err)
assert.Len(t, res, 7)
assertEqualHashes(t, featureCommits[7], res[0])
@@ -183,27 +210,27 @@ func TestGetDotDotRevisions(t *testing.T) {
assertEqualHashes(t, featureCommits[2], res[5])
assertEqualHashes(t, featureCommits[1], res[6])
res, err = GetDotDotRevisions(context.Background(), dEnv.DoltDB, mainHash, dEnv.DoltDB, featureHash, 100)
res, err = GetDotDotRevisions(context.Background(), dEnv.DoltDB, []hash.Hash{mainHash}, dEnv.DoltDB, []hash.Hash{featureHash}, 100)
require.Error(t, err)
res, err = GetDotDotRevisions(context.Background(), dEnv.DoltDB, mainHash, forkEnv.DoltDB, featureHash, 100)
res, err = GetDotDotRevisions(context.Background(), dEnv.DoltDB, []hash.Hash{mainHash}, forkEnv.DoltDB, []hash.Hash{featureHash}, 100)
require.NoError(t, err)
assert.Len(t, res, 0)
res, err = GetDotDotRevisions(context.Background(), forkEnv.DoltDB, featureHash, dEnv.DoltDB, mainHash, 3)
res, err = GetDotDotRevisions(context.Background(), forkEnv.DoltDB, []hash.Hash{featureHash}, dEnv.DoltDB, []hash.Hash{mainHash}, 3)
require.NoError(t, err)
assert.Len(t, res, 3)
assertEqualHashes(t, featureCommits[7], res[0])
assertEqualHashes(t, featureCommits[6], res[1])
assertEqualHashes(t, featureCommits[5], res[2])
res, err = GetDotDotRevisions(context.Background(), dEnv.DoltDB, featurePreMergeHash, dEnv.DoltDB, mainHash, 3)
res, err = GetDotDotRevisions(context.Background(), dEnv.DoltDB, []hash.Hash{featurePreMergeHash}, dEnv.DoltDB, []hash.Hash{mainHash}, 3)
require.NoError(t, err)
assert.Len(t, res, 3)
assertEqualHashes(t, featureCommits[3], res[0])
assertEqualHashes(t, featureCommits[2], res[1])
assertEqualHashes(t, featureCommits[1], res[2])
res, err = GetDotDotRevisions(context.Background(), forkEnv.DoltDB, featurePreMergeHash, dEnv.DoltDB, mainHash, 3)
res, err = GetDotDotRevisions(context.Background(), forkEnv.DoltDB, []hash.Hash{featurePreMergeHash}, dEnv.DoltDB, []hash.Hash{mainHash}, 3)
require.NoError(t, err)
assert.Len(t, res, 3)
assertEqualHashes(t, featureCommits[3], res[0])

View File

@@ -37,6 +37,7 @@ import (
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/globalstate"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlutil"
"github.com/dolthub/dolt/go/libraries/doltcore/table/editor"
"github.com/dolthub/dolt/go/store/hash"
)
var ErrInvalidTableName = errors.NewKind("Invalid table name %s. Table names must match the regular expression " + doltdb.TableNameRegexStr)
@@ -512,12 +513,12 @@ func resolveAsOfTime(ctx *sql.Context, ddb *doltdb.DoltDB, head ref.DoltRef, asO
return nil, nil, err
}
hash, err := cm.HashOf()
h, err := cm.HashOf()
if err != nil {
return nil, nil, err
}
cmItr, err := commitwalk.GetTopologicalOrderIterator(ctx, ddb, hash, nil)
cmItr, err := commitwalk.GetTopologicalOrderIterator(ctx, ddb, []hash.Hash{h}, nil)
if err != nil {
return nil, nil, err
}

View File

@@ -24,6 +24,7 @@ import (
"github.com/dolthub/dolt/go/cmd/dolt/cli"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/env/actions/commitwalk"
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
"github.com/dolthub/dolt/go/store/hash"
)
@@ -303,7 +304,7 @@ func (ltf *LogTableFunction) validateRevisionExpressions() error {
return ltf.invalidArgDetailsErr(ltf.revisionExpr, "second revision must exist if first revision contains '^'")
}
if strings.Contains(revisionStr, "..") && strings.HasPrefix(revisionStr, "^") {
return ltf.invalidArgDetailsErr(ltf.revisionExpr, "revision cannot contain both '..' and '^'")
return ltf.invalidArgDetailsErr(ltf.revisionExpr, "revision cannot contain both '..' or '...' and '^'")
}
}
@@ -313,10 +314,10 @@ func (ltf *LogTableFunction) validateRevisionExpressions() error {
return sql.ErrInvalidArgumentDetails.New(ltf.FunctionName(), ltf.secondRevisionExpr.String())
}
if strings.Contains(secondRevisionStr, "..") {
return ltf.invalidArgDetailsErr(ltf.secondRevisionExpr, "second revision cannot contain '..'")
return ltf.invalidArgDetailsErr(ltf.secondRevisionExpr, "second revision cannot contain '..' or '...'")
}
if strings.Contains(revisionStr, "..") {
return ltf.invalidArgDetailsErr(ltf.revisionExpr, "revision cannot contain '..' if second revision exists")
return ltf.invalidArgDetailsErr(ltf.revisionExpr, "revision cannot contain '..' or '...' if second revision exists")
}
}
@@ -334,7 +335,7 @@ func (ltf *LogTableFunction) validateRevisionExpressions() error {
return ltf.invalidArgDetailsErr(ltf.revisionExpr, "must have revision in order to use --not")
}
if ltf.revisionExpr != nil && (strings.Contains(revisionStr, "..") || strings.HasPrefix(revisionStr, "^")) {
return ltf.invalidArgDetailsErr(ltf.revisionExpr, "cannot use --not if '..' or '^' present in revision")
return ltf.invalidArgDetailsErr(ltf.revisionExpr, "cannot use --not if dots or '^' present in revision")
}
if ltf.secondRevisionExpr != nil && strings.HasPrefix(secondRevisionStr, "^") {
return ltf.invalidArgDetailsErr(ltf.secondRevisionExpr, "cannot use --not if '^' present in second revision")
@@ -352,7 +353,7 @@ func (ltf *LogTableFunction) validateRevisionExpressions() error {
// RowIter implements the sql.Node interface
func (ltf *LogTableFunction) RowIter(ctx *sql.Context, row sql.Row) (sql.RowIter, error) {
revisionVal, excludingRevisionVal, err := ltf.evaluateArguments()
revisionVal, secondRevisionVal, threeDot, err := ltf.evaluateArguments()
if err != nil {
return nil, err
}
@@ -392,18 +393,40 @@ func (ltf *LogTableFunction) RowIter(ctx *sql.Context, row sql.Row) (sql.RowIter
return nil, err
}
// Two dot log
if len(excludingRevisionVal) > 0 {
exCs, err := doltdb.NewCommitSpec(excludingRevisionVal)
// Two and three dot log
if len(secondRevisionVal) > 0 {
secondCs, err := doltdb.NewCommitSpec(secondRevisionVal)
if err != nil {
return nil, err
}
excludingCommit, err := sqledb.ddb.Resolve(ctx, exCs, nil)
secondCommit, err := sqledb.ddb.Resolve(ctx, secondCs, nil)
if err != nil {
return nil, err
}
return ltf.NewDotDotLogTableFunctionRowIter(ctx, sqledb.ddb, commit, excludingCommit, matchFunc, cHashToRefs)
if threeDot {
mergeBase, err := merge.MergeBase(ctx, commit, secondCommit)
if err != nil {
return nil, err
}
mergeCs, err := doltdb.NewCommitSpec(mergeBase.String())
if err != nil {
return nil, err
}
// Use merge base as excluding commit
mergeCommit, err := sqledb.ddb.Resolve(ctx, mergeCs, nil)
if err != nil {
return nil, err
}
return ltf.NewDotDotLogTableFunctionRowIter(ctx, sqledb.ddb, []*doltdb.Commit{commit, secondCommit}, mergeCommit, matchFunc, cHashToRefs)
}
return ltf.NewDotDotLogTableFunctionRowIter(ctx, sqledb.ddb, []*doltdb.Commit{commit}, secondCommit, matchFunc, cHashToRefs)
}
return ltf.NewLogTableFunctionRowIter(ctx, sqledb.ddb, commit, matchFunc, cHashToRefs)
@@ -455,39 +478,40 @@ func getCommitHashToRefs(ctx *sql.Context, ddb *doltdb.DoltDB, decoration string
return cHashToRefs, nil
}
// evaluateArguments returns revisionValStr and excludingRevisionValStr.
// evaluateArguments returns revisionValStr, secondRevisionValStr, and three dot boolean.
// It evaluates the argument expressions to turn them into values this LogTableFunction
// can use. Note that this method only evals the expressions, and doesn't validate the values.
func (ltf *LogTableFunction) evaluateArguments() (string, string, error) {
func (ltf *LogTableFunction) evaluateArguments() (string, string, bool, error) {
var revisionValStr string
var excludingRevisionValStr string
var secondRevisionValStr string
var err error
threeDot := false
if ltf.revisionExpr != nil {
revisionValStr, excludingRevisionValStr, err = getRevisionsFromExpr(ltf.ctx, ltf.revisionExpr, true)
revisionValStr, secondRevisionValStr, threeDot, err = getRevisionsFromExpr(ltf.ctx, ltf.revisionExpr, true)
if err != nil {
return "", "", err
return "", "", false, err
}
}
if ltf.secondRevisionExpr != nil {
rvs, ervs, err := getRevisionsFromExpr(ltf.ctx, ltf.secondRevisionExpr, false)
rvs, srvs, _, err := getRevisionsFromExpr(ltf.ctx, ltf.secondRevisionExpr, false)
if err != nil {
return "", "", err
return "", "", false, err
}
if len(rvs) > 0 {
revisionValStr = rvs
}
if len(ervs) > 0 {
excludingRevisionValStr = ervs
if len(srvs) > 0 {
secondRevisionValStr = srvs
}
}
if len(ltf.notRevision) > 0 {
excludingRevisionValStr = ltf.notRevision
secondRevisionValStr = ltf.notRevision
}
return revisionValStr, excludingRevisionValStr, nil
return revisionValStr, secondRevisionValStr, threeDot, nil
}
func mustExpressionToString(ctx *sql.Context, expr sql.Expression) string {
@@ -512,23 +536,28 @@ func expressionToString(ctx *sql.Context, expr sql.Expression) (string, error) {
return valStr, nil
}
// Gets revisionName and/or excludingRevisionName from sql expression
func getRevisionsFromExpr(ctx *sql.Context, expr sql.Expression, canDot bool) (string, string, error) {
// getRevisionsFromExpr returns the revisionName and/or secondRevisionName, as
// well as a threeDot boolean from sql expression
func getRevisionsFromExpr(ctx *sql.Context, expr sql.Expression, canDot bool) (string, string, bool, error) {
revisionValStr, err := expressionToString(ctx, expr)
if err != nil {
return "", "", err
return "", "", false, err
}
if canDot && strings.Contains(revisionValStr, "..") {
if strings.Contains(revisionValStr, "...") {
refs := strings.Split(revisionValStr, "...")
return refs[0], refs[1], true, nil
}
refs := strings.Split(revisionValStr, "..")
return refs[1], refs[0], nil
return refs[1], refs[0], false, nil
}
if strings.HasPrefix(revisionValStr, "^") {
return "", strings.TrimPrefix(revisionValStr, "^"), nil
return "", strings.TrimPrefix(revisionValStr, "^"), false, nil
}
return revisionValStr, "", nil
return revisionValStr, "", false, nil
}
//------------------------------------
@@ -547,12 +576,12 @@ type logTableFunctionRowIter struct {
}
func (ltf *LogTableFunction) NewLogTableFunctionRowIter(ctx *sql.Context, ddb *doltdb.DoltDB, commit *doltdb.Commit, matchFn func(*doltdb.Commit) (bool, error), cHashToRefs map[hash.Hash][]string) (*logTableFunctionRowIter, error) {
hash, err := commit.HashOf()
h, err := commit.HashOf()
if err != nil {
return nil, err
}
child, err := commitwalk.GetTopologicalOrderIterator(ctx, ddb, hash, matchFn)
child, err := commitwalk.GetTopologicalOrderIterator(ctx, ddb, []hash.Hash{h}, matchFn)
if err != nil {
return nil, err
}
@@ -562,14 +591,19 @@ func (ltf *LogTableFunction) NewLogTableFunctionRowIter(ctx *sql.Context, ddb *d
showParents: ltf.showParents,
decoration: ltf.decoration,
cHashToRefs: cHashToRefs,
headHash: hash,
headHash: h,
}, nil
}
func (ltf *LogTableFunction) NewDotDotLogTableFunctionRowIter(ctx *sql.Context, ddb *doltdb.DoltDB, commit, excludingCommit *doltdb.Commit, matchFn func(*doltdb.Commit) (bool, error), cHashToRefs map[hash.Hash][]string) (*logTableFunctionRowIter, error) {
hash, err := commit.HashOf()
if err != nil {
return nil, err
func (ltf *LogTableFunction) NewDotDotLogTableFunctionRowIter(ctx *sql.Context, ddb *doltdb.DoltDB, commits []*doltdb.Commit, excludingCommit *doltdb.Commit, matchFn func(*doltdb.Commit) (bool, error), cHashToRefs map[hash.Hash][]string) (*logTableFunctionRowIter, error) {
hashes := make([]hash.Hash, len(commits))
for i, commit := range commits {
h, err := commit.HashOf()
if err != nil {
return nil, err
}
hashes[i] = h
}
exHash, err := excludingCommit.HashOf()
@@ -577,17 +611,23 @@ func (ltf *LogTableFunction) NewDotDotLogTableFunctionRowIter(ctx *sql.Context,
return nil, err
}
child, err := commitwalk.GetDotDotRevisionsIterator(ctx, ddb, hash, exHash, matchFn)
child, err := commitwalk.GetDotDotRevisionsIterator(ctx, ddb, hashes, ddb, []hash.Hash{exHash}, matchFn)
if err != nil {
return nil, err
}
var headHash hash.Hash
if len(hashes) == 1 {
headHash = hashes[0]
}
return &logTableFunctionRowIter{
child: child,
showParents: ltf.showParents,
decoration: ltf.decoration,
cHashToRefs: cHashToRefs,
headHash: hash,
headHash: headHash,
}, nil
}

View File

@@ -18,6 +18,7 @@ import (
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/dolt/go/libraries/doltcore/env/actions/commitwalk"
"github.com/dolthub/dolt/go/store/hash"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/index"
@@ -81,12 +82,12 @@ type LogItr struct {
// NewLogItr creates a LogItr from the current environment.
func NewLogItr(ctx *sql.Context, ddb *doltdb.DoltDB, head *doltdb.Commit) (*LogItr, error) {
hash, err := head.HashOf()
h, err := head.HashOf()
if err != nil {
return nil, err
}
child, err := commitwalk.GetTopologicalOrderIterator(ctx, ddb, hash, nil)
child, err := commitwalk.GetTopologicalOrderIterator(ctx, ddb, []hash.Hash{h}, nil)
if err != nil {
return nil, err
}

View File

@@ -5198,14 +5198,26 @@ var LogTableFunctionScriptTests = []queries.ScriptTest{
Query: "SELECT * from dolt_log('^main..branch1');",
ExpectedErr: sql.ErrInvalidArgumentDetails,
},
{
Query: "SELECT * from dolt_log('^main...branch1');",
ExpectedErr: sql.ErrInvalidArgumentDetails,
},
{
Query: "SELECT * from dolt_log(@Commit1, 'main..branch1');",
ExpectedErr: sql.ErrInvalidArgumentDetails,
},
{
Query: "SELECT * from dolt_log(@Commit1, 'main...branch1');",
ExpectedErr: sql.ErrInvalidArgumentDetails,
},
{
Query: "SELECT * from dolt_log('main..branch1', '--not', @Commit1);",
ExpectedErr: sql.ErrInvalidArgumentDetails,
},
{
Query: "SELECT * from dolt_log('main...branch1', '--not', @Commit1);",
ExpectedErr: sql.ErrInvalidArgumentDetails,
},
{
Query: "SELECT * from dolt_log('^main', '--not', @Commit1);",
ExpectedErr: sql.ErrInvalidArgumentDetails,
@@ -5366,30 +5378,39 @@ var LogTableFunctionScriptTests = []queries.ScriptTest{
"insert into t values (5, 'five', 'six');",
"set @Commit5 = dolt_commit('-am', 'inserting into t 5');",
},
/* Commit graph:
3 - 4 (new-branch)
/
0 - 1 - 2 - 5 (main)
*/
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT count(*) from dolt_log('^main', 'new-branch');",
Expected: []sql.Row{{2}},
Expected: []sql.Row{{2}}, // 4, 3
},
{
Query: "SELECT count(*) from dolt_log('main..new-branch');",
Expected: []sql.Row{{2}},
Expected: []sql.Row{{2}}, // 4, 3
},
{
Query: "SELECT count(*) from dolt_log('main...new-branch');",
Expected: []sql.Row{{3}}, // 5, 4, 3
},
{
Query: "SELECT count(*) from dolt_log('new-branch', '--not', 'main');",
Expected: []sql.Row{{2}},
Expected: []sql.Row{{2}}, // 4, 3
},
{
Query: "SELECT count(*) from dolt_log('new-branch', '^main');",
Expected: []sql.Row{{2}},
Expected: []sql.Row{{2}}, // 4, 3
},
{
Query: "SELECT count(*) from dolt_log('^new-branch', 'main');",
Expected: []sql.Row{{1}},
Expected: []sql.Row{{1}}, // 5
},
{
Query: "SELECT count(*) from dolt_log('main', '--not', 'new-branch');",
Expected: []sql.Row{{1}},
Expected: []sql.Row{{1}}, // 5
},
{
Query: "SELECT count(*) from dolt_log('^main', 'main');",
@@ -5399,6 +5420,10 @@ var LogTableFunctionScriptTests = []queries.ScriptTest{
Query: "SELECT count(*) from dolt_log('main..main');",
Expected: []sql.Row{{0}},
},
{
Query: "SELECT count(*) from dolt_log('main...main');",
Expected: []sql.Row{{0}},
},
{
Query: "SELECT count(*) from dolt_log('main', '--not', 'main');",
Expected: []sql.Row{{0}},
@@ -5409,7 +5434,7 @@ var LogTableFunctionScriptTests = []queries.ScriptTest{
},
{
Query: "SELECT count(*) from dolt_log('^main^', 'main');",
Expected: []sql.Row{{1}},
Expected: []sql.Row{{1}}, // 5
},
{
Query: "SELECT count(*) from dolt_log('^main', 'main^');",
@@ -5421,15 +5446,15 @@ var LogTableFunctionScriptTests = []queries.ScriptTest{
},
{
Query: "SELECT count(*) from dolt_log('^new-branch', @Commit5);",
Expected: []sql.Row{{1}},
Expected: []sql.Row{{1}}, // 5
},
{
Query: "SELECT count(*) from dolt_log(@Commit3, '--not', @Commit2);",
Expected: []sql.Row{{1}},
Expected: []sql.Row{{1}}, // 3
},
{
Query: "SELECT count(*) from dolt_log(@Commit4, '--not', @Commit2);",
Expected: []sql.Row{{2}},
Expected: []sql.Row{{2}}, // 4, 3
},
},
},
@@ -5492,6 +5517,11 @@ var LogTableFunctionScriptTests = []queries.ScriptTest{
"insert into t values (5, 'five', 'six');",
"set @Commit5 = dolt_commit('-am', 'inserting into t 5');",
},
/* Commit graph:
3 - 4 (new-branch)
/
0 - 1 - 2 - 5 (main)
*/
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT commit_hash = @Commit4, commit_hash = @Commit3, committer, email, message from dolt_log('^main', 'new-branch');",
@@ -5507,6 +5537,14 @@ var LogTableFunctionScriptTests = []queries.ScriptTest{
{false, true, "John Doe", "johndoe@example.com", "inserting into t 3"},
},
},
{
Query: "SELECT commit_hash = @Commit5, commit_hash = @Commit4, commit_hash = @Commit3, committer, email, message from dolt_log('main...new-branch');",
Expected: []sql.Row{
{true, false, false, "billy bob", "bigbillieb@fake.horse", "inserting into t 5"},
{false, true, false, "John Doe", "johndoe@example.com", "inserting into t 4"},
{false, false, true, "John Doe", "johndoe@example.com", "inserting into t 3"},
},
},
{
Query: "SELECT commit_hash = @Commit4, commit_hash = @Commit3, committer, email, message from dolt_log('new-branch', '--not', 'main');",
Expected: []sql.Row{

View File

@@ -22,9 +22,10 @@ import (
"github.com/stretchr/testify/require"
)
var forceOpt = &Option{"force", "f", "", OptionalFlag, "force desc", nil}
var messageOpt = &Option{"message", "m", "msg", OptionalValue, "msg desc", nil}
var fileTypeOpt = &Option{"file-type", "", "", OptionalValue, "file type", nil}
var forceOpt = &Option{"force", "f", "", OptionalFlag, "force desc", nil, false}
var messageOpt = &Option{"message", "m", "msg", OptionalValue, "msg desc", nil, false}
var fileTypeOpt = &Option{"file-type", "", "", OptionalValue, "file type", nil, false}
var notOpt = &Option{"not", "", "", OptionalValue, "not desc", nil, true}
func TestParsing(t *testing.T) {
tests := []struct {
@@ -158,6 +159,13 @@ func TestParsing(t *testing.T) {
expectedOpts: map[string]string{"message": "f"},
expectedArgs: []string{"value"},
},
{
name: "--not string list value",
options: []*Option{forceOpt, messageOpt, notOpt},
args: []string{"-mf", "value", "--not", "main", "branch"},
expectedOpts: map[string]string{"message": "f", "not": "main,branch"},
expectedArgs: []string{"value"},
},
{
name: "-fm value",
options: []*Option{forceOpt, messageOpt},

View File

@@ -63,4 +63,6 @@ type Option struct {
Desc string
// Function to validate an Option after parsing, returning any error.
Validator ValidationFunc
// Allows more than one arg to an Option.
AllowMultipleOptions bool
}

View File

@@ -89,7 +89,7 @@ func (ap *ArgParser) SupportOption(opt *Option) {
// SupportsFlag adds support for a new flag (argument with no value). See SupportOpt for details on params.
func (ap *ArgParser) SupportsFlag(name, abbrev, desc string) *ArgParser {
opt := &Option{name, abbrev, "", OptionalFlag, desc, nil}
opt := &Option{name, abbrev, "", OptionalFlag, desc, nil, false}
ap.SupportOption(opt)
return ap
@@ -97,7 +97,15 @@ func (ap *ArgParser) SupportsFlag(name, abbrev, desc string) *ArgParser {
// SupportsString adds support for a new string argument with the description given. See SupportOpt for details on params.
func (ap *ArgParser) SupportsString(name, abbrev, valDesc, desc string) *ArgParser {
opt := &Option{name, abbrev, valDesc, OptionalValue, desc, nil}
opt := &Option{name, abbrev, valDesc, OptionalValue, desc, nil, false}
ap.SupportOption(opt)
return ap
}
// SupportsStringList adds support for a new string list argument with the description given. See SupportOpt for details on params.
func (ap *ArgParser) SupportsStringList(name, abbrev, valDesc, desc string) *ArgParser {
opt := &Option{name, abbrev, valDesc, OptionalValue, desc, nil, true}
ap.SupportOption(opt)
return ap
@@ -105,7 +113,7 @@ func (ap *ArgParser) SupportsString(name, abbrev, valDesc, desc string) *ArgPars
// SupportsOptionalString adds support for a new string argument with the description given and optional empty value.
func (ap *ArgParser) SupportsOptionalString(name, abbrev, valDesc, desc string) *ArgParser {
opt := &Option{name, abbrev, valDesc, OptionalEmptyValue, desc, nil}
opt := &Option{name, abbrev, valDesc, OptionalEmptyValue, desc, nil, false}
ap.SupportOption(opt)
return ap
@@ -113,7 +121,7 @@ func (ap *ArgParser) SupportsOptionalString(name, abbrev, valDesc, desc string)
// SupportsValidatedString adds support for a new string argument with the description given and defined validation function.
func (ap *ArgParser) SupportsValidatedString(name, abbrev, valDesc, desc string, validator ValidationFunc) *ArgParser {
opt := &Option{name, abbrev, valDesc, OptionalValue, desc, validator}
opt := &Option{name, abbrev, valDesc, OptionalValue, desc, validator, false}
ap.SupportOption(opt)
return ap
@@ -121,7 +129,7 @@ func (ap *ArgParser) SupportsValidatedString(name, abbrev, valDesc, desc string,
// SupportsUint adds support for a new uint argument with the description given. See SupportOpt for details on params.
func (ap *ArgParser) SupportsUint(name, abbrev, valDesc, desc string) *ArgParser {
opt := &Option{name, abbrev, valDesc, OptionalValue, desc, isUintStr}
opt := &Option{name, abbrev, valDesc, OptionalValue, desc, isUintStr, false}
ap.SupportOption(opt)
return ap
@@ -129,7 +137,7 @@ func (ap *ArgParser) SupportsUint(name, abbrev, valDesc, desc string) *ArgParser
// SupportsInt adds support for a new int argument with the description given. See SupportOpt for details on params.
func (ap *ArgParser) SupportsInt(name, abbrev, valDesc, desc string) *ArgParser {
opt := &Option{name, abbrev, valDesc, OptionalValue, desc, isIntStr}
opt := &Option{name, abbrev, valDesc, OptionalValue, desc, isIntStr, false}
ap.SupportOption(opt)
return ap
@@ -280,9 +288,14 @@ func (ap *ArgParser) Parse(args []string) (*ArgParseResults, error) {
return nil, errors.New("error: no value for option `" + opt.Name + "'")
}
} else {
valueStr = args[i]
if opt.AllowMultipleOptions {
list := getListValues(args[i:])
valueStr = strings.Join(list, ",")
i += len(list) - 1
} else {
valueStr = args[i]
}
}
value = &valueStr
}
@@ -303,3 +316,17 @@ func (ap *ArgParser) Parse(args []string) (*ArgParseResults, error) {
return &ArgParseResults{results, list, ap}, nil
}
func getListValues(args []string) []string {
var values []string
for _, arg := range args {
// Stop if another option found
if arg[0] == '-' || arg == "--" {
return values
}
values = append(values, arg)
}
return values
}

View File

@@ -17,6 +17,7 @@ package argparser
import (
"math"
"strconv"
"strings"
"github.com/dolthub/dolt/go/libraries/utils/set"
)
@@ -96,6 +97,11 @@ func (res *ArgParseResults) GetValue(name string) (string, bool) {
return val, ok
}
func (res *ArgParseResults) GetValueList(name string) ([]string, bool) {
val, ok := res.options[name]
return strings.Split(val, ","), ok
}
func (res *ArgParseResults) GetValues(names ...string) map[string]string {
vals := make(map[string]string)

View File

@@ -363,7 +363,7 @@ func findCommonAncestorUsingParentsList(ctx context.Context, c1, c2 *Commit, vr1
// FindCommonAncestor returns the most recent common ancestor of c1 and c2, if
// one exists, setting ok to true. If there is no common ancestor, ok is set
// to false. Refs of |c1| are dereferenced through |vr1|, while refs of |c2|
// are dereference through |vr2|.
// are dereferenced through |vr2|.
//
// This implementation makes use of the parents_closure field on the commit
// struct. If the commit does not have a materialized parents_closure, this

View File

@@ -40,101 +40,200 @@ teardown() {
[[ ! "$output" =~ "BRANCH1" ]] || false
}
@test "log: two dot log" {
@test "log: two and three dot log" {
dolt sql -q "create table testtable (pk int PRIMARY KEY)"
dolt add .
dolt commit -m "commit 1 MAIN"
dolt commit --allow-empty -m "commit 2 MAIN"
dolt checkout -b branch1
dolt commit --allow-empty -m "commit 1 BRANCH1"
dolt commit --allow-empty -m "commit 2 BRANCH1"
dolt commit --allow-empty -m "commit 3 BRANCH1"
dolt checkout -b branchA
dolt commit --allow-empty -m "commit 1 BRANCHA"
dolt commit --allow-empty -m "commit 2 BRANCHA"
dolt checkout -b branchB
dolt commit --allow-empty -m "commit 1 BRANCHB"
dolt checkout branchA
dolt commit --allow-empty -m "commit 3 BRANCHA"
run dolt log branch1
run dolt log branchA
[ $status -eq 0 ]
[[ "$output" =~ "MAIN" ]] || false
[[ "$output" =~ "BRANCH1" ]] || false
[[ "$output" =~ "BRANCHA" ]] || false
run dolt log main
[ $status -eq 0 ]
[[ "$output" =~ "MAIN" ]] || false
[[ ! "$output" =~ "BRANCH1" ]] || false
[[ ! "$output" =~ "BRANCHA" ]] || false
dolt checkout main
dolt commit --allow-empty -m "commit 3 AFTER"
# # # # # # # # # # # # # # # # # # # # # # #
# #
# 1B (branchB) #
# / #
# 1A - 2A - 3A (branchA) #
# / #
# (init) - 1M - 2M - 3M (main) #
# #
# # # # # # # # # # # # # # # # # # # # # # #
# Valid two dot
run dolt log main..branch1
run dolt log main..branchA
[ $status -eq 0 ]
[[ ! "$output" =~ "MAIN" ]] || false
[[ ! "$output" =~ "AFTER" ]] || false
[[ "$output" =~ "BRANCH1" ]] || false
run dolt log ^main branch1
[[ "$output" =~ "BRANCHA" ]] || false
run dolt log ^main branchA
[ $status -eq 0 ]
[[ ! "$output" =~ "MAIN" ]] || false
[[ ! "$output" =~ "AFTER" ]] || false
[[ "$output" =~ "BRANCH1" ]] || false
run dolt log branch1 ^main
[[ "$output" =~ "BRANCHA" ]] || false
run dolt log branchA ^main
[ $status -eq 0 ]
[[ ! "$output" =~ "MAIN" ]] || false
[[ ! "$output" =~ "AFTER" ]] || false
[[ "$output" =~ "BRANCH1" ]] || false
run dolt log branch1 --not main
[[ "$output" =~ "BRANCHA" ]] || false
run dolt log branchA --not main
[ $status -eq 0 ]
[[ ! "$output" =~ "MAIN" ]] || false
[[ ! "$output" =~ "AFTER" ]] || false
[[ "$output" =~ "BRANCH1" ]] || false
run dolt log branch1..main
[[ "$output" =~ "BRANCHA" ]] || false
run dolt log branchA..main
[ $status -eq 0 ]
[[ ! "$output" =~ "MAIN" ]] || false
[[ "$output" =~ "AFTER" ]] || false
[[ ! "$output" =~ "BRANCH1" ]] || false
[[ ! "$output" =~ "BRANCHA" ]] || false
run dolt log main..main
[ $status -eq 0 ]
run dolt log main^..branch1
run dolt log main^..branchA
[ $status -eq 0 ]
[[ ! "$output" =~ "MAIN" ]] || false
[[ ! "$output" =~ "AFTER" ]] || false
[[ "$output" =~ "BRANCH1" ]] || false
run dolt log ^main^ branch1
[[ "$output" =~ "BRANCHA" ]] || false
run dolt log ^main^ branchA
[ $status -eq 0 ]
[[ ! "$output" =~ "MAIN" ]] || false
[[ ! "$output" =~ "AFTER" ]] || false
[[ "$output" =~ "BRANCH1" ]] || false
run dolt log branch1 --not main^
[[ "$output" =~ "BRANCHA" ]] || false
run dolt log branchA --not main^
[ $status -eq 0 ]
[[ ! "$output" =~ "MAIN" ]] || false
[[ ! "$output" =~ "AFTER" ]] || false
[[ "$output" =~ "BRANCH1" ]] || false
[[ "$output" =~ "BRANCHA" ]] || false
run dolt log ^main ^branchA
[ $status -eq 0 ]
# Invalid two dot
run dolt log main..branch1 testtable
# Valid three dot
run dolt log main...branchA
[ $status -eq 0 ]
[[ ! "$output" =~ "MAIN" ]] || false
[[ "$output" =~ "AFTER" ]] || false
[[ "$output" =~ "BRANCHA" ]] || false
run dolt log branchA...main
[ $status -eq 0 ]
[[ ! "$output" =~ "MAIN" ]] || false
[[ "$output" =~ "AFTER" ]] || false
[[ "$output" =~ "BRANCHA" ]] || false
run dolt log main branchA --not $(dolt merge-base main branchA)
[ $status -eq 0 ]
[[ ! "$output" =~ "MAIN" ]] || false
[[ "$output" =~ "AFTER" ]] || false
[[ "$output" =~ "BRANCHA" ]] || false
run dolt log branchB...branchA
[ $status -eq 0 ]
[[ ! "$output" =~ "MAIN" ]] || false
[[ ! "$output" =~ "AFTER" ]] || false
[[ "$output" =~ "BRANCHA" ]] || false
[[ "$output" =~ "BRANCHB" ]] || false
# Multiple refs
run dolt log branchB branchA
[ $status -eq 0 ]
[[ "$output" =~ "MAIN" ]] || false
[[ ! "$output" =~ "AFTER" ]] || false
[[ "$output" =~ "BRANCHA" ]] || false
[[ "$output" =~ "BRANCHB" ]] || false
run dolt log main branchA
[ $status -eq 0 ]
[[ "$output" =~ "MAIN" ]] || false
[[ "$output" =~ "AFTER" ]] || false
[[ "$output" =~ "BRANCHA" ]] || false
[[ ! "$output" =~ "BRANCHB" ]] || false
run dolt log main branchB branchA
[ $status -eq 0 ]
[[ "$output" =~ "MAIN" ]] || false
[[ "$output" =~ "AFTER" ]] || false
[[ "$output" =~ "BRANCHA" ]] || false
[[ "$output" =~ "BRANCHB" ]] || false
run dolt log branchB main ^branchA
[ $status -eq 0 ]
[[ ! "$output" =~ "MAIN" ]] || false
[[ "$output" =~ "AFTER" ]] || false
[[ ! "$output" =~ "BRANCHA" ]] || false
[[ "$output" =~ "BRANCHB" ]] || false
run dolt log branchB main --not branchA
[ $status -eq 0 ]
[[ ! "$output" =~ "MAIN" ]] || false
[[ "$output" =~ "AFTER" ]] || false
[[ ! "$output" =~ "BRANCHA" ]] || false
[[ "$output" =~ "BRANCHB" ]] || false
run dolt log branchB main --not branchA --oneline
[ $status -eq 0 ]
[[ ! "$output" =~ "MAIN" ]] || false
[[ "$output" =~ "AFTER" ]] || false
[[ ! "$output" =~ "BRANCHA" ]] || false
[[ "$output" =~ "BRANCHB" ]] || false
run dolt log branchB main ^branchA ^main
[ $status -eq 0 ]
[[ ! "$output" =~ "MAIN" ]] || false
[[ ! "$output" =~ "AFTER" ]] || false
[[ ! "$output" =~ "BRANCHA" ]] || false
[[ "$output" =~ "BRANCHB" ]] || false
run dolt log branchB main --not branchA main
[ $status -eq 0 ]
[[ ! "$output" =~ "MAIN" ]] || false
[[ ! "$output" =~ "AFTER" ]] || false
[[ ! "$output" =~ "BRANCHA" ]] || false
[[ "$output" =~ "BRANCHB" ]] || false
run dolt log branchB main --not branchA main --oneline
[ $status -eq 0 ]
[[ ! "$output" =~ "MAIN" ]] || false
[[ ! "$output" =~ "AFTER" ]] || false
[[ ! "$output" =~ "BRANCHA" ]] || false
[[ "$output" =~ "BRANCHB" ]] || false
# Invalid
run dolt log main..branchA testtable
[ $status -eq 1 ]
run dolt log testtable main..branch1
run dolt log testtable main..branchA
[ $status -eq 1 ]
run dolt log ^main branch1 testtable
run dolt log main...branchA testtable
[ $status -eq 1 ]
run dolt log branch1 testtable --not main
run dolt log testtable main...branchA
[ $status -eq 1 ]
run dolt log main..branch1 main
run dolt log ^main branchA testtable
[ $status -eq 1 ]
run dolt log main main..branch1
run dolt log branchA testtable --not main
[ $status -eq 1 ]
run dolt log ^main ^branch1
run dolt log main..branchA main
[ $status -eq 1 ]
run dolt log main branch1
run dolt log main main..branchA
[ $status -eq 1 ]
run dolt log main...branchA main
[ $status -eq 1 ]
run dolt log main main...branchA
[ $status -eq 1 ]
run dolt log ^main testtable
[ $status -eq 1 ]
run dolt log ^branch1 --not main
run dolt log testtable ^main
[ $status -eq 1 ]
run dolt log main..branch1 --not main
run dolt log ^branchA --not main
[ $status -eq 1 ]
run dolt log branch1 --not main branch1
run dolt log main..branchA --not main
[ $status -eq 1 ]
run dolt log branch1 --not main --not branch1
run dolt log main..branchA --not ^main
[ $status -eq 1 ]
run dolt log main...branch1
run dolt log main...branchA --not main
[ $status -eq 1 ]
run dolt log main...branchA --not ^main
[ $status -eq 1 ]
}

View File

@@ -27,7 +27,7 @@ setup() {
# / #
# / <-- (one) #
# / / #
# (init) -- (A) -- (B) -- (C) <-- (main) #
# (init) -- (A) -- (B) -- (C) <-- (main) #
# \ #
# -- (D) <-- (two) #
# #