package lanesnapshot import ( "context" "fmt" "strings" "inbox/internal/app/lanegit" "inbox/internal/base/timeutil" "inbox/internal/domain/lane" "inbox/internal/domain/task" ) type Service struct { runner lanegit.Runner clock timeutil.Clock } func NewService(runner lanegit.Runner, clock timeutil.Clock) *Service { if runner == nil { runner = lanegit.ExecRunner{} } if clock == nil { clock = timeutil.SystemClock{} } return &Service{runner: runner, clock: clock} } func (s *Service) Capture(ctx context.Context, item lane.Record, taskRecord task.Record) (string, error) { worktree := strings.TrimSpace(item.WorktreePath) if worktree == "" { return "", fmt.Errorf("lane %s has empty worktree path", item.ID) } headBefore, err := s.headCommit(ctx, worktree) if err != nil { return "", err } changed, err := s.hasChanges(ctx, worktree) if err != nil { return "", err } if !changed { return headBefore, nil } if _, err := lanegit.Run(ctx, s.runner, worktree, nil, "add", "-A"); err != nil { return "", err } staged, err := s.hasStagedChanges(ctx, worktree) if err != nil { return "", err } if !staged { return headBefore, nil } msg := fmt.Sprintf("lane(%s): complete task %s - %s", strings.TrimSpace(item.Slug), taskRecord.ID, strings.TrimSpace(taskRecord.Title)) env := map[string]string{ "GIT_AUTHOR_NAME": "Inbox", "GIT_AUTHOR_EMAIL": "inbox@local", "GIT_COMMITTER_NAME": "Inbox", "GIT_COMMITTER_EMAIL": "inbox@local", "GIT_AUTHOR_DATE": timeutil.FormatRFC3339(s.clock.Now()), "GIT_COMMITTER_DATE": timeutil.FormatRFC3339(s.clock.Now()), } if _, err := lanegit.Run(ctx, s.runner, worktree, env, "commit", "-m", msg); err != nil { return "", err } return s.headCommit(ctx, worktree) } 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) hasChanges(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) hasStagedChanges(ctx context.Context, worktree string) (bool, error) { _, err := s.runner.Run(ctx, worktree, nil, "git", "diff", "--cached", "--quiet", "--exit-code") if err == nil { return false, nil } if lanegit.IsExitCode(err, 1) { return true, nil } return false, fmt.Errorf("git diff --cached --quiet --exit-code: %w", err) }