Files
LazyPM/cmd/pm/tasks/gitTask.go
2026-03-22 15:27:05 +01:00

210 lines
5.7 KiB
Go

package tasks
import (
"context"
"errors"
"os"
"strings"
"charm.land/huh/v2"
"github.com/LazyBachelor/LazyPM/internal/models"
"github.com/LazyBachelor/LazyPM/internal/utils/check"
"github.com/go-git/go-git/v6"
)
const gitTaskDescription = `You are tasked with performing a Git operation.
Modify a file in a Git repository and commit the change.
Your task:
1. Assign the given issue to yourself as 'Me'.
2. A folder called "task" is created in this directory.
3. Inside the folder you will find README.md.
- Edit this file and add something to it.
- The file must be different from its original content.
4. Commit your change:
- Open a terminal and change into the task folder.
- Run "git add ." to stage the changes.
- Run "git commit -m "updated codebase"" to commit.
5. Set the Issue status to "Closed" when done.
The repository is in the task folder (./task).`
const gitTaskReadmeContent = "This is a Git task. Add your name or a short note below this line to complete it, then commit your change."
type GitTask struct {
app *App
done bool
repo *git.Repository
setupIssue *Issue
}
var gitTaskInProgress = false
func NewGitTask(app *App) *GitTask {
return &GitTask{app: app, done: false}
}
func (t *GitTask) Config() Config {
return BaseConfig().WithStatisticsStoragePath("./.pm/git-task-stats.json")
}
func (t *GitTask) Details(interfaceType InterfaceType) TaskDetails {
return BaseDetails(interfaceType).
WithTitle("Git Task").
WithDescription(gitTaskDescription).
WithDifficulty("Hard").WithTimeToComplete("5m")
}
func (t *GitTask) Questions(interfaceType InterfaceType) Questions {
return BaseQuestions(interfaceType).
With(
huh.NewGroup(
huh.NewSelect[string]().Key("git_interface_used").
Title("What Git Interface did you use for the task?").
Description("If you used multiple interfaces, select the one you relied on most for this task.").
Options(
huh.Option[string]{Value: "cli", Key: "Command Line Interface"},
huh.Option[string]{Value: "tui", Key: "Terminal User Interface"},
huh.Option[string]{Value: "gui", Key: "Graphical User Interface"},
),
),
).With(
huh.NewGroup(
huh.NewSelect[string]().Key("git_interface_normally").
Title("What Git Interface do you normally use?").
Description("If you regularly use multiple interfaces, select the one you prefer most.").
Options(
huh.Option[string]{Value: "cli", Key: "Command Line Interface"},
huh.Option[string]{Value: "tui", Key: "Terminal User Interface"},
huh.Option[string]{Value: "gui", Key: "Graphical User Interface"},
),
))
}
func (t *GitTask) QuestionnaireKeys(_ InterfaceType) []string {
return BaseKeys().With("git_interface_used", "git_interface_normally")
}
func (t *GitTask) Setup(ctx context.Context) error {
gitTaskInProgress = false
if err := ClearIssues(t.app); err != nil {
return err
}
_ = os.RemoveAll("./task")
var err error
t.repo, err = t.initRepo()
if err != nil {
return err
}
_ = os.WriteFile("./task/README.md", []byte(gitTaskReadmeContent), 0o644)
_ = os.WriteFile("./task/.gitattributes", []byte("* text=auto\n"), 0o644)
t.setupIssue = NewIssueBuilder().
WithTitle("Upgrade the codebase").
WithDescription(gitTaskDescription).
WithIssueType(models.TypeTask).
Build()
if err := t.app.Issues.CreateIssue(ctx, t.setupIssue, ""); err != nil {
return err
}
return nil
}
func (t *GitTask) Validate(ctx context.Context) ValidationFeedback {
expect := check.NewExpector()
issue, err := t.app.Issues.GetIssue(ctx, t.setupIssue.ID)
if err != nil {
return expect.Fatal("Could not fetch issue")
}
if issue == nil {
expect.Fail("Issue could not be found. It may have been deleted; please recreate it and try again.")
return expect.ValidationFeedback
}
expect.Equal(issue.Assignee, "Me", "Issue Assignee")
if !expect.Valid() {
return expect.ValidationFeedback
}
// Ensure the repository is available.
if t.repo == nil {
t.repo, err = t.initRepo()
if err != nil {
expect.Fail("Failed to open the Git repository: " + err.Error())
return expect.ValidationFeedback
}
}
headRef, err := t.repo.Head()
if err != nil {
expect.Fail("No commits found in the Git repository. Please commit your changes.")
return expect.ValidationFeedback
}
commit, err := t.repo.CommitObject(headRef.Hash())
if err != nil {
expect.Fail("Failed to read the latest commit: " + err.Error())
return expect.ValidationFeedback
}
expect.NotEmptyAndEqual(strings.TrimSpace(commit.Message), "updated codebase", "Git commit message")
tree, err := commit.Tree()
if err != nil {
expect.Fail("Failed to read commit tree: " + err.Error())
return expect.ValidationFeedback
}
readmeFile, err := tree.File("README.md")
if err != nil {
expect.Fail("README.md should be part of the committed changes.")
return expect.ValidationFeedback
}
readmeContent, err := readmeFile.Contents()
if err != nil {
expect.Fail("Failed to read README.md from the commit: " + err.Error())
return expect.ValidationFeedback
}
expect.Assert(readmeContent != gitTaskReadmeContent,
"You should modify README.md content before committing")
if wt, err := t.repo.Worktree(); err == nil {
if status, err := wt.Status(); err == nil {
expect.Assert(status.IsClean(), "The working tree should be clean after committing")
}
}
if !expect.Valid() {
return expect.ValidationFeedback
}
expect.Equal(issue.Status, models.StatusClosed, "Issue Status")
return expect.Complete()
}
func (t *GitTask) initRepo() (*git.Repository, error) {
repo, err := git.PlainInit("./task", false)
if err != nil {
if errors.Is(err, git.ErrTargetDirNotEmpty) {
repo, err = git.PlainOpen("./task")
if err != nil {
return nil, err
}
return repo, nil
}
return nil, err
}
return repo, nil
}