chore(repo): reinitialize repository
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
package lanematerialize
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"inbox/internal/app/lanegit"
|
||||
"inbox/internal/base/timeutil"
|
||||
"inbox/internal/domain/lane"
|
||||
"inbox/internal/domain/lanesync"
|
||||
)
|
||||
|
||||
type SyncRecorder interface {
|
||||
CreateLaneSync(ctx context.Context, value lanesync.Record) (lanesync.Record, error)
|
||||
}
|
||||
|
||||
type Upstream struct {
|
||||
TaskID string
|
||||
Lane lane.Record
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
recorder SyncRecorder
|
||||
runner lanegit.Runner
|
||||
clock timeutil.Clock
|
||||
}
|
||||
|
||||
func NewService(recorder SyncRecorder, runner lanegit.Runner, clock timeutil.Clock) *Service {
|
||||
if runner == nil {
|
||||
runner = lanegit.ExecRunner{}
|
||||
}
|
||||
if clock == nil {
|
||||
clock = timeutil.SystemClock{}
|
||||
}
|
||||
return &Service{recorder: recorder, runner: runner, clock: clock}
|
||||
}
|
||||
|
||||
func (s *Service) Materialize(ctx context.Context, downstream lane.Record, taskID string, upstreams []Upstream) error {
|
||||
if len(upstreams) == 0 {
|
||||
return nil
|
||||
}
|
||||
worktree := strings.TrimSpace(downstream.WorktreePath)
|
||||
if worktree == "" {
|
||||
return fmt.Errorf("lane %s has empty worktree path", downstream.ID)
|
||||
}
|
||||
clean, err := s.isClean(ctx, worktree)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !clean {
|
||||
return fmt.Errorf("lane %s worktree has uncommitted changes before materialization", downstream.ID)
|
||||
}
|
||||
|
||||
sort.SliceStable(upstreams, func(i, j int) bool {
|
||||
if upstreams[i].Lane.ID == upstreams[j].Lane.ID {
|
||||
return upstreams[i].TaskID < upstreams[j].TaskID
|
||||
}
|
||||
return upstreams[i].Lane.ID < upstreams[j].Lane.ID
|
||||
})
|
||||
|
||||
for _, upstream := range upstreams {
|
||||
if upstream.Lane.ID == downstream.ID {
|
||||
continue
|
||||
}
|
||||
commit := strings.TrimSpace(upstream.Lane.HeadCommit)
|
||||
if commit == "" {
|
||||
err := fmt.Errorf("upstream lane %s has no head_commit", upstream.Lane.ID)
|
||||
s.record(ctx, downstream, upstream, "", lanesync.StatusFailed, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
alreadyMerged, err := s.isAncestor(ctx, worktree, commit)
|
||||
if err != nil {
|
||||
s.record(ctx, downstream, upstream, "", lanesync.StatusFailed, err.Error())
|
||||
return err
|
||||
}
|
||||
if alreadyMerged {
|
||||
s.record(ctx, downstream, upstream, commit, lanesync.StatusSkipped, "")
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := lanegit.Run(ctx, s.runner, worktree, nil, "merge", "--no-ff", "--no-edit", commit); err != nil {
|
||||
_ = s.abortMerge(ctx, worktree)
|
||||
s.record(ctx, downstream, upstream, "", lanesync.StatusFailed, err.Error())
|
||||
return err
|
||||
}
|
||||
mergedHead, err := s.headCommit(ctx, worktree)
|
||||
if err != nil {
|
||||
s.record(ctx, downstream, upstream, "", lanesync.StatusFailed, err.Error())
|
||||
return err
|
||||
}
|
||||
s.record(ctx, downstream, upstream, mergedHead, lanesync.StatusApplied, "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) record(ctx context.Context, downstream lane.Record, upstream Upstream, mergeCommit string, status lanesync.Status, errorMessage string) {
|
||||
if s.recorder == nil {
|
||||
return
|
||||
}
|
||||
_, _ = s.recorder.CreateLaneSync(ctx, lanesync.Record{
|
||||
WorkspaceID: downstream.WorkspaceID,
|
||||
TopicID: downstream.TopicID,
|
||||
DownstreamLaneID: downstream.ID,
|
||||
UpstreamLaneID: upstream.Lane.ID,
|
||||
TaskID: upstream.TaskID,
|
||||
UpstreamCommit: strings.TrimSpace(upstream.Lane.HeadCommit),
|
||||
MergeCommit: strings.TrimSpace(mergeCommit),
|
||||
Status: status,
|
||||
ErrorMessage: strings.TrimSpace(errorMessage),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) abortMerge(ctx context.Context, worktree string) error {
|
||||
_, err := lanegit.Run(ctx, s.runner, worktree, nil, "merge", "--abort")
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Service) headCommit(ctx context.Context, worktree string) (string, error) {
|
||||
out, err := lanegit.Run(ctx, s.runner, worktree, nil, "rev-parse", "HEAD")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(out), nil
|
||||
}
|
||||
|
||||
func (s *Service) isClean(ctx context.Context, worktree string) (bool, error) {
|
||||
out, err := lanegit.Run(ctx, s.runner, worktree, nil, "status", "--porcelain")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return strings.TrimSpace(out) == "", nil
|
||||
}
|
||||
|
||||
func (s *Service) isAncestor(ctx context.Context, worktree, commit string) (bool, error) {
|
||||
head, err := s.headCommit(ctx, worktree)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
_, err = s.runner.Run(ctx, worktree, nil, "git", "merge-base", "--is-ancestor", commit, head)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if lanegit.IsExitCode(err, 1) {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("git merge-base --is-ancestor %s %s: %w", commit, head, err)
|
||||
}
|
||||
Reference in New Issue
Block a user