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}