Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions cmd/codecrafters/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ USAGE
$ codecrafters [command]

EXAMPLES
$ codecrafters test # Run tests without committing changes
$ codecrafters test # Run tests without committing changes
$ codecrafters submit # Commit changes & submit to move to next step

COMMANDS
test: Run tests without committing changes
help: Show usage instructions
test: Run tests without committing changes
submit: Commit changes & submit to move to next step
help: Show usage instructions

VERSION
%s
Expand Down Expand Up @@ -75,6 +77,8 @@ func run() error {
switch cmd {
case "test":
return commands.TestCommand(ctx)
case "submit":
return commands.SubmitCommand(ctx)
case "help",
"": // no argument
flag.Usage()
Expand Down
111 changes: 111 additions & 0 deletions internal/commands/submit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package commands

import (
"context"
"errors"
"fmt"
"os/exec"
"strings"

"github.com/codecrafters-io/cli/internal/utils"
"github.com/getsentry/sentry-go"
"github.com/rs/zerolog"
)

func SubmitCommand(ctx context.Context) (err error) {
logger := zerolog.Ctx(ctx)

logger.Debug().Msg("submit command starts")
defer func() {
logger.Debug().Err(err).Msg("submit command ends")
}()

defer func() {
if p := recover(); p != nil {
logger.Panic().Str("panic", fmt.Sprintf("%v", p)).Stack().Msg("panic")
sentry.CurrentHub().Recover(p)

panic(p)
}

if err == nil {
return
}

var noRepo utils.NoCodecraftersRemoteFoundError
if errors.Is(err, &noRepo) {
// ignore
return
}

sentry.CurrentHub().CaptureException(err)
}()

logger.Debug().Msg("computing repository directory")

repoDir, err := utils.GetRepositoryDir()
if err != nil {
return err
}

logger.Debug().Msgf("found repository directory: %s", repoDir)

logger.Debug().Msg("identifying remotes")

codecraftersRemote, err := utils.IdentifyGitRemote(repoDir)
if err != nil {
return err
}

logger.Debug().Msgf("identified remote: %s, %s", codecraftersRemote.Name, codecraftersRemote.Url)

currentBranchName, err := getCurrentBranch(repoDir)
if err != nil {
return fmt.Errorf("get current branch: %w", err)
}

defaultBranchName := "master" // TODO: Change when we allow customizing the defaultBranch

if currentBranchName != defaultBranchName {
return fmt.Errorf("You need to be on the `%s` branch to run this command.", defaultBranchName)
}

logger.Debug().Msgf("committing changes to %s", defaultBranchName)

commitSha, err := commitChanges(repoDir, "codecrafters submit [skip ci]")
if err != nil {
return fmt.Errorf("commit changes: %w", err)
}

// Place this before the push so that it "feels" fast
fmt.Printf("Submitting changes (commit: %s)...\n", commitSha[:7])

err = pushBranchToRemote(repoDir, codecraftersRemote.Name)
if err != nil {
return fmt.Errorf("push changes: %w", err)
}

logger.Debug().Msgf("pushed changes to remote branch %s", defaultBranchName)

codecraftersClient := utils.NewCodecraftersClient(codecraftersRemote.CodecraftersServerURL())

logger.Debug().Msgf("creating submission for %s", commitSha)

createSubmissionResponse, err := codecraftersClient.CreateSubmission(codecraftersRemote.CodecraftersRepositoryId(), commitSha)
if err != nil {
return fmt.Errorf("create submission: %w", err)
}

logger.Debug().Msgf("submission created: %v", createSubmissionResponse.Id)

return utils.HandleSubmission(createSubmissionResponse, ctx, codecraftersClient)
}

func getCurrentBranch(repoDir string) (string, error) {
outputBytes, err := exec.Command("git", "-C", repoDir, "rev-parse", "--abbrev-ref", "HEAD").CombinedOutput()
if err != nil {
return "", wrapError(err, outputBytes, "get current branch")
}

return strings.TrimSpace(string(outputBytes)), nil
}
111 changes: 1 addition & 110 deletions internal/commands/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
Expand All @@ -13,8 +12,6 @@ import (
"time"

"github.com/codecrafters-io/cli/internal/utils"
logstream_consumer "github.com/codecrafters-io/logstream/consumer"
"github.com/fatih/color"
"github.com/getsentry/sentry-go"
cp "github.com/otiai10/copy"
"github.com/rs/zerolog"
Expand Down Expand Up @@ -115,99 +112,7 @@ func TestCommand(ctx context.Context) (err error) {

logger.Debug().Msgf("submission created: %v", createSubmissionResponse.Id)

for _, message := range createSubmissionResponse.OnInitMessages {
fmt.Println("")
message.Print()
}

if createSubmissionResponse.BuildLogstreamURL != "" {
logger.Debug().Msgf("streaming build logs from %s", createSubmissionResponse.BuildLogstreamURL)

fmt.Println("")
err = streamLogs(createSubmissionResponse.BuildLogstreamURL)
if err != nil {
return fmt.Errorf("stream build logs: %w", err)
}

logger.Debug().Msg("Finished streaming build logs")
logger.Debug().Msg("fetching build")

fetchBuildResponse, err := codecraftersClient.FetchBuild(createSubmissionResponse.BuildID)
if err != nil {
// TODO: Notify sentry
red := color.New(color.FgRed).SprintFunc()
fmt.Fprintln(os.Stderr, red(err.Error()))
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, red("We couldn't fetch the results of your submission. Please try again?"))
fmt.Fprintln(os.Stderr, red("Let us know at hello@codecrafters.io if this error persists."))
return err
}

logger.Debug().Msgf("finished fetching build: %v", fetchBuildResponse)
red := color.New(color.FgRed).SprintFunc()

switch fetchBuildResponse.Status {
case "failure":
fmt.Fprintln(os.Stderr, red(""))
fmt.Fprintln(os.Stderr, red("Looks like your codebase failed to build."))
fmt.Fprintln(os.Stderr, red("If you think this is a CodeCrafters error, please let us know at hello@codecrafters.io."))
fmt.Fprintln(os.Stderr, red(""))
os.Exit(0)
case "success":
time.Sleep(1 * time.Second) // The delay in-between build and test logs is usually 5-10 seconds, so let's buy some time
default:
red := color.New(color.FgRed).SprintFunc()

fmt.Fprintln(os.Stderr, red("We couldn't fetch the results of your build. Please try again?"))
fmt.Fprintln(os.Stderr, red("Let us know at hello@codecrafters.io if this error persists."))
os.Exit(1)
}
}

fmt.Println("")
fmt.Println("Running tests. Logs should appear shortly...")
fmt.Println("")

err = streamLogs(createSubmissionResponse.LogstreamURL)
if err != nil {
return fmt.Errorf("stream logs: %w", err)
}

logger.Debug().Msgf("fetching submission %s", createSubmissionResponse.Id)

fetchSubmissionResponse, err := codecraftersClient.FetchSubmission(createSubmissionResponse.Id)
if err != nil {
// TODO: Notify sentry
red := color.New(color.FgRed).SprintFunc()
fmt.Fprintln(os.Stderr, red(err.Error()))
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, red("We couldn't fetch the results of your submission. Please try again?"))
fmt.Fprintln(os.Stderr, red("Let us know at hello@codecrafters.io if this error persists."))
return err
}

logger.Debug().Msgf("finished fetching submission, status: %s", fetchSubmissionResponse.Status)

switch fetchSubmissionResponse.Status {
case "failure":
for _, message := range createSubmissionResponse.OnFailureMessages {
fmt.Println("")
message.Print()
}
case "success":
for _, message := range createSubmissionResponse.OnSuccessMessages {
fmt.Println("")
message.Print()
}
default:
fmt.Println("")
}

if fetchSubmissionResponse.IsError {
return fmt.Errorf("%s", fetchSubmissionResponse.ErrorMessage)
}

return nil
return utils.HandleSubmission(createSubmissionResponse, ctx, codecraftersClient)
}

func copyRepositoryDirToTempDir(repoDir string) (string, error) {
Expand Down Expand Up @@ -272,20 +177,6 @@ func pushBranchToRemote(tmpDir string, remoteName string) error {
return nil
}

func streamLogs(logstreamUrl string) error {
consumer, err := logstream_consumer.NewConsumer(logstreamUrl, func(message string) {})
if err != nil {
return fmt.Errorf("new log consumer: %w", err)
}

_, err = io.Copy(os.Stdout, consumer)
if err != nil {
return fmt.Errorf("stream data: %w", err)
}

return nil
}

func wrapError(err error, output []byte, msg string) error {
if _, ok := err.(*exec.ExitError); ok {
return fmt.Errorf("add all files: %s", output)
Expand Down
Loading