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 "fmt"
11 "slices"
12 "time"
13
14 "github.com/go-git/go-git/v5/plumbing"
15 "github.com/go-git/go-git/v5/plumbing/object"
16 "github.com/go-git/go-git/v5/plumbing/protocol/packp"
17 "github.com/samber/oops"
18 pluginLib "gitroot.dev/libs/golang/plugin/model"
19 "gitroot.dev/server/logger"
20 "gitroot.dev/server/repository"
21 "gitroot.dev/server/user"
22)
23
24type commitForDiffAction int
25
26const (
27 commitForDiffActionAdd commitForDiffAction = iota
28 commitForDiffActionMod
29 commitForDiffActionDel
30)
31
32type CommandForDiff struct {
33 branch plumbing.ReferenceName
34 branchAction commitForDiffAction
35 commits []commitForDiffCommit
36 pusher user.SimpleUser
37}
38
39type commitForDiffCommit struct {
40 parentHash plumbing.Hash
41 hash plumbing.Hash
42 message string
43 sshCommiter string
44 files []commitForDiffFile
45 date time.Time
46 committer string
47}
48
49type commitForDiffFile struct {
50 path string
51 oldPath string //only if action == commitForDiffActionMod
52 action commitForDiffAction
53}
54
55func MarshallOne(branch string, c commitForDiffCommit) (string, error) {
56 pc := pluginLib.Commit{
57 Branch: branch,
58 Hash: c.hash.String(),
59 Message: c.message,
60 Date: c.date,
61 Committer: c.committer,
62 ParentHash: c.parentHash.String(),
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 Committer: c.committer,
80 ParentHash: c.parentHash.String(),
81 }
82 }
83 res, err := json.Marshal(pcs)
84 if err != nil {
85 return "", err
86 }
87 return string(res), nil
88}
89
90func CommandForDiffFromPackpCmd(ctx context.Context, log *logger.Logger, repo *repository.GitRootRepository, commands []*packp.Command, pusher user.SimpleUser) ([]CommandForDiff, error) {
91 res := make([]CommandForDiff, len(commands))
92 for i, cmd := range commands {
93 branchAction := commitForDiffActionMod
94 log.Debug("CommandForDiffFromPackpCmd cmd", logger.NewLoggerPair("new", cmd.New.String()), logger.NewLoggerPair("old", cmd.Old.String()))
95 if cmd.New == plumbing.ZeroHash {
96 branchAction = commitForDiffActionDel
97 } else if cmd.Old == plumbing.ZeroHash {
98 branchAction = commitForDiffActionAdd
99 }
100 commits := make([]commitForDiffCommit, 0)
101 if cmd.Old.String() != cmd.New.String() { //nothing todo
102 if err := repo.WalkCommit(cmd.Old, cmd.New, func(com *object.Commit) error {
103 parent, err := com.Parent(0) // TODO what parent?
104 if err != nil {
105 return oops.With("hash", com.Hash.String()).Wrapf(err, "no parent")
106 }
107 patch, err := parent.PatchContext(ctx, com)
108 if err != nil {
109 return oops.With("hash", com.Hash.String(), "parentHash", parent.Hash.String()).Wrapf(err, "can't patch")
110 }
111 files := make([]commitForDiffFile, 0)
112 for _, d := range patch.FilePatches() {
113 from, to := d.Files()
114 fileAction := commitForDiffActionMod
115 path := ""
116 oldPath := ""
117 if from == nil && to != nil {
118 fileAction = commitForDiffActionAdd
119 path = to.Path()
120 } else if from != nil && to == nil {
121 fileAction = commitForDiffActionDel
122 path = from.Path()
123 } else {
124 fileAction = commitForDiffActionMod
125 path = to.Path()
126 oldPath = from.Path()
127 }
128 files = append(files, commitForDiffFile{path: path, oldPath: oldPath, action: fileAction})
129 }
130 commits = append(commits, commitToCommitForDiff(com, files))
131 return nil
132 }); err != nil {
133 return nil, oops.With("old", cmd.Old.String(), "new", cmd.New.String()).Wrapf(err, "can't walk")
134 }
135 }
136 slices.Reverse(commits)
137 res[i] = CommandForDiff{
138 branch: cmd.Name,
139 branchAction: branchAction,
140 commits: commits,
141 pusher: pusher,
142 }
143 }
144 return res, nil
145}
146
147func CommandForDiffFromCommitCmd(ctx context.Context, pusher user.SimpleUser, com repository.LastCommit, branch plumbing.ReferenceName) (CommandForDiff, error) {
148 files := []commitForDiffFile{}
149 for _, f := range com.Filepath {
150 files = append(files, commitForDiffFile{
151 path: f,
152 oldPath: f,
153 action: commitForDiffActionMod, //TODO can be add
154 })
155 }
156 comP := plumbing.ZeroHash //todo make a constant in lib
157 if len(com.Commit.ParentHashes) > 0 {
158 comP = com.Commit.ParentHashes[0]
159 }
160 commits := []commitForDiffCommit{{
161 parentHash: comP,
162 hash: com.Commit.Hash,
163 message: com.Commit.Message,
164 sshCommiter: pusher.Ssh,
165 files: files,
166 date: com.Commit.Committer.When,
167 committer: fmt.Sprintf("%s (%s)", pusher.Email, pusher.Pseudo),
168 }}
169 return CommandForDiff{
170 branch: branch,
171 branchAction: commitForDiffActionMod,
172 commits: commits,
173 pusher: pusher,
174 }, nil
175}
176
177func commitToCommitForDiff(com *object.Commit, files []commitForDiffFile) commitForDiffCommit {
178 comP := plumbing.ZeroHash //todo make a constant in lib
179 if len(com.ParentHashes) > 0 {
180 comP = com.ParentHashes[0]
181 }
182 return commitForDiffCommit{
183 parentHash: comP,
184 hash: com.Hash,
185 message: com.Message,
186 sshCommiter: com.PGPSignature,
187 files: files,
188 date: com.Committer.When,
189 committer: fmt.Sprintf("%s (%s)", com.Committer.Email, com.Committer.Name),
190 }
191}
192
193func (c CommandForDiff) IsFileTouched(branch plumbing.ReferenceName, filepath string) bool {
194 if branch != c.branch {
195 return false
196 }
197 for _, com := range c.commits {
198 for _, f := range com.files {
199 if f.path == filepath || f.oldPath == filepath {
200 return true
201 }
202 }
203 }
204 return false
205}
206
207func IsFileTouched(cmds []CommandForDiff, branch plumbing.ReferenceName, filepath string) bool {
208 for _, c := range cmds {
209 if branch != c.branch {
210 continue
211 }
212 for _, com := range c.commits {
213 for _, f := range com.files {
214 if f.path == filepath || f.oldPath == filepath {
215 return true
216 }
217 }
218 }
219 }
220 return false
221}