-
-
Notifications
You must be signed in to change notification settings - Fork 535
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CLI command for reflog #7032
Merged
Merged
CLI command for reflog #7032
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
adcf3c1
tests for dolt reflog
fccec02
basic dolt reflog implementation
d3e7c7d
add --all for reflog
37158e0
[ga-format-pr] Run go/utils/repofmt/format_repo.sh and go/Godeps/upda…
stephkyou 6ba1d09
change error test output
3e5c6b6
minor test updates
5d24edd
address PR comments
0f2e926
reflog doc update
e9c63ac
another description update
b47eeb4
PR comments
e2f18ee
Merge remote-tracking branch 'origin/main' into steph/reflog
a99b52d
small fix
d73c8fd
Merge branch 'main' into steph/reflog
stephkyou File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
// Copyright 2023 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 ( | ||
"context" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/dolthub/go-mysql-server/sql" | ||
"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" | ||
eventsapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi/v1alpha1" | ||
"github.com/dolthub/dolt/go/libraries/doltcore/env" | ||
"github.com/dolthub/dolt/go/libraries/utils/argparser" | ||
"github.com/dolthub/dolt/go/store/util/outputpager" | ||
) | ||
|
||
var reflogDocs = cli.CommandDocumentationContent{ | ||
ShortDesc: "Shows a history of named refs", | ||
LongDesc: `Shows the history of named refs (e.g. branches and tags), which is useful for understanding how a branch | ||
or tag changed over time to reference different commits, particularly for information not surfaced through {{.EmphasisLeft}}dolt log{{.EmphasisRight}}. | ||
The data from Dolt's reflog comes from [Dolt's journaling chunk store](https://www.dolthub.com/blog/2023-03-08-dolt-chunk-journal/). | ||
This data is local to a Dolt database and never included when pushing, pulling, or cloning a Dolt database. This means when you clone a Dolt database, it will not have any reflog data until you perform operations that change what commit branches or tags reference. | ||
|
||
Dolt's reflog is similar to [Git's reflog](https://git-scm.com/docs/git-reflog), but there are a few differences: | ||
- The Dolt reflog currently only supports named references, such as branches and tags, and not any of Git's special refs (e.g. {{.EmphasisLeft}}HEAD{{.EmphasisRight}}, {{.EmphasisLeft}}FETCH-HEAD{{.EmphasisRight}}, {{.EmphasisLeft}}MERGE-HEAD{{.EmphasisRight}}). | ||
- The Dolt reflog can be queried for the log of references, even after a reference has been deleted. In Git, once a branch or tag is deleted, the reflog for that ref is also deleted and to find the last commit a branch or tag pointed to you have to use Git's special {{.EmphasisLeft}}HEAD{{.EmphasisRight}} reflog to find the commit, which can sometimes be challenging. Dolt makes this much easier by allowing you to see the history for a deleted ref so you can easily see the last commit a branch or tag pointed to before it was deleted.`, | ||
Synopsis: []string{ | ||
`[--all] {{.LessThan}}ref{{.GreaterThan}}`, | ||
}, | ||
} | ||
|
||
type ReflogCmd 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 ReflogCmd) Name() string { | ||
return "reflog" | ||
} | ||
|
||
// Description returns a description of the command | ||
func (cmd ReflogCmd) Description() string { | ||
return "Show history of named refs." | ||
} | ||
|
||
// EventType returns the type of the event to log | ||
func (cmd ReflogCmd) EventType() eventsapi.ClientEventType { | ||
return eventsapi.ClientEventType_REFLOG | ||
} | ||
|
||
func (cmd ReflogCmd) Docs() *cli.CommandDocumentation { | ||
ap := cmd.ArgParser() | ||
return cli.NewCommandDocumentation(reflogDocs, ap) | ||
} | ||
|
||
func (cmd ReflogCmd) ArgParser() *argparser.ArgParser { | ||
return cli.CreateReflogArgParser() | ||
} | ||
|
||
func (cmd ReflogCmd) RequiresRepo() bool { | ||
return false | ||
} | ||
|
||
// Exec executes the command | ||
func (cmd ReflogCmd) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv, cliCtx cli.CliContext) int { | ||
ap := cmd.ArgParser() | ||
help, usage := cli.HelpAndUsagePrinters(cli.CommandDocsForCommandString(commandStr, reflogDocs, ap)) | ||
apr := cli.ParseArgsOrDie(ap, args, help) | ||
|
||
queryist, sqlCtx, closeFunc, err := cliCtx.QueryEngine(ctx) | ||
if err != nil { | ||
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage) | ||
} | ||
if closeFunc != nil { | ||
defer closeFunc() | ||
} | ||
|
||
query, err := constructInterpolatedDoltReflogQuery(apr) | ||
if err != nil { | ||
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage) | ||
} | ||
|
||
rows, err := GetRowsForSql(queryist, sqlCtx, query) | ||
if err != nil { | ||
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage) | ||
} | ||
|
||
return printReflog(rows, queryist, sqlCtx) | ||
} | ||
|
||
// constructInterpolatedDoltReflogQuery generates the sql query necessary to call the DOLT_REFLOG() function. | ||
// Also interpolates this query to prevent sql injection | ||
func constructInterpolatedDoltReflogQuery(apr *argparser.ArgParseResults) (string, error) { | ||
var params []interface{} | ||
var args []string | ||
|
||
if apr.NArg() == 1 { | ||
params = append(params, apr.Arg(0)) | ||
args = append(args, "?") | ||
} | ||
if apr.Contains(cli.AllFlag) { | ||
args = append(args, "'--all'") | ||
} | ||
|
||
query := fmt.Sprintf("SELECT ref, commit_hash, commit_message FROM DOLT_REFLOG(%s)", strings.Join(args, ", ")) | ||
interpolatedQuery, err := dbr.InterpolateForDialect(query, params, dialect.MySQL) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return interpolatedQuery, nil | ||
} | ||
|
||
type ReflogInfo struct { | ||
ref string | ||
commitHash string | ||
commitMessage string | ||
} | ||
|
||
// printReflog takes a list of sql rows with columns ref, commit hash, commit message. Prints the reflog to stdout | ||
func printReflog(rows []sql.Row, queryist cli.Queryist, sqlCtx *sql.Context) int { | ||
var reflogInfo []ReflogInfo | ||
|
||
// Get the hash of HEAD for the `HEAD ->` decoration | ||
headHash := "" | ||
res, err := GetRowsForSql(queryist, sqlCtx, "SELECT hashof('HEAD')") | ||
if err == nil { | ||
// still print the reflog even if we can't get the hash | ||
headHash = res[0][0].(string) | ||
} | ||
|
||
for _, row := range rows { | ||
ref := row[0].(string) | ||
commitHash := row[1].(string) | ||
commitMessage := row[2].(string) | ||
reflogInfo = append(reflogInfo, ReflogInfo{ref, commitHash, commitMessage}) | ||
} | ||
|
||
reflogToStdOut(reflogInfo, headHash) | ||
|
||
return 0 | ||
} | ||
|
||
// reflogToStdOut takes a list of ReflogInfo and prints the reflog to stdout | ||
func reflogToStdOut(reflogInfo []ReflogInfo, headHash string) { | ||
if cli.ExecuteWithStdioRestored == nil { | ||
return | ||
} | ||
cli.ExecuteWithStdioRestored(func() { | ||
pager := outputpager.Start() | ||
defer pager.Stop() | ||
|
||
for _, info := range reflogInfo { | ||
// TODO: use short hash instead | ||
line := []string{fmt.Sprintf("\033[33m%s\033[0m", info.commitHash)} // commit hash in yellow (33m) | ||
|
||
processedRef := processRefForReflog(info.ref) | ||
if headHash != "" && headHash == info.commitHash { | ||
line = append(line, fmt.Sprintf("\033[33m(\033[36;1mHEAD -> %s\033[33m)\033[0m", processedRef)) // HEAD in cyan (36;1) | ||
} else { | ||
line = append(line, fmt.Sprintf("\033[33m(%s\033[33m)\033[0m", processedRef)) // () in yellow (33m) | ||
} | ||
line = append(line, fmt.Sprintf("%s\n", info.commitMessage)) | ||
pager.Writer.Write([]byte(strings.Join(line, " "))) | ||
} | ||
}) | ||
} | ||
|
||
// processRefForReflog takes a full ref (e.g. refs/heads/master) or tag name and returns the ref name (e.g. master) with relevant decoration. | ||
func processRefForReflog(fullRef string) string { | ||
if strings.HasPrefix(fullRef, "refs/heads/") { | ||
return fmt.Sprintf("\033[32;1m%s\033[0m", strings.TrimPrefix(fullRef, "refs/heads/")) // branch in green (32;1m) | ||
} else if strings.HasPrefix(fullRef, "refs/tags/") { | ||
return fmt.Sprintf("\033[33mtag: %s\033[0m", strings.TrimPrefix(fullRef, "refs/tags/")) // tag in yellow (33m) | ||
} else if strings.HasPrefix(fullRef, "refs/remotes/") { | ||
return fmt.Sprintf("\033[31;1m%s\033[0m", strings.TrimPrefix(fullRef, "refs/remotes/")) // remote in red (31;1m) | ||
} else if strings.HasPrefix(fullRef, "refs/workspaces/") { | ||
return fmt.Sprintf("\033[35;1mworkspace: %s\033[0m", strings.TrimPrefix(fullRef, "refs/workspaces/")) // workspace in magenta (35;1m) | ||
} else { | ||
return fullRef | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions
1
go/gen/proto/dolt/services/eventsapi/v1alpha1/event_constants.pb.go
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -135,6 +135,7 @@ SKIP_SERVER_TESTS=$(cat <<-EOM | |
~cli-hosted.bats~ | ||
~profile.bats~ | ||
~ls.bats~ | ||
~reflog.bats~ | ||
EOM | ||
) | ||
|
||
|
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(minor) It would be nice if we could somehow document why we're skipping some of these. It'll be hard to remember in the future. I know you mentioned we couldn't run
reflog.bats
through the local-remote test because it depends ondolt gc
which won't there yet. That would be a nice note to include, but I'm not sure this format would allow it.