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}