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}