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}