From e82a9f9ef743cb7d7d3eea68ccd99f8856983c9b Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Wed, 21 Oct 2026 12:13:04 -0700 Subject: [PATCH] Share commit preparation and checkout updates across merge paths --- internal/agents/maincommit.go | 70 +++++++++++++++++++++++++++++++++++ internal/agents/mergein.go | 53 ++------------------------ internal/agents/mergemain.go | 25 ++----------- 3 files changed, 77 insertions(+), 71 deletions(-) create mode 100644 internal/agents/maincommit.go diff --git a/internal/agents/maincommit.go b/internal/agents/maincommit.go new file mode 100644 index 0000000..a1f7e98 --- /dev/null +++ b/internal/agents/maincommit.go @@ -0,0 +1,70 @@ +package agents + +import ( + "context" + "fmt" + "strings" + + "g.fc.run/ai/byre/internal/api" + "g.fc.run/ai/byre/internal/git" +) + +func (m *Manager) prepareMainCommit(ctx context.Context, task api.Task, project api.Project, user api.User) (string, string, error) { + coAuthors, err := m.CoAuthors(ctx, task.Id, user.Login) + if err != nil { + return "", "", err + } + env := git.IdentityEnv(user.GitName, user.GitEmail) + commit, err := git.SquashCommit(project.RepoPath, task.WorktreePath, project.DefaultBranch, squashMessage(task.MergeTitle, coAuthors), env, m.signer(ctx, user)) + if err != nil || commit == "" { + return "", "", err + } + note, err := m.mergeNote(ctx, task, user.Login) + if err != nil { + return "", "", err + } + notes, err := git.NoteCommit(project.RepoPath, commit, note, env) + return commit, notes, err +} + +func advanceMainCheckout(project api.Project, commit, notes string) error { + pushed := git.Remote + "/" + project.DefaultBranch + err := git.AdvanceNotes(project.RepoPath, notes) + if err != nil { + return fmt.Errorf("pushed %s to %s, but updating %s in the project checkout failed: %w", commit, pushed, git.NotesRef, err) + } + err = git.FastForward(project.RepoPath, commit) + if err != nil { + return fmt.Errorf("pushed %s to %s, but fast-forwarding the project checkout failed: %w", commit, pushed, err) + } + return nil +} + +func (m *Manager) CoAuthors(ctx context.Context, taskID, login string) ([]api.Participant, error) { + participants, err := m.store.Participants(ctx, taskID) + if err != nil { + return nil, err + } + + coAuthors := []api.Participant{} + for _, p := range participants { + if p.Login != login { + coAuthors = append(coAuthors, p) + } + } + return coAuthors, nil +} + +func squashMessage(title string, coAuthors []api.Participant) string { + trailers := []string{} + for _, p := range coAuthors { + if p.GitName == "" || p.GitEmail == "" { + continue + } + trailers = append(trailers, fmt.Sprintf("Co-authored-by: %s <%s>", p.GitName, p.GitEmail)) + } + if len(trailers) == 0 { + return title + } + return title + "\n\n" + strings.Join(trailers, "\n") +} diff --git a/internal/agents/mergein.go b/internal/agents/mergein.go index 2a3a3f8..faef9d9 100644 --- a/internal/agents/mergein.go +++ b/internal/agents/mergein.go @@ -330,12 +330,7 @@ func (m *Manager) landMerge(ctx context.Context, f store.FinalizingMerge) error if err != nil { return err } - coAuthors, err := m.CoAuthors(ctx, task.Id, f.By) - if err != nil { - return err - } - env := git.IdentityEnv(user.GitName, user.GitEmail) - commit, err = git.SquashCommit(project.RepoPath, task.WorktreePath, project.DefaultBranch, squashMessage(task.MergeTitle, coAuthors), env, m.signer(ctx, user)) + commit, notes, err = m.prepareMainCommit(ctx, task, project, user) if err != nil { return err } @@ -346,15 +341,6 @@ func (m *Manager) landMerge(ctx context.Context, f store.FinalizingMerge) error } return m.closeUnchanged(ctx, task, project, f.By) } - - note, err := m.mergeNote(ctx, task, f.By) - if err != nil { - return err - } - notes, err = git.NoteCommit(project.RepoPath, commit, note, env) - if err != nil { - return err - } err = m.store.SetFinalizingCommit(ctx, task.Id, commit, notes) if err != nil { return err @@ -391,13 +377,9 @@ func (m *Manager) closeUnchanged(ctx context.Context, task api.Task, project api func (m *Manager) closePushed(ctx context.Context, task api.Task, project api.Project, commit, notes, login string) error { pushed := git.Remote + "/" + project.DefaultBranch - err := git.AdvanceNotes(project.RepoPath, notes) + err := advanceMainCheckout(project, commit, notes) if err != nil { - return &unfinishedMergeError{fmt.Errorf("pushed %s to %s, but updating %s in the project checkout failed: %w", commit, pushed, git.NotesRef, err)} - } - err = git.FastForward(project.RepoPath, commit) - if err != nil { - return &unfinishedMergeError{fmt.Errorf("pushed %s to %s, but fast-forwarding the project checkout failed: %w", commit, pushed, err)} + return &unfinishedMergeError{err} } err = m.RemoveTask(ctx, task, project, login) if err != nil { @@ -410,35 +392,6 @@ func (m *Manager) closePushed(ctx context.Context, task api.Task, project api.Pr return nil } -func (m *Manager) CoAuthors(ctx context.Context, taskID, login string) ([]api.Participant, error) { - participants, err := m.store.Participants(ctx, taskID) - if err != nil { - return nil, err - } - - coAuthors := []api.Participant{} - for _, p := range participants { - if p.Login != login { - coAuthors = append(coAuthors, p) - } - } - return coAuthors, nil -} - -func squashMessage(title string, coAuthors []api.Participant) string { - trailers := []string{} - for _, p := range coAuthors { - if p.GitName == "" || p.GitEmail == "" { - continue - } - trailers = append(trailers, fmt.Sprintf("Co-authored-by: %s <%s>", p.GitName, p.GitEmail)) - } - if len(trailers) == 0 { - return title - } - return title + "\n\n" + strings.Join(trailers, "\n") -} - 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 { diff --git a/internal/agents/mergemain.go b/internal/agents/mergemain.go index a7060c9..4cd3826 100644 --- a/internal/agents/mergemain.go +++ b/internal/agents/mergemain.go @@ -116,12 +116,8 @@ func (m *Manager) pushToMain(ctx context.Context, l *live, task api.Task, projec return "", err } - coAuthors, err := m.CoAuthors(ctx, task.Id, user.Login) - if err != nil { - return "", err - } - env := git.IdentityEnv(user.GitName, user.GitEmail) - commit, err := git.SquashCommit(project.RepoPath, l.dir, project.DefaultBranch, squashMessage(title, coAuthors), env, m.signer(ctx, user)) + task.MergeTitle = title + commit, notes, err := m.prepareMainCommit(ctx, task, project, user) conflict := &git.ConflictError{} if errors.As(err, &conflict) { return "", fmt.Errorf("%w; run `git merge --no-ff --no-commit %s`, resolve them, and call merge_to_main again", err, project.DefaultBranch) @@ -134,15 +130,6 @@ func (m *Manager) pushToMain(ctx context.Context, l *live, task api.Task, projec return "", err } - task.MergeTitle = title - note, err := m.mergeNote(ctx, task, user.Login) - if err != nil { - return "", err - } - notes, err := git.NoteCommit(project.RepoPath, commit, note, env) - if err != nil { - return "", err - } err = git.Push(ctx, project.RepoPath, commit, project.DefaultBranch, notes) if errors.Is(err, git.ErrRemoteMoved) { return "", fmt.Errorf("%w; call merge_to_main again", err) @@ -152,13 +139,9 @@ func (m *Manager) pushToMain(ctx context.Context, l *live, task api.Task, projec } pushed := git.Remote + "/" + project.DefaultBranch - err = git.AdvanceNotes(project.RepoPath, notes) + err = advanceMainCheckout(project, commit, notes) if err != nil { - return "", fmt.Errorf("pushed %s to %s, but updating %s in the project checkout failed: %w", commit, pushed, git.NotesRef, err) - } - err = git.FastForward(project.RepoPath, commit) - if err != nil { - return "", fmt.Errorf("pushed %s to %s, but fast-forwarding the project checkout failed: %w", commit, pushed, err) + return "", err } err = git.Merge(l.dir, commit, fmt.Sprintf("Merge %s from %s", commit[:7], project.DefaultBranch), l.gitName, l.gitEmail) if err != nil {