114 lines
3.0 KiB
Go
114 lines
3.0 KiB
Go
package agents
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"fmt"
|
||
|
|
"log/slog"
|
||
|
|
|
||
|
|
"g.fc.run/misc/byre/internal/api"
|
||
|
|
"g.fc.run/misc/byre/internal/git"
|
||
|
|
)
|
||
|
|
|
||
|
|
func mergeInPrompt(target string) string {
|
||
|
|
return fmt.Sprintf("Merge %[1]s into this branch: run `git merge --no-ff --no-commit %[1]s`. "+
|
||
|
|
"Resolve any conflicts keeping both sides' intent; regenerate generated files instead of hand-merging them. "+
|
||
|
|
"Check that the build and tests pass. Leave the merge uncommitted: byre concludes it when the turn ends.", target)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *Manager) MergeIn(ctx context.Context, task api.Task, user api.User, target string) (api.AgentSession, error) {
|
||
|
|
sessions, err := m.store.ListSessions(ctx, task.Id)
|
||
|
|
if err != nil {
|
||
|
|
return api.AgentSession{}, err
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, sess := range sessions {
|
||
|
|
if sess.State != api.AgentSessionStateCompleted {
|
||
|
|
return m.Send(ctx, sess.Id, "byre", mergeInPrompt(target), "")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return m.create(ctx, task, user, mergeInPrompt(target), "", "byre", nil)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *Manager) Merge(ctx context.Context, task api.Task, project api.Project, user api.User) (api.AgentSession, error) {
|
||
|
|
if user.GitName == "" || user.GitEmail == "" {
|
||
|
|
return api.AgentSession{}, ErrNoGitIdentity
|
||
|
|
}
|
||
|
|
|
||
|
|
m.mu.Lock()
|
||
|
|
m.merges[task.Id] = user.Login
|
||
|
|
m.mu.Unlock()
|
||
|
|
|
||
|
|
sess, err := m.MergeIn(ctx, task, user, project.DefaultBranch)
|
||
|
|
if err != nil {
|
||
|
|
m.mu.Lock()
|
||
|
|
delete(m.merges, task.Id)
|
||
|
|
m.mu.Unlock()
|
||
|
|
}
|
||
|
|
return sess, err
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *Manager) takeMerge(taskID string) (string, bool) {
|
||
|
|
m.mu.Lock()
|
||
|
|
defer m.mu.Unlock()
|
||
|
|
login, ok := m.merges[taskID]
|
||
|
|
delete(m.merges, taskID)
|
||
|
|
return login, ok
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *Manager) finishMerge(l *live, login string) {
|
||
|
|
ctx := context.Background()
|
||
|
|
err := m.squashAndClose(ctx, l.taskID, login)
|
||
|
|
if err == nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
slog.Error("finish merge", "task", l.taskID, "err", err)
|
||
|
|
m.fileError(l, "The agent merged the default branch in, but squashing onto it failed: "+err.Error())
|
||
|
|
err = m.Deliver(ctx, l.id)
|
||
|
|
if err != nil {
|
||
|
|
slog.Error("deliver inbox responses", "session", l.id, "err", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *Manager) squashAndClose(ctx context.Context, taskID, login string) error {
|
||
|
|
task, err := m.store.GetTask(ctx, taskID)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
project, err := m.store.GetProject(ctx, task.ProjectId)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
user, err := m.store.GetUser(ctx, login)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
commit, err := git.SquashMerge(project.RepoPath, task.WorktreePath, project.DefaultBranch, task.Title, []string{
|
||
|
|
"GIT_AUTHOR_NAME=" + user.GitName,
|
||
|
|
"GIT_AUTHOR_EMAIL=" + user.GitEmail,
|
||
|
|
"GIT_COMMITTER_NAME=" + user.GitName,
|
||
|
|
"GIT_COMMITTER_EMAIL=" + user.GitEmail,
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
err = m.RemoveTask(ctx, task, project, login)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
_, err = m.store.MergeTask(ctx, taskID, commit, login)
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *Manager) RemoveTask(ctx context.Context, task api.Task, project api.Project, actor string) error {
|
||
|
|
err := m.StopTask(ctx, task.Id, actor)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return git.RemoveWorktree(project.RepoPath, task.WorktreePath, task.Branch)
|
||
|
|
}
|