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}