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 repository
  6
  7import (
  8	"errors"
  9	"fmt"
 10	"io"
 11
 12	git "github.com/go-git/go-git/v5"
 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/storer"
 16	"github.com/samber/oops"
 17	"github.com/sergi/go-diff/diffmatchpatch"
 18	pluginLib "gitroot.dev/libs/golang/plugin/model"
 19)
 20
 21type LastCommit struct {
 22	Commit   *object.Commit
 23	Filepath []pluginLib.File
 24}
 25
 26func commitChange(com *object.Commit, useToFound bool, toFound map[string]bool) (LastCommit, error) {
 27	currentRes := LastCommit{Commit: com, Filepath: []pluginLib.File{}}
 28	tree, err := com.Tree()
 29	if err != nil {
 30		return currentRes, oops.Wrapf(err, "can't tree commit %s", com.Hash.String())
 31	}
 32	p, err := com.Parents().Next()
 33	if err != nil && !errors.Is(err, io.EOF) {
 34		return currentRes, oops.Wrapf(err, "can't parent %s", com.Hash.String())
 35	}
 36	var pTree *object.Tree
 37	if p != nil {
 38		pTree, err = p.Tree()
 39		if err != nil {
 40			return currentRes, oops.Wrapf(err, "can't pTree commit %s", p.Hash.String())
 41		}
 42	}
 43	changes, err := object.DiffTree(pTree, tree)
 44	if err != nil {
 45		return currentRes, oops.Wrapf(err, "can't DiffTree commit %s and %s", com.Hash.String(), p.Hash.String())
 46	}
 47	for _, file := range changes {
 48		var f pluginLib.File
 49		if file.From.Name == "" && file.To.Name != "" {
 50			f = pluginLib.File{
 51				Path:     file.To.Name,
 52				FileHash: file.To.TreeEntry.Hash.String(),
 53				Action:   pluginLib.FileActionTypeAdd,
 54			}
 55		} else if file.From.Name != "" && file.To.Name == "" {
 56			f = pluginLib.File{
 57				Path:     file.From.Name,
 58				FileHash: file.From.TreeEntry.Hash.String(),
 59				Action:   pluginLib.FileActionTypeDel,
 60			}
 61		} else {
 62			f = pluginLib.File{
 63				Path:        file.To.Name,
 64				FileHash:    file.To.TreeEntry.Hash.String(),
 65				OldPath:     file.From.Name,
 66				OldFileHash: file.From.TreeEntry.Hash.String(),
 67				Action:      pluginLib.FileActionTypeMod,
 68			}
 69		}
 70
 71		if useToFound {
 72			if val, ok := toFound[f.Path]; ok && !val {
 73				currentRes.Filepath = append(currentRes.Filepath, f)
 74				toFound[f.Path] = true
 75			}
 76		} else {
 77			currentRes.Filepath = append(currentRes.Filepath, f)
 78		}
 79	}
 80	return currentRes, nil
 81}
 82
 83func (repo *GitRootRepository) GetLastCommits(filepath []string) ([]LastCommit, error) {
 84	comIter, err := repo.repo.Log(&git.LogOptions{Order: git.LogOrderCommitterTime})
 85	if err != nil {
 86		return nil, repo.errorHandler.Wrapf(err, "can't Log")
 87	}
 88	defer comIter.Close()
 89	nbToFound := len(filepath)
 90	found := make(map[string]bool, len(filepath))
 91	for _, path := range filepath {
 92		found[path] = false
 93	}
 94	res := []LastCommit{}
 95	err = comIter.ForEach(func(com *object.Commit) error {
 96		currentRes, err := commitChange(com, true, found)
 97		if err != nil {
 98			return oops.Wrapf(err, "commitChange")
 99		}
100
101		if len(currentRes.Filepath) > 0 {
102			res = append(res, currentRes)
103			for _, path := range currentRes.Filepath {
104				found[path.Path] = true
105				nbToFound = nbToFound - 1
106			}
107		}
108
109		if nbToFound == 0 {
110			return storer.ErrStop
111		}
112		return nil
113	})
114	return res, oops.Wrapf(err, "err in log fro GetLastCommit")
115}
116
117func (repo *GitRootRepositoryWrite) GetDiff(hash plumbing.Hash, oldFilepath string, newFilepath string) (string, error) {
118	com, err := repo.repoWrite.CommitObject(hash)
119	if err != nil {
120		return "", repo.errorHandler.Wrapf(err, "can't find CommitObject")
121	}
122	parent, _ := com.Parent(0)
123	src, err := getFileContent(parent, oldFilepath)
124	if err != nil {
125		return "", repo.errorHandler.With("hash", parent.Hash.String(), "filepath", oldFilepath).Wrapf(err, "can't getFileContent parent")
126	}
127	dst, err := getFileContent(com, newFilepath)
128	if err != nil {
129		return "", repo.errorHandler.With("hash", hash.String(), "filepath", newFilepath).Wrapf(err, "can't getFileContent")
130	}
131	return "```diff\n" + diffmatchpatch.New().Unified(
132		src,
133		dst,
134		diffmatchpatch.UnifiedContextLines(3),
135		diffmatchpatch.UnifiedLabels(fmt.Sprintf("a/%s", oldFilepath), fmt.Sprintf("b/%s", newFilepath)),
136	) + "```\n", nil
137}
138
139func getFileContent(com *object.Commit, filepath string) (string, error) {
140	fromTree, err := com.Tree()
141	if err != nil {
142		return "", err
143	}
144	fromFile, err := fromTree.File(filepath)
145	if err != nil {
146		return "", err
147	}
148	return fromFile.Contents()
149}