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}