Skip to content

Commit fda64ff

Browse files
committed
feat: adding git2go
1 parent 4c833e0 commit fda64ff

11 files changed

Lines changed: 857 additions & 112 deletions

File tree

.envrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export DYLD_LIBRARY_PATH=/usr/local/lib:$DYLD_LIBRARY_PATH

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ require (
135135
github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef // indirect
136136
github.com/jmespath/go-jmespath v0.4.0 // indirect
137137
github.com/lib/pq v1.10.5 // indirect
138+
github.com/libgit2/git2go/v34 v34.0.0
138139
github.com/mattn/go-colorable v0.1.13 // indirect
139140
github.com/mattn/go-isatty v0.0.20 // indirect
140141
github.com/mattn/go-runewidth v0.0.14 // indirect

go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLe
334334
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
335335
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
336336
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
337+
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
337338
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
338339
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
339340
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
@@ -457,6 +458,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
457458
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
458459
github.com/lib/pq v1.10.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ=
459460
github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
461+
github.com/libgit2/git2go/v34 v34.0.0 h1:UKoUaKLmiCRbOCD3PtUi2hD6hESSXzME/9OUZrGcgu8=
462+
github.com/libgit2/git2go/v34 v34.0.0/go.mod h1:blVco2jDAw6YTXkErMMqzHLcAjKkwF0aWIRHBqiJkZ0=
460463
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
461464
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
462465
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
@@ -648,6 +651,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
648651
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
649652
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
650653
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
654+
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
651655
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
652656
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
653657
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
@@ -815,6 +819,7 @@ golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7w
815819
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
816820
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
817821
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
822+
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
818823
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
819824
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
820825
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -853,6 +858,7 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
853858
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
854859
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
855860
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
861+
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
856862
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
857863
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
858864
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=

internal/git/clone.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package git
2+
3+
import (
4+
"os"
5+
"time"
6+
7+
git2go "github.com/libgit2/git2go/v34"
8+
9+
"github.com/rs/zerolog/log"
10+
)
11+
12+
func (c *Connection) Clone() error {
13+
if c.Options.Directory == "" {
14+
directoryName, err := os.MkdirTemp(os.TempDir(), "gitops-repo-")
15+
if err != nil {
16+
log.Error().Err(err).Msg("Error creating temporary directory for cloning")
17+
return err
18+
}
19+
c.Options.Directory = directoryName
20+
} else {
21+
err := os.MkdirAll(c.Options.Directory, 0755)
22+
if err != nil {
23+
log.Error().Err(err).Msg("Error creating directory for cloning")
24+
return err
25+
}
26+
}
27+
28+
startTime := time.Now()
29+
30+
repo, err := git2go.Clone(c.Options.Repository, c.Options.Directory, &git2go.CloneOptions{
31+
CheckoutBranch: c.Options.Branch,
32+
CheckoutOptions: git2go.CheckoutOptions{},
33+
FetchOptions: c.getFetchOptions(FetchOptionUsageDefault),
34+
})
35+
if err != nil {
36+
log.Error().Err(err).Msgf("Error cloning repository %s on branch %s", c.Options.Repository, c.Options.Branch)
37+
return err
38+
}
39+
40+
log.Debug().Msgf("Cloned repository %s on branch %s in %d", c.Options.Repository, c.Options.Branch, time.Since(startTime))
41+
c.Repository = repo
42+
43+
err = repo.CheckoutHead(&git2go.CheckoutOptions{
44+
Strategy: git2go.CheckoutSafe,
45+
})
46+
if err != nil {
47+
log.Error().Err(err).Msg("Error checking out HEAD")
48+
return err
49+
}
50+
return nil
51+
}

internal/git/commit.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package git
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
git2go "github.com/libgit2/git2go/v34"
8+
9+
"github.com/rs/zerolog/log"
10+
)
11+
12+
func (c *Connection) Commit(files []string, message string) (string, error) {
13+
if c.Repository == nil {
14+
return "", fmt.Errorf("repository is not initialized")
15+
}
16+
17+
// Get the index for staging changes
18+
index, err := c.Repository.Index()
19+
if err != nil {
20+
log.Error().Err(err).Msg("Error accessing repository index")
21+
return "", fmt.Errorf("error accessing repository index: %w", err)
22+
}
23+
24+
// Add specified files to the index
25+
for _, file := range files {
26+
err = index.AddByPath(file)
27+
if err != nil {
28+
log.Error().Err(err).Str("file", file).Msg("Error adding file to index")
29+
return "", fmt.Errorf("error adding file to index: %w", err)
30+
}
31+
}
32+
33+
// Write the index to the repository's staging area
34+
err = index.Write()
35+
if err != nil {
36+
log.Error().Err(err).Msg("Error writing index to repository")
37+
return "", fmt.Errorf("error writing index to repository: %w", err)
38+
}
39+
40+
treeID, err := index.WriteTree()
41+
if err != nil {
42+
log.Error().Err(err).Msg("Error writing tree from index")
43+
return "", fmt.Errorf("error writing tree from index: %w", err)
44+
}
45+
46+
// Lookup the tree object
47+
tree, err := c.Repository.LookupTree(treeID)
48+
if err != nil {
49+
log.Error().Err(err).Msg("Error looking up tree object")
50+
return "", fmt.Errorf("error looking up tree object: %w", err)
51+
}
52+
53+
// Get the HEAD commit for parent
54+
headRef, err := c.Repository.Head()
55+
if err != nil {
56+
log.Error().Err(err).Msg("Error retrieving HEAD reference")
57+
return "", fmt.Errorf("error retrieving HEAD reference: %w", err)
58+
}
59+
60+
var parentCommit *git2go.Commit
61+
if headRef != nil && headRef.Target() != nil {
62+
parentCommit, err = c.Repository.LookupCommit(headRef.Target())
63+
if err != nil {
64+
log.Error().Err(err).Msg("Error looking up HEAD commit")
65+
return "", fmt.Errorf("error looking up HEAD commit: %w", err)
66+
}
67+
}
68+
69+
sig := &git2go.Signature{
70+
Name: c.Options.Signature.Name,
71+
Email: c.Options.Signature.Email,
72+
When: time.Now(),
73+
}
74+
75+
var commitID *git2go.Oid
76+
if parentCommit != nil {
77+
commitID, err = c.Repository.CreateCommit(
78+
"HEAD", sig, sig, message, tree, parentCommit,
79+
)
80+
} else {
81+
commitID, err = c.Repository.CreateCommit(
82+
"HEAD", sig, sig, message, tree,
83+
)
84+
}
85+
86+
if err != nil {
87+
log.Error().Err(err).Msg("Error creating commit")
88+
return "", fmt.Errorf("error creating commit: %w", err)
89+
}
90+
91+
log.Debug().Str("commit", commitID.String()).Msg("Commit created successfully")
92+
return commitID.String(), nil
93+
}

internal/git/git.go

Lines changed: 92 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,52 +2,103 @@ package git
22

33
import (
44
"errors"
5+
"os"
56
"strings"
67

7-
"github.com/go-git/go-git/v5"
8-
"github.com/go-git/go-git/v5/plumbing/transport"
9-
"github.com/go-git/go-git/v5/plumbing/transport/http"
10-
gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
8+
git2go "github.com/libgit2/git2go/v34"
9+
1110
"golang.org/x/crypto/ssh"
1211
)
1312

1413
type ConnectionOptions struct {
15-
Repository string
16-
Branch string
17-
Auth transport.AuthMethod
14+
Directory string
15+
Repository string
16+
Branch string
17+
Authentication *Authentication
18+
IgnoreSslHostKey bool
19+
PullRebase bool
20+
Signature *Signature
21+
}
22+
23+
type Authentication struct {
24+
BasicAuth *BasicAuth
25+
SshKey *SshKey
26+
}
27+
28+
type BasicAuth struct {
29+
Username string
30+
Password string
31+
}
32+
33+
type SshKey struct {
34+
PrivateKey []byte
35+
Passphrase *string
36+
Signer *ssh.Signer
37+
}
38+
39+
type Signature struct {
40+
Name string
41+
Email string
1842
}
1943

2044
type Connection struct {
21-
Repository *git.Repository
45+
Repository *git2go.Repository
2246
Options *ConnectionOptions
2347
}
2448

2549
func NewGitConnection(options *ConnectionOptions) (*Connection, error) {
26-
return &Connection{
50+
connection := &Connection{
2751
Options: options,
28-
}, nil
52+
}
53+
54+
if options.Directory != "" {
55+
stat, err := os.Stat(options.Directory)
56+
if err == nil {
57+
if stat.IsDir() {
58+
repository, err := git2go.OpenRepository(options.Directory)
59+
if err != nil {
60+
return nil, err
61+
}
62+
connection.Repository = repository
63+
}
64+
}
65+
}
66+
67+
if options.Signature == nil {
68+
connection.Options.Signature = &Signature{
69+
Name: "GitOps CI User",
70+
Email: "gitops@example.com",
71+
}
72+
} else {
73+
connection.Options.Signature = options.Signature
74+
}
75+
76+
return connection, nil
2977
}
3078

31-
func GetAuthFromUsernamePassword(username string, password string) (transport.AuthMethod, error) {
32-
return &http.BasicAuth{
33-
Username: username,
34-
Password: password,
79+
func GetAuthFromUsernamePassword(username string, password string) (*Authentication, error) {
80+
return &Authentication{
81+
BasicAuth: &BasicAuth{
82+
Username: username,
83+
Password: password,
84+
},
3585
}, nil
3686
}
3787

38-
func GetAuthFromBasicAuthString(basicAuth string) (transport.AuthMethod, error) {
88+
func GetAuthFromBasicAuthString(basicAuth string) (*Authentication, error) {
3989
split := strings.Split(basicAuth, ":")
4090
if len(split) != 2 {
4191
return nil, errors.New("invalid basic auth string")
4292
}
43-
return &http.BasicAuth{
44-
Username: split[0],
45-
Password: split[1],
93+
return &Authentication{
94+
BasicAuth: &BasicAuth{
95+
Username: split[0],
96+
Password: split[1],
97+
},
4698
}, nil
4799
}
48100

49-
func GetAuthFromSshKey(sshKey []byte, sshKeyPassphrase *string, noStrictHostKeyChecking bool) (transport.AuthMethod, error) {
50-
101+
func GetAuthFromSshKey(sshKey []byte, sshKeyPassphrase *string) (*Authentication, error) {
51102
var signer ssh.Signer
52103
if sshKeyPassphrase != nil {
53104
_signer, err := ssh.ParsePrivateKeyWithPassphrase(sshKey, []byte(*sshKeyPassphrase))
@@ -63,11 +114,28 @@ func GetAuthFromSshKey(sshKey []byte, sshKeyPassphrase *string, noStrictHostKeyC
63114
signer = _signer
64115
}
65116

66-
auth := &gitssh.PublicKeys{User: "git", Signer: signer}
117+
return &Authentication{
118+
SshKey: &SshKey{
119+
PrivateKey: sshKey,
120+
Passphrase: sshKeyPassphrase,
121+
Signer: &signer,
122+
},
123+
}, nil
124+
}
125+
126+
func (c *Connection) credentialsCallback(url string, usernameFromURL string, allowedTypes git2go.CredentialType) (*git2go.Cred, error) {
127+
128+
if c.Options.Authentication == nil {
129+
return git2go.NewCredentialDefault()
130+
}
131+
132+
if c.Options.Authentication.BasicAuth != nil {
133+
return git2go.NewCredentialUserpassPlaintext(c.Options.Authentication.BasicAuth.Username, c.Options.Authentication.BasicAuth.Password)
134+
}
67135

68-
if noStrictHostKeyChecking {
69-
auth.HostKeyCallback = ssh.InsecureIgnoreHostKey()
136+
if c.Options.Authentication.SshKey != nil {
137+
return git2go.NewCredentialSSHKeyFromSigner(usernameFromURL, *c.Options.Authentication.SshKey.Signer)
70138
}
71139

72-
return auth, nil
140+
return git2go.NewCredentialDefault()
73141
}

0 commit comments

Comments
 (0)