1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
2//
3// SPDX-License-Identifier: EUPL-1.2
4
5package plugin
6
7import (
8 "context"
9 "encoding/json"
10 "slices"
11 "time"
12
13 "github.com/go-git/go-git/v6/plumbing"
14 "github.com/go-git/go-git/v6/plumbing/object"
15 "github.com/go-git/go-git/v6/plumbing/protocol/packp"
16 "github.com/samber/oops"
17 pluginLib "gitroot.dev/libs/golang/plugin/model"
18 "gitroot.dev/server/logger"
19 "gitroot.dev/server/repository"
20 "gitroot.dev/server/user"
21)
22
23type commitForDiffAction int
24
25const (
26 commitForDiffActionAdd commitForDiffAction = iota
27 commitForDiffActionMod
28 commitForDiffActionDel
29)
30
31type CommandForDiff struct {
32 branch plumbing.ReferenceName
33 branchAction commitForDiffAction
34 commits []commitForDiffCommit
35 pusher user.SimpleUser
36}
37
38type commitForDiffCommit struct {
39 parentHash plumbing.Hash
40 hash plumbing.Hash
41 message string
42 files []pluginLib.File
43 date time.Time
44 committerEmail string
45 committerName string
46 isSigned bool
47 isValidSignature bool
48 signingKey string
49}
50
51func MarshallOne(branch string, c commitForDiffCommit) (string, error) {
52 pc := pluginLib.Commit{
53 Branch: branch,
54 Hash: c.hash.String(),
55 Message: c.message,
56 Date: c.date,
57 CommitterEmail: c.committerEmail,
58 CommitterName: c.committerName,
59 ParentHash: c.parentHash.String(),
60 IsSigned: c.isSigned,
61 IsValidSignature: c.isValidSignature,
62 SigningKey: c.signingKey,
63 }
64 res, err := json.Marshal(pc)
65 if err != nil {
66 return "", err
67 }
68 return string(res), nil
69}
70
71func Marshall(branch string, commits []commitForDiffCommit) (string, error) {
72 pcs := make([]pluginLib.Commit, len(commits))
73 for i, c := range commits {
74 pcs[i] = pluginLib.Commit{
75 Branch: branch,
76 Hash: c.hash.String(),
77 Message: c.message,
78 Date: c.date,
79 CommitterEmail: c.committerEmail,
80 CommitterName: c.committerName,
81 ParentHash: c.parentHash.String(),
82 IsSigned: c.isSigned,
83 IsValidSignature: c.isValidSignature,
84 SigningKey: c.signingKey,
85 }
86 }
87 res, err := json.Marshal(pcs)
88 if err != nil {
89 return "", err
90 }
91 return string(res), nil
92}
93
94func CommandForDiffFromPackpCmd(ctx context.Context, log *logger.Logger, repo *repository.GitRootRepository, commands []*packp.Command, commitsByRef map[plumbing.ReferenceName][]plumbing.Hash, pusher user.SimpleUser) ([]CommandForDiff, error) {
95 res := make([]CommandForDiff, len(commands))
96 repo.Configuration()
97 for i, cmd := range commands {
98 branchAction := commitForDiffActionMod
99 log.Debug("CommandForDiffFromPackpCmd cmd", logger.NewLoggerPair("new", cmd.New.String()), logger.NewLoggerPair("old", cmd.Old.String()))
100 if cmd.New == plumbing.ZeroHash {
101 branchAction = commitForDiffActionDel
102 } else if cmd.Old == plumbing.ZeroHash {
103 branchAction = commitForDiffActionAdd
104 }
105 groups := []user.Group{}
106 if branchAction != commitForDiffActionDel {
107 g, err := user.LoadGroup(repo, cmd.Name)
108 if err != nil {
109 return nil, oops.Wrapf(err, "can't load group for %s", cmd.Name)
110 }
111 groups = g
112 }
113
114 commits := make([]commitForDiffCommit, 0)
115 if cmd.Old.String() != cmd.New.String() { //nothing todo
116 log.Debug("CommandForDiffFromPackpCmd commitsByRef", logger.NewLoggerPair("len", len(commitsByRef)))
117 for _, commitHash := range commitsByRef[cmd.Name] {
118 com, err := repo.Commit(commitHash)
119 if err != nil {
120 return res, oops.With("hash", commitHash.String()).Wrapf(err, "no exist")
121 }
122 log.Debug("CommandForDiffFromPackpCmd commit", logger.NewLoggerPair("msg", com.Message))
123 parent, err := com.Parent(0) // TODO what parent?
124 if err != nil {
125 return res, oops.With("hash", com.Hash.String()).Wrapf(err, "no parent")
126 }
127 patch, err := parent.PatchContext(ctx, com)
128 if err != nil {
129 return res, oops.With("hash", com.Hash.String(), "parentHash", parent.Hash.String()).Wrapf(err, "can't patch")
130 }
131 files := make([]pluginLib.File, 0)
132 for _, d := range patch.FilePatches() {
133 from, to := d.Files()
134 fileAction := pluginLib.FileActionTypeAdd
135 path := ""
136 oldPath := ""
137 fileHash := ""
138 oldFileHash := ""
139 if from == nil && to != nil {
140 fileAction = pluginLib.FileActionTypeAdd
141 path = to.Path()
142 fileHash = to.Hash().String()
143 } else if from != nil && to == nil {
144 fileAction = pluginLib.FileActionTypeDel
145 path = from.Path()
146 fileHash = from.Hash().String()
147 } else {
148 fileAction = pluginLib.FileActionTypeMod
149 path = to.Path()
150 fileHash = to.Hash().String()
151 oldPath = from.Path()
152 oldFileHash = from.Hash().String()
153 }
154 files = append(files, pluginLib.File{Path: path, FileHash: fileHash, OldPath: oldPath, OldFileHash: oldFileHash, Action: fileAction})
155 }
156 log.Debug("append com", logger.NewLoggerPair("hash", com.Hash.String()))
157 commits = append(commits, commitToCommitForDiff(com, files, groups))
158 }
159 }
160 slices.Reverse(commits)
161 res[i] = CommandForDiff{
162 branch: cmd.Name,
163 branchAction: branchAction,
164 commits: commits,
165 pusher: pusher,
166 }
167 }
168 return res, nil
169}
170
171func CommandForDiffFromCommitCmd(ctx context.Context, pusher user.SimpleUser, com repository.LastCommit, branch plumbing.ReferenceName) (CommandForDiff, error) {
172 comP := plumbing.ZeroHash //todo make a constant in lib
173 if len(com.Commit.ParentHashes) > 0 {
174 comP = com.Commit.ParentHashes[0]
175 }
176 commits := []commitForDiffCommit{{
177 parentHash: comP,
178 hash: com.Commit.Hash,
179 message: com.Commit.Message,
180 files: com.Filepath,
181 date: com.Commit.Committer.When,
182 committerEmail: com.Commit.Committer.Email,
183 committerName: com.Commit.Committer.Name,
184 isSigned: com.Commit.Signature != "",
185 isValidSignature: true, //called only when plugin commit
186 signingKey: pusher.Ssh,
187 }}
188 return CommandForDiff{
189 branch: branch,
190 branchAction: commitForDiffActionMod,
191 commits: commits,
192 pusher: pusher,
193 }, nil
194}
195
196func commitToCommitForDiff(com *object.Commit, files []pluginLib.File, groups []user.Group) commitForDiffCommit {
197 comP := plumbing.ZeroHash //todo make a constant in lib
198 if len(com.ParentHashes) > 0 {
199 comP = com.ParentHashes[0]
200 }
201 isValid, key, _ := user.IsValidSignCommit(groups, com) // TODO manage error
202 return commitForDiffCommit{
203 parentHash: comP,
204 hash: com.Hash,
205 message: com.Message,
206 files: files,
207 date: com.Committer.When,
208 committerEmail: com.Committer.Email,
209 committerName: com.Committer.Name,
210 isSigned: com.Signature != "",
211 isValidSignature: isValid,
212 signingKey: key,
213 }
214}
215
216func (c CommandForDiff) IsFileTouched(branch plumbing.ReferenceName, filepath string) bool {
217 if branch != c.branch {
218 return false
219 }
220 for _, com := range c.commits {
221 for _, f := range com.files {
222 if f.Path == filepath || f.OldPath == filepath {
223 return true
224 }
225 }
226 }
227 return false
228}
229
230func IsFileTouched(cmds []CommandForDiff, branch plumbing.ReferenceName, filepath string) bool {
231 return slices.ContainsFunc(cmds, func(c CommandForDiff) bool {
232 return c.IsFileTouched(branch, filepath)
233 })
234}