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