// SPDX-FileCopyrightText: 2025 Romain Maneschi // // SPDX-License-Identifier: EUPL-1.2 package plugin import ( "context" "encoding/json" "slices" "time" "github.com/go-git/go-git/v6/plumbing" "github.com/go-git/go-git/v6/plumbing/object" "github.com/go-git/go-git/v6/plumbing/protocol/packp" "github.com/samber/oops" pluginLib "gitroot.dev/libs/golang/plugin/model" "gitroot.dev/server/logger" "gitroot.dev/server/repository" "gitroot.dev/server/user" ) type commitForDiffAction int const ( commitForDiffActionAdd commitForDiffAction = iota commitForDiffActionMod commitForDiffActionDel ) type CommandForDiff struct { branch plumbing.ReferenceName branchAction commitForDiffAction commits []commitForDiffCommit pusher user.SimpleUser } type commitForDiffCommit struct { parentHash plumbing.Hash hash plumbing.Hash message string files []pluginLib.File date time.Time committerEmail string committerName string isSigned bool isValidSignature bool signingKey string } func MarshallOne(branch string, c commitForDiffCommit) (string, error) { pc := pluginLib.Commit{ Branch: branch, Hash: c.hash.String(), Message: c.message, Date: c.date, CommitterEmail: c.committerEmail, CommitterName: c.committerName, ParentHash: c.parentHash.String(), IsSigned: c.isSigned, IsValidSignature: c.isValidSignature, SigningKey: c.signingKey, } res, err := json.Marshal(pc) if err != nil { return "", err } return string(res), nil } func Marshall(branch string, commits []commitForDiffCommit) (string, error) { pcs := make([]pluginLib.Commit, len(commits)) for i, c := range commits { pcs[i] = pluginLib.Commit{ Branch: branch, Hash: c.hash.String(), Message: c.message, Date: c.date, CommitterEmail: c.committerEmail, CommitterName: c.committerName, ParentHash: c.parentHash.String(), IsSigned: c.isSigned, IsValidSignature: c.isValidSignature, SigningKey: c.signingKey, } } res, err := json.Marshal(pcs) if err != nil { return "", err } return string(res), nil } func CommandForDiffFromPackpCmd(ctx context.Context, log *logger.Logger, repo *repository.GitRootRepository, commands []*packp.Command, commitsByRef map[plumbing.ReferenceName][]plumbing.Hash, pusher user.SimpleUser) ([]CommandForDiff, error) { res := make([]CommandForDiff, len(commands)) repo.Configuration() groups, err := user.LoadGroup(repo) if err != nil { return nil, oops.Wrapf(err, "can't load group") } for i, cmd := range commands { branchAction := commitForDiffActionMod log.Debug("CommandForDiffFromPackpCmd cmd", logger.NewLoggerPair("new", cmd.New.String()), logger.NewLoggerPair("old", cmd.Old.String())) if cmd.New == plumbing.ZeroHash { branchAction = commitForDiffActionDel } else if cmd.Old == plumbing.ZeroHash { branchAction = commitForDiffActionAdd } commits := make([]commitForDiffCommit, 0) if cmd.Old.String() != cmd.New.String() { //nothing todo log.Debug("CommandForDiffFromPackpCmd commitsByRef", logger.NewLoggerPair("len", len(commitsByRef))) for _, commitHash := range commitsByRef[cmd.Name] { com, err := repo.Commit(commitHash) if err != nil { return res, oops.With("hash", commitHash.String()).Wrapf(err, "no exist") } log.Debug("CommandForDiffFromPackpCmd commit", logger.NewLoggerPair("msg", com.Message)) parent, err := com.Parent(0) // TODO what parent? if err != nil { return res, oops.With("hash", com.Hash.String()).Wrapf(err, "no parent") } patch, err := parent.PatchContext(ctx, com) if err != nil { return res, oops.With("hash", com.Hash.String(), "parentHash", parent.Hash.String()).Wrapf(err, "can't patch") } files := make([]pluginLib.File, 0) for _, d := range patch.FilePatches() { from, to := d.Files() fileAction := pluginLib.FileActionTypeAdd path := "" oldPath := "" fileHash := "" oldFileHash := "" if from == nil && to != nil { fileAction = pluginLib.FileActionTypeAdd path = to.Path() fileHash = to.Hash().String() } else if from != nil && to == nil { fileAction = pluginLib.FileActionTypeDel path = from.Path() fileHash = from.Hash().String() } else { fileAction = pluginLib.FileActionTypeMod path = to.Path() fileHash = to.Hash().String() oldPath = from.Path() oldFileHash = from.Hash().String() } files = append(files, pluginLib.File{Path: path, FileHash: fileHash, OldPath: oldPath, OldFileHash: oldFileHash, Action: fileAction}) } log.Debug("append com", logger.NewLoggerPair("hash", com.Hash.String())) commits = append(commits, commitToCommitForDiff(com, files, groups)) } } slices.Reverse(commits) res[i] = CommandForDiff{ branch: cmd.Name, branchAction: branchAction, commits: commits, pusher: pusher, } } return res, nil } func CommandForDiffFromCommitCmd(ctx context.Context, pusher user.SimpleUser, com repository.LastCommit, branch plumbing.ReferenceName) (CommandForDiff, error) { comP := plumbing.ZeroHash //todo make a constant in lib if len(com.Commit.ParentHashes) > 0 { comP = com.Commit.ParentHashes[0] } commits := []commitForDiffCommit{{ parentHash: comP, hash: com.Commit.Hash, message: com.Commit.Message, files: com.Filepath, date: com.Commit.Committer.When, committerEmail: com.Commit.Committer.Email, committerName: com.Commit.Committer.Name, isSigned: com.Commit.Signature != "", isValidSignature: true, //called only when plugin commit signingKey: pusher.Ssh, }} return CommandForDiff{ branch: branch, branchAction: commitForDiffActionMod, commits: commits, pusher: pusher, }, nil } func commitToCommitForDiff(com *object.Commit, files []pluginLib.File, groups []user.Group) commitForDiffCommit { comP := plumbing.ZeroHash //todo make a constant in lib if len(com.ParentHashes) > 0 { comP = com.ParentHashes[0] } isValid, key, _ := user.IsValidSignCommit(groups, com) // TODO manage error return commitForDiffCommit{ parentHash: comP, hash: com.Hash, message: com.Message, files: files, date: com.Committer.When, committerEmail: com.Committer.Email, committerName: com.Committer.Name, isSigned: com.Signature != "", isValidSignature: isValid, signingKey: key, } } func (c CommandForDiff) IsFileTouched(branch plumbing.ReferenceName, filepath string) bool { if branch != c.branch { return false } for _, com := range c.commits { for _, f := range com.files { if f.Path == filepath || f.OldPath == filepath { return true } } } return false } func IsFileTouched(cmds []CommandForDiff, branch plumbing.ReferenceName, filepath string) bool { return slices.ContainsFunc(cmds, func(c CommandForDiff) bool { return c.IsFileTouched(branch, filepath) }) }