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	"strings"
 12
 13	git "github.com/go-git/go-git/v5"
 14	"github.com/go-git/go-git/v5/plumbing"
 15	"github.com/go-git/go-git/v5/plumbing/format/diff"
 16	"github.com/go-git/go-git/v5/plumbing/object"
 17	"github.com/go-git/go-git/v5/plumbing/storer"
 18	"github.com/samber/oops"
 19)
 20
 21type LastCommit struct {
 22	Commit   *object.Commit
 23	Filepath []string
 24}
 25
 26func commitChange(com *object.Commit, useToFound bool, toFound map[string]bool) (LastCommit, error) {
 27	currentRes := LastCommit{Commit: com, Filepath: []string{}}
 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(tree, pTree)
 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		name := file.From.Name
 49		if file.From.Name == "" {
 50			name = file.To.Name
 51		}
 52		if useToFound {
 53			if val, ok := toFound[name]; ok && !val {
 54				currentRes.Filepath = append(currentRes.Filepath, name)
 55				toFound[name] = true
 56			}
 57		} else {
 58			currentRes.Filepath = append(currentRes.Filepath, name)
 59		}
 60	}
 61	return currentRes, nil
 62}
 63
 64func (repo *GitRootRepository) GetLastCommits(filepath []string) ([]LastCommit, error) {
 65	comIter, err := repo.repo.Log(&git.LogOptions{Order: git.LogOrderCommitterTime})
 66	if err != nil {
 67		return nil, repo.errorHandler.Wrapf(err, "can't Log")
 68	}
 69	defer comIter.Close()
 70	nbToFound := len(filepath)
 71	found := make(map[string]bool, len(filepath))
 72	for _, path := range filepath {
 73		found[path] = false
 74	}
 75	res := []LastCommit{}
 76	err = comIter.ForEach(func(com *object.Commit) error {
 77		currentRes, err := commitChange(com, true, found)
 78		if err != nil {
 79			return oops.Wrapf(err, "commitChange")
 80		}
 81
 82		if len(currentRes.Filepath) > 0 {
 83			res = append(res, currentRes)
 84			for _, path := range currentRes.Filepath {
 85				found[path] = true
 86				nbToFound = nbToFound - 1
 87			}
 88		}
 89
 90		if nbToFound == 0 {
 91			return storer.ErrStop
 92		}
 93		return nil
 94	})
 95	return res, oops.Wrapf(err, "err in log fro GetLastCommit")
 96}
 97
 98func (repo *GitRootRepositoryWrite) GetDiff(hash plumbing.Hash, filepath string) (string, error) {
 99	com, err := repo.repoWrite.CommitObject(hash)
100	if err != nil {
101		return "", repo.errorHandler.Wrapf(err, "can't find CommitObject")
102	}
103	parent, _ := com.Parent(0)
104	diffStr := "```diff"
105	patch, _ := parent.Patch(com) // TODO find a way to get diff only on one file
106	for _, d := range patch.FilePatches() {
107		from, to := d.Files()
108		if from != nil && to != nil && from.Path() == to.Path() && from.Path() == filepath {
109			for _, chunk := range d.Chunks() {
110				op := "="
111				if chunk.Type() == diff.Add {
112					op = "+"
113				} else if chunk.Type() == diff.Delete {
114					op = "-"
115				}
116				chunksStr := strings.Split(chunk.Content(), "\n")
117				for i, c := range strings.Split(chunk.Content(), "\n") {
118					chunksStr[i] = fmt.Sprintf("%s %s", op, c)
119				}
120				diffStr = fmt.Sprintf("%s\n%s", diffStr, strings.Join(chunksStr, "\n"))
121			}
122			break
123		}
124	}
125	return diffStr + "\n```\n", nil
126}