Skip to content
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

Adds graceful continue #73

Merged
merged 6 commits into from
Jul 10, 2024
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
31 changes: 21 additions & 10 deletions cmd/gh-classroom/clone/student-repos/student-repos.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/github/gh-classroom/cmd/gh-classroom/shared"
"github.com/github/gh-classroom/pkg/classroom"
"github.com/spf13/cobra"
"github.com/github/gh-classroom/cmd/gh-classroom/clone/utils"
)

func NewCmdStudentRepo(f *cmdutil.Factory) *cobra.Command {
Expand All @@ -21,6 +22,7 @@ func NewCmdStudentRepo(f *cmdutil.Factory) *cobra.Command {
var page int
var perPage int
var getAll bool
var verbose bool

cmd := &cobra.Command{
Use: "student-repos",
Expand Down Expand Up @@ -89,18 +91,26 @@ func NewCmdStudentRepo(f *cmdutil.Factory) *cobra.Command {
}

totalCloned := 0
cloneErrors := []string{}
for _, acceptAssignment := range acceptedAssignmentList.AcceptedAssignments {
clonePath := filepath.Join(fullPath, acceptAssignment.Repository.Name())
if _, err := os.Stat(clonePath); os.IsNotExist(err) {
fmt.Printf("Cloning into: %v\n", clonePath)
_, _, err := gh.Exec("repo", "clone", acceptAssignment.Repository.FullName, "--", clonePath)
totalCloned++
if err != nil {
log.Fatal(err)
return
}
clonePath := filepath.Join(fullPath, acceptAssignment.Repository.Name)
err := utils.CloneRepository(clonePath, acceptAssignment.Repository.FullName, gh.Exec)
if err != nil {
errMsg := fmt.Sprintf("Error cloning %s: %v", acceptAssignment.Repository.FullName, err)
fmt.Println(errMsg)
cloneErrors = append(cloneErrors, errMsg)
continue // Continue with the next iteration
}
totalCloned++
}
if len(cloneErrors) > 0 {
fmt.Println("Some repositories failed to clone.")
if !verbose {
fmt.Println("Run with --verbose flag to see more details")
} else {
fmt.Printf("Skip existing repo: %v use gh classroom pull to get new commits\n", clonePath)
for _, errMsg := range cloneErrors {
fmt.Println(errMsg)
}
}
}
if getAll {
Expand All @@ -117,6 +127,7 @@ func NewCmdStudentRepo(f *cmdutil.Factory) *cobra.Command {
cmd.Flags().IntVar(&page, "page", 1, "Page number")
cmd.Flags().IntVar(&perPage, "per-page", 15, "Number of accepted assignments per page")
cmd.Flags().BoolVar(&getAll, "all", true, "Clone All assignments by default")
cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose error output")

return cmd
}
29 changes: 29 additions & 0 deletions cmd/gh-classroom/clone/utils/clone-repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// cmd/gh-classroom/clone/utils/clone-repository.go

package utils

import (
"fmt"
"os"
"bytes"
)

// This abstraction allows for easier testing and decoupling from the actual CLI.
// Exec invokes a gh command in a subprocess and captures the output and error streams.
type GitHubExec func(args ...string) (stdout, stderr bytes.Buffer, err error)

// CloneRepository attempts to clone a repository into the specified path.
// It returns an error if the cloning process fails.
func CloneRepository(clonePath string, repoFullName string, ghExec GitHubExec) error {
if _, err := os.Stat(clonePath); os.IsNotExist(err) {
fmt.Printf("Cloning into: %v\n", clonePath)
_, _, err := ghExec("repo", "clone", repoFullName, "--", clonePath)
if err != nil {
fmt.Printf("error cloning %s: %v\n", repoFullName, err)
return fmt.Errorf("error cloning %s: %v", repoFullName, err)
}
return nil // Success
}
fmt.Printf("Skip existing repo: %v use gh classroom pull to get new commits\n", clonePath)
return fmt.Errorf("repository already exists: %s", clonePath)
}
83 changes: 83 additions & 0 deletions cmd/gh-classroom/clone/utils/clone-repository_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package utils

import (
"os"
"testing"
"bytes"
"fmt"
"errors"
"path/filepath"
)

func TestCloneRepository(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "cloneTest")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
defer os.RemoveAll(tmpDir) // clean up
// Test cases
tests := []struct {
name string
execMock GitHubExec
clonePath string
repoFullName string
wantErr bool
errMsg string
}{
{
name: "successful clone",
execMock: func(args ...string) (stdout bytes.Buffer, stderr bytes.Buffer, err error) {
var stdoutBuf, stderrBuf bytes.Buffer
stdoutBuf.Write([]byte("your string here"))
return stdoutBuf, stderrBuf, nil
},
clonePath: "", // Will be set to a temp directory in the test
repoFullName: "example/repo",
wantErr: false,
},
{
name: "clone failure",
execMock: func(args ...string) (stdout bytes.Buffer, stderr bytes.Buffer, err error) {
var stdoutBuf, stderrBuf bytes.Buffer
return stdoutBuf, stderrBuf, errors.New("clone error")
},
clonePath: filepath.Join(tmpDir, "repo"),
repoFullName: "example/repo",
wantErr: true,
errMsg: "error cloning example/repo: clone error",
},
{
name: "repository already exists",
execMock: func(args ...string) (stdout bytes.Buffer, stderr bytes.Buffer, err error) {
var stdoutBuf, stderrBuf bytes.Buffer
return stdoutBuf, stderrBuf, nil
},
clonePath: "./repo", // Current directory always exists
repoFullName: "example/repo",
wantErr: true,
errMsg: "repository already exists: .",
},
}


for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.name == "successful clone" {
fmt.Println("Running successful clone test")
tmpDir, err := os.MkdirTemp("", "cloneTest")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
defer os.RemoveAll(tmpDir) // clean up
tt.clonePath = filepath.Join(tmpDir, "repo")
}


fmt.Println("Running test", tt.name, "with clonePath", tt.clonePath, "and repoFullName", tt.repoFullName)
err := CloneRepository(tt.clonePath, tt.repoFullName, tt.execMock)
if err != nil && err.Error() != tt.errMsg {
t.Errorf("CloneRepository() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
6 changes: 1 addition & 5 deletions pkg/classroom/classroom.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package classroom

import (
"fmt"
"strings"
)

type AssignmentList struct {
Expand Down Expand Up @@ -57,6 +56,7 @@ type Classroom struct {

type GithubRepository struct {
Id int `json:"id"`
Name string `json:"name"`
FullName string `json:"full_name"`
HtmlUrl string `json:"html_url"`
NodeId string `json:"node_id"`
Expand Down Expand Up @@ -138,7 +138,3 @@ func (a Assignment) IsGroupAssignment() bool {
func (a AcceptedAssignment) RepositoryUrl() string {
return a.Repository.HtmlUrl
}

func (gr GithubRepository) Name() string {
return strings.Split(gr.FullName, "/")[1]
}
9 changes: 1 addition & 8 deletions pkg/classroom/classroom_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,4 @@ func TestAcceptedAssignments(t *testing.T) {
acceptedAssignment := AcceptedAssignment{Repository: GithubRepository{HtmlUrl: "https://github.com/owner/repo"}}
assert.Equal(t, acceptedAssignment.RepositoryUrl(), "https://github.com/owner/repo")
})
}

func TestGithubRepositories(t *testing.T) {
t.Run("Returns repo name", func(t *testing.T) {
repository := GithubRepository{FullName: "owner/repo"}
assert.Equal(t, repository.Name(), "repo")
})
}
}
Loading