1package backend23import (4 "context"5 "io"6 "os"7 "sync"89 "github.com/charmbracelet/soft-serve/git"10 "github.com/charmbracelet/soft-serve/pkg/hooks"11 "github.com/charmbracelet/soft-serve/pkg/proto"12 "github.com/charmbracelet/soft-serve/pkg/sshutils"13 "github.com/charmbracelet/soft-serve/pkg/webhook"14)1516var _ hooks.Hooks = (*Backend)(nil)1718// PostReceive is called by the git post-receive hook.19//20// It implements Hooks.21func (d *Backend) PostReceive(_ context.Context, _ io.Writer, _ io.Writer, repo string, args []hooks.HookArg) {22 d.logger.Debug("post-receive hook called", "repo", repo, "args", args)23}2425// PreReceive is called by the git pre-receive hook.26//27// It implements Hooks.28func (d *Backend) PreReceive(_ context.Context, _ io.Writer, _ io.Writer, repo string, args []hooks.HookArg) {29 d.logger.Debug("pre-receive hook called", "repo", repo, "args", args)30}3132// Update is called by the git update hook.33//34// It implements Hooks.35func (d *Backend) Update(ctx context.Context, _ io.Writer, _ io.Writer, repo string, arg hooks.HookArg) {36 d.logger.Debug("update hook called", "repo", repo, "arg", arg)3738 // Find user39 var user proto.User40 if pubkey := os.Getenv("SOFT_SERVE_PUBLIC_KEY"); pubkey != "" {41 pk, _, err := sshutils.ParseAuthorizedKey(pubkey)42 if err != nil {43 d.logger.Error("error parsing public key", "err", err)44 return45 }4647 user, err = d.UserByPublicKey(ctx, pk)48 if err != nil {49 d.logger.Error("error finding user from public key", "key", pubkey, "err", err)50 return51 }52 } else if username := os.Getenv("SOFT_SERVE_USERNAME"); username != "" {53 var err error54 user, err = d.User(ctx, username)55 if err != nil {56 d.logger.Error("error finding user from username", "username", username, "err", err)57 return58 }59 } else {60 d.logger.Error("error finding user")61 return62 }6364 // Get repo65 r, err := d.Repository(ctx, repo)66 if err != nil {67 d.logger.Error("error finding repository", "repo", repo, "err", err)68 return69 }7071 // TODO: run this async72 // This would probably need something like an RPC server to communicate with the hook process.73 if git.IsZeroHash(arg.OldSha) || git.IsZeroHash(arg.NewSha) {74 wh, err := webhook.NewBranchTagEvent(ctx, user, r, arg.RefName, arg.OldSha, arg.NewSha)75 if err != nil {76 d.logger.Error("error creating branch_tag webhook", "err", err)77 } else if err := webhook.SendEvent(ctx, wh); err != nil {78 d.logger.Error("error sending branch_tag webhook", "err", err)79 }80 }81 wh, err := webhook.NewPushEvent(ctx, user, r, arg.RefName, arg.OldSha, arg.NewSha)82 if err != nil {83 d.logger.Error("error creating push webhook", "err", err)84 } else if err := webhook.SendEvent(ctx, wh); err != nil {85 d.logger.Error("error sending push webhook", "err", err)86 }87}8889// PostUpdate is called by the git post-update hook.90//91// It implements Hooks.92func (d *Backend) PostUpdate(ctx context.Context, _ io.Writer, _ io.Writer, repo string, args ...string) {93 d.logger.Debug("post-update hook called", "repo", repo, "args", args)9495 var wg sync.WaitGroup9697 // Populate last-modified file.98 wg.Add(1)99 go func() {100 defer wg.Done()101 if err := populateLastModified(ctx, d, repo); err != nil {102 d.logger.Error("error populating last-modified", "repo", repo, "err", err)103 return104 }105 }()106107 wg.Wait()108}109110func populateLastModified(ctx context.Context, d *Backend, name string) error {111 var rr *repo112 _rr, err := d.Repository(ctx, name)113 if err != nil {114 return err115 }116117 if r, ok := _rr.(*repo); ok {118 rr = r119 } else {120 return proto.ErrRepoNotFound121 }122123 r, err := rr.Open()124 if err != nil {125 return err126 }127128 c, err := r.LatestCommitTime()129 if err != nil {130 return err131 }132133 return rr.writeLastModified(c)134}