1package git
2
3import (
4 "fmt"
5 "log"
6 "strings"
7
8 "github.com/bluekeyes/go-gitdiff/gitdiff"
9 "github.com/go-git/go-git/v5/plumbing"
10 "github.com/go-git/go-git/v5/plumbing/object"
11 "tangled.sh/tangled.sh/core/types"
12)
13
14func (g *GitRepo) Diff() (*types.NiceDiff, error) {
15 c, err := g.r.CommitObject(g.h)
16 if err != nil {
17 return nil, fmt.Errorf("commit object: %w", err)
18 }
19
20 patch := &object.Patch{}
21 commitTree, err := c.Tree()
22 parent := &object.Commit{}
23 if err == nil {
24 parentTree := &object.Tree{}
25 if c.NumParents() != 0 {
26 parent, err = c.Parents().Next()
27 if err == nil {
28 parentTree, err = parent.Tree()
29 if err == nil {
30 patch, err = parentTree.Patch(commitTree)
31 if err != nil {
32 return nil, fmt.Errorf("patch: %w", err)
33 }
34 }
35 }
36 } else {
37 patch, err = parentTree.Patch(commitTree)
38 if err != nil {
39 return nil, fmt.Errorf("patch: %w", err)
40 }
41 }
42 }
43
44 diffs, _, err := gitdiff.Parse(strings.NewReader(patch.String()))
45 if err != nil {
46 log.Println(err)
47 }
48
49 nd := types.NiceDiff{}
50 for _, d := range diffs {
51 ndiff := types.Diff{}
52 ndiff.Name.New = d.NewName
53 ndiff.Name.Old = d.OldName
54 ndiff.IsBinary = d.IsBinary
55 ndiff.IsNew = d.IsNew
56 ndiff.IsDelete = d.IsDelete
57 ndiff.IsCopy = d.IsCopy
58 ndiff.IsRename = d.IsRename
59
60 for _, tf := range d.TextFragments {
61 ndiff.TextFragments = append(ndiff.TextFragments, *tf)
62 for _, l := range tf.Lines {
63 switch l.Op {
64 case gitdiff.OpAdd:
65 nd.Stat.Insertions += 1
66 case gitdiff.OpDelete:
67 nd.Stat.Deletions += 1
68 }
69 }
70 }
71
72 nd.Diff = append(nd.Diff, ndiff)
73 }
74
75 nd.Stat.FilesChanged = len(diffs)
76 nd.Commit.This = c.Hash.String()
77
78 if parent.Hash.IsZero() {
79 nd.Commit.Parent = ""
80 } else {
81 nd.Commit.Parent = parent.Hash.String()
82 }
83 nd.Commit.Author = c.Author
84 nd.Commit.Message = c.Message
85
86 return &nd, nil
87}
88
89func (g *GitRepo) DiffTree(commit1, commit2 *object.Commit) (*types.DiffTree, error) {
90 tree1, err := commit1.Tree()
91 if err != nil {
92 return nil, err
93 }
94
95 tree2, err := commit2.Tree()
96 if err != nil {
97 return nil, err
98 }
99
100 diff, err := object.DiffTree(tree1, tree2)
101 if err != nil {
102 return nil, err
103 }
104
105 patch, err := diff.Patch()
106 if err != nil {
107 return nil, err
108 }
109
110 diffs, _, err := gitdiff.Parse(strings.NewReader(patch.String()))
111 if err != nil {
112 return nil, err
113 }
114
115 return &types.DiffTree{
116 Rev1: commit1.Hash.String(),
117 Rev2: commit2.Hash.String(),
118 Patch: patch.String(),
119 Diff: diffs,
120 }, nil
121}
122
123func (g *GitRepo) MergeBase(commit1, commit2 *object.Commit) (*object.Commit, error) {
124 isAncestor, err := commit1.IsAncestor(commit2)
125 if err != nil {
126 return nil, err
127 }
128
129 if isAncestor {
130 return commit1, nil
131 }
132
133 mergeBase, err := commit1.MergeBase(commit2)
134 if err != nil {
135 return nil, err
136 }
137
138 if len(mergeBase) == 0 {
139 return nil, fmt.Errorf("failed to find a merge-base")
140 }
141
142 return mergeBase[0], nil
143}
144
145func (g *GitRepo) ResolveRevision(revStr string) (*object.Commit, error) {
146 rev, err := g.r.ResolveRevision(plumbing.Revision(revStr))
147 if err != nil {
148 return nil, fmt.Errorf("resolving revision %s: %w", revStr, err)
149 }
150
151 commit, err := g.r.CommitObject(*rev)
152 if err != nil {
153
154 return nil, fmt.Errorf("getting commit for %s: %w", revStr, err)
155 }
156
157 return commit, nil
158}