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