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	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}