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