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 []pluginLib.File
45 date time.Time
46 committer string
47}
48
49func MarshallOne(branch string, c commitForDiffCommit) (string, error) {
50 pc := pluginLib.Commit{
51 Branch: branch,
52 Hash: c.hash.String(),
53 Message: c.message,
54 Date: c.date,
55 Committer: c.committer,
56 ParentHash: c.parentHash.String(),
57 }
58 res, err := json.Marshal(pc)
59 if err != nil {
60 return "", err
61 }
62 return string(res), nil
63}
64
65func Marshall(branch string, commits []commitForDiffCommit) (string, error) {
66 pcs := make([]pluginLib.Commit, len(commits))
67 for i, c := range commits {
68 pcs[i] = pluginLib.Commit{
69 Branch: branch,
70 Hash: c.hash.String(),
71 Message: c.message,
72 Date: c.date,
73 Committer: c.committer,
74 ParentHash: c.parentHash.String(),
75 }
76 }
77 res, err := json.Marshal(pcs)
78 if err != nil {
79 return "", err
80 }
81 return string(res), nil
82}
83
84func CommandForDiffFromPackpCmd(ctx context.Context, log *logger.Logger, repo *repository.GitRootRepository, commands []*packp.Command, pusher user.SimpleUser) ([]CommandForDiff, error) {
85 res := make([]CommandForDiff, len(commands))
86 for i, cmd := range commands {
87 branchAction := commitForDiffActionMod
88 log.Debug("CommandForDiffFromPackpCmd cmd", logger.NewLoggerPair("new", cmd.New.String()), logger.NewLoggerPair("old", cmd.Old.String()))
89 if cmd.New == plumbing.ZeroHash {
90 branchAction = commitForDiffActionDel
91 } else if cmd.Old == plumbing.ZeroHash {
92 branchAction = commitForDiffActionAdd
93 }
94 commits := make([]commitForDiffCommit, 0)
95 if cmd.Old.String() != cmd.New.String() { //nothing todo
96 if err := repo.WalkCommit(cmd.Old, cmd.New, func(com *object.Commit) error {
97 parent, err := com.Parent(0) // TODO what parent?
98 if err != nil {
99 return oops.With("hash", com.Hash.String()).Wrapf(err, "no parent")
100 }
101 patch, err := parent.PatchContext(ctx, com)
102 if err != nil {
103 return oops.With("hash", com.Hash.String(), "parentHash", parent.Hash.String()).Wrapf(err, "can't patch")
104 }
105 files := make([]pluginLib.File, 0)
106 for _, d := range patch.FilePatches() {
107 from, to := d.Files()
108 fileAction := pluginLib.FileActionTypeAdd
109 path := ""
110 oldPath := ""
111 fileHash := ""
112 oldFileHash := ""
113 if from == nil && to != nil {
114 fileAction = pluginLib.FileActionTypeAdd
115 path = to.Path()
116 fileHash = to.Hash().String()
117 } else if from != nil && to == nil {
118 fileAction = pluginLib.FileActionTypeDel
119 path = from.Path()
120 fileHash = from.Hash().String()
121 } else {
122 fileAction = pluginLib.FileActionTypeMod
123 path = to.Path()
124 fileHash = to.Hash().String()
125 oldPath = from.Path()
126 oldFileHash = from.Hash().String()
127 }
128 files = append(files, pluginLib.File{Path: path, FileHash: fileHash, OldPath: oldPath, OldFileHash: oldFileHash, 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 comP := plumbing.ZeroHash //todo make a constant in lib
149 if len(com.Commit.ParentHashes) > 0 {
150 comP = com.Commit.ParentHashes[0]
151 }
152 commits := []commitForDiffCommit{{
153 parentHash: comP,
154 hash: com.Commit.Hash,
155 message: com.Commit.Message,
156 sshCommiter: pusher.Ssh,
157 files: com.Filepath,
158 date: com.Commit.Committer.When,
159 committer: fmt.Sprintf("%s (%s)", pusher.Email, pusher.Pseudo),
160 }}
161 return CommandForDiff{
162 branch: branch,
163 branchAction: commitForDiffActionMod,
164 commits: commits,
165 pusher: pusher,
166 }, nil
167}
168
169func commitToCommitForDiff(com *object.Commit, files []pluginLib.File) commitForDiffCommit {
170 comP := plumbing.ZeroHash //todo make a constant in lib
171 if len(com.ParentHashes) > 0 {
172 comP = com.ParentHashes[0]
173 }
174 return commitForDiffCommit{
175 parentHash: comP,
176 hash: com.Hash,
177 message: com.Message,
178 sshCommiter: com.PGPSignature,
179 files: files,
180 date: com.Committer.When,
181 committer: fmt.Sprintf("%s (%s)", com.Committer.Email, com.Committer.Name),
182 }
183}
184
185func (c CommandForDiff) IsFileTouched(branch plumbing.ReferenceName, filepath string) bool {
186 if branch != c.branch {
187 return false
188 }
189 for _, com := range c.commits {
190 for _, f := range com.files {
191 if f.Path == filepath || f.OldPath == filepath {
192 return true
193 }
194 }
195 }
196 return false
197}
198
199func IsFileTouched(cmds []CommandForDiff, branch plumbing.ReferenceName, filepath string) bool {
200 return slices.ContainsFunc(cmds, func(c CommandForDiff) bool {
201 return c.IsFileTouched(branch, filepath)
202 })
203}