GitRoot
craft your forge, build your project, grow your community freely
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/v5/plumbing"
14 "github.com/go-git/go-git/v5/plumbing/object"
15 "github.com/go-git/go-git/v5/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, pusher user.SimpleUser) ([]CommandForDiff, error) {
95 res := make([]CommandForDiff, len(commands))
96 repo.Configuration()
97 groups, err := user.LoadGroup(repo)
98 if err != nil {
99 return nil, oops.Wrapf(err, "can't load group")
100 }
101 for i, cmd := range commands {
102 branchAction := commitForDiffActionMod
103 log.Debug("CommandForDiffFromPackpCmd cmd", logger.NewLoggerPair("new", cmd.New.String()), logger.NewLoggerPair("old", cmd.Old.String()))
104 if cmd.New == plumbing.ZeroHash {
105 branchAction = commitForDiffActionDel
106 } else if cmd.Old == plumbing.ZeroHash {
107 branchAction = commitForDiffActionAdd
108 }
109 commits := make([]commitForDiffCommit, 0)
110 if cmd.Old.String() != cmd.New.String() { //nothing todo
111 if err := repo.WalkCommit(cmd.Old, cmd.New, func(com *object.Commit) error {
112 parent, err := com.Parent(0) // TODO what parent?
113 if err != nil {
114 return oops.With("hash", com.Hash.String()).Wrapf(err, "no parent")
115 }
116 patch, err := parent.PatchContext(ctx, com)
117 if err != nil {
118 return oops.With("hash", com.Hash.String(), "parentHash", parent.Hash.String()).Wrapf(err, "can't patch")
119 }
120 files := make([]pluginLib.File, 0)
121 for _, d := range patch.FilePatches() {
122 from, to := d.Files()
123 fileAction := pluginLib.FileActionTypeAdd
124 path := ""
125 oldPath := ""
126 fileHash := ""
127 oldFileHash := ""
128 if from == nil && to != nil {
129 fileAction = pluginLib.FileActionTypeAdd
130 path = to.Path()
131 fileHash = to.Hash().String()
132 } else if from != nil && to == nil {
133 fileAction = pluginLib.FileActionTypeDel
134 path = from.Path()
135 fileHash = from.Hash().String()
136 } else {
137 fileAction = pluginLib.FileActionTypeMod
138 path = to.Path()
139 fileHash = to.Hash().String()
140 oldPath = from.Path()
141 oldFileHash = from.Hash().String()
142 }
143 files = append(files, pluginLib.File{Path: path, FileHash: fileHash, OldPath: oldPath, OldFileHash: oldFileHash, Action: fileAction})
144 }
145 commits = append(commits, commitToCommitForDiff(com, files, groups))
146 return nil
147 }); err != nil {
148 return nil, oops.With("old", cmd.Old.String(), "new", cmd.New.String()).Wrapf(err, "can't walk")
149 }
150 }
151 slices.Reverse(commits)
152 res[i] = CommandForDiff{
153 branch: cmd.Name,
154 branchAction: branchAction,
155 commits: commits,
156 pusher: pusher,
157 }
158 }
159 return res, nil
160}
161
162func CommandForDiffFromCommitCmd(ctx context.Context, pusher user.SimpleUser, com repository.LastCommit, branch plumbing.ReferenceName) (CommandForDiff, error) {
163 comP := plumbing.ZeroHash //todo make a constant in lib
164 if len(com.Commit.ParentHashes) > 0 {
165 comP = com.Commit.ParentHashes[0]
166 }
167 commits := []commitForDiffCommit{{
168 parentHash: comP,
169 hash: com.Commit.Hash,
170 message: com.Commit.Message,
171 files: com.Filepath,
172 date: com.Commit.Committer.When,
173 committerEmail: com.Commit.Committer.Email,
174 committerName: com.Commit.Committer.Name,
175 isSigned: com.Commit.PGPSignature != "",
176 isValidSignature: true, //called only when plugin commit
177 signingKey: pusher.Ssh,
178 }}
179 return CommandForDiff{
180 branch: branch,
181 branchAction: commitForDiffActionMod,
182 commits: commits,
183 pusher: pusher,
184 }, nil
185}
186
187func commitToCommitForDiff(com *object.Commit, files []pluginLib.File, groups []user.Group) commitForDiffCommit {
188 comP := plumbing.ZeroHash //todo make a constant in lib
189 if len(com.ParentHashes) > 0 {
190 comP = com.ParentHashes[0]
191 }
192 isValid, key, _ := user.IsValidSignCommit(groups, com) // TODO manage error
193 return commitForDiffCommit{
194 parentHash: comP,
195 hash: com.Hash,
196 message: com.Message,
197 files: files,
198 date: com.Committer.When,
199 committerEmail: com.Committer.Email,
200 committerName: com.Committer.Name,
201 isSigned: com.PGPSignature != "",
202 isValidSignature: isValid,
203 signingKey: key,
204 }
205}
206
207func (c CommandForDiff) IsFileTouched(branch plumbing.ReferenceName, filepath string) bool {
208 if branch != c.branch {
209 return false
210 }
211 for _, com := range c.commits {
212 for _, f := range com.files {
213 if f.Path == filepath || f.OldPath == filepath {
214 return true
215 }
216 }
217 }
218 return false
219}
220
221func IsFileTouched(cmds []CommandForDiff, branch plumbing.ReferenceName, filepath string) bool {
222 return slices.ContainsFunc(cmds, func(c CommandForDiff) bool {
223 return c.IsFileTouched(branch, filepath)
224 })
225}