summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml5
-rw-r--r--.travis.yml1
-rw-r--r--credentials.go62
-rw-r--r--go.mod6
-rw-r--r--go.sum12
-rw-r--r--remote_test.go276
-rwxr-xr-xscript/build-libgit2.sh12
-rw-r--r--testdata/TestGitRepository.git/HEAD1
-rw-r--r--testdata/TestGitRepository.git/config6
-rw-r--r--testdata/TestGitRepository.git/description1
-rw-r--r--testdata/TestGitRepository.git/info/exclude6
-rw-r--r--testdata/TestGitRepository.git/info/refs8
-rw-r--r--testdata/TestGitRepository.git/objects/info/commit-graphbin0 -> 2296 bytes
-rw-r--r--testdata/TestGitRepository.git/objects/info/packs2
-rw-r--r--testdata/TestGitRepository.git/objects/pack/pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.bitmapbin0 -> 1334 bytes
-rw-r--r--testdata/TestGitRepository.git/objects/pack/pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.idxbin0 -> 3032 bytes
-rw-r--r--testdata/TestGitRepository.git/objects/pack/pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.packbin0 -> 6072 bytes
-rw-r--r--testdata/TestGitRepository.git/packed-refs9
-rw-r--r--testdata/TestGitRepository.git/refs/heads/master1
-rw-r--r--wrapper.c23
20 files changed, 421 insertions, 10 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d326170..fc615f2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -34,6 +34,7 @@ jobs:
GOPATH: /home/runner/work/git2go
run: |
git submodule update --init
+ sudo apt-get install -y --no-install-recommends libssh2-1-dev
make build-libgit2-static
go get -tags static -t github.com/${{ github.repository }}/...
go build -tags static github.com/${{ github.repository }}/...
@@ -62,6 +63,7 @@ jobs:
- name: Build
run: |
git submodule update --init
+ sudo apt-get install -y --no-install-recommends libssh2-1-dev
make build-libgit2-static
- name: Test
run: make TEST_ARGS=-test.v test-static
@@ -84,6 +86,7 @@ jobs:
- name: Build
run: |
git submodule update --init
+ sudo apt-get install -y --no-install-recommends libssh2-1-dev
make build-libgit2-dynamic
- name: Test
run: make TEST_ARGS=-test.v test-dynamic
@@ -108,6 +111,7 @@ jobs:
- name: Build libgit2 ${{ matrix.libgit2 }}
run: |
git submodule update --init
+ sudo apt-get install -y --no-install-recommends libssh2-1-dev
sudo env BUILD_LIBGIT_REF=v${{ matrix.libgit2 }} ./script/build-libgit2.sh --dynamic --system
- name: Test
run: make TEST_ARGS=-test.v test
@@ -130,6 +134,7 @@ jobs:
- name: Build libgit2
run: |
git submodule update --init
+ sudo apt-get install -y --no-install-recommends libssh2-1-dev
sudo ./script/build-libgit2.sh --static --system
- name: Test
run: go test --count=1 --tags "static,system_libgit2" ./...
diff --git a/.travis.yml b/.travis.yml
index 1717c8e..0b7f482 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,6 +8,7 @@ go:
- tip
install:
+ - sudo apt-get install -y --no-install-recommends libssh2-1-dev
- make build-libgit2-static
- go get --tags "static" ./...
diff --git a/credentials.go b/credentials.go
index b1051b9..deb399a 100644
--- a/credentials.go
+++ b/credentials.go
@@ -3,15 +3,20 @@ package git
/*
#include <git2.h>
#include <git2/credential.h>
+#include <git2/sys/credential.h>
git_credential_t _go_git_credential_credtype(git_credential *cred);
+void _go_git_populate_credential_ssh_custom(git_credential_ssh_custom *cred);
*/
import "C"
import (
+ "crypto/rand"
"fmt"
"runtime"
"strings"
"unsafe"
+
+ "golang.org/x/crypto/ssh"
)
// CredentialType is a bitmask of supported credential types.
@@ -192,6 +197,63 @@ func NewCredentialSSHKeyFromAgent(username string) (*Credential, error) {
return cred, nil
}
+type credentialSSHCustomData struct {
+ signer ssh.Signer
+}
+
+//export credentialSSHCustomFree
+func credentialSSHCustomFree(cred *C.git_credential_ssh_custom) {
+ if cred == nil {
+ return
+ }
+
+ C.free(unsafe.Pointer(cred.username))
+ C.free(unsafe.Pointer(cred.publickey))
+ pointerHandles.Untrack(cred.payload)
+ C.free(unsafe.Pointer(cred))
+}
+
+//export credentialSSHSignCallback
+func credentialSSHSignCallback(
+ errorMessage **C.char,
+ sig **C.uchar,
+ sig_len *C.size_t,
+ data *C.uchar,
+ data_len C.size_t,
+ handle unsafe.Pointer,
+) C.int {
+ signer := pointerHandles.Get(handle).(*credentialSSHCustomData).signer
+ signature, err := signer.Sign(rand.Reader, C.GoBytes(unsafe.Pointer(data), C.int(data_len)))
+ if err != nil {
+ return setCallbackError(errorMessage, err)
+ }
+ *sig = (*C.uchar)(C.CBytes(signature.Blob))
+ *sig_len = C.size_t(len(signature.Blob))
+ return C.int(ErrorCodeOK)
+}
+
+// NewCredentialSSHKeyFromSigner creates new SSH credentials using the provided signer.
+func NewCredentialSSHKeyFromSigner(username string, signer ssh.Signer) (*Credential, error) {
+ publicKey := signer.PublicKey().Marshal()
+
+ ccred := (*C.git_credential_ssh_custom)(C.calloc(1, C.size_t(unsafe.Sizeof(C.git_credential_ssh_custom{}))))
+ ccred.parent.credtype = C.GIT_CREDENTIAL_SSH_CUSTOM
+ ccred.username = C.CString(username)
+ ccred.publickey = (*C.char)(C.CBytes(publicKey))
+ ccred.publickey_len = C.size_t(len(publicKey))
+ C._go_git_populate_credential_ssh_custom(ccred)
+
+ data := credentialSSHCustomData{
+ signer: signer,
+ }
+ ccred.payload = pointerHandles.Track(&data)
+
+ cred := newCredential()
+ cred.ptr = &ccred.parent
+
+ return cred, nil
+}
+
func NewCredentialDefault() (*Credential, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
diff --git a/go.mod b/go.mod
index 42cdb9d..df3b1fb 100644
--- a/go.mod
+++ b/go.mod
@@ -2,4 +2,8 @@ module github.com/libgit2/git2go/v31
go 1.13
-require golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
+require (
+ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
+ golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c
+ golang.org/x/sys v0.0.0-20201204225414-ed752295db88 // indirect
+)
diff --git a/go.sum b/go.sum
index 1769e6b..35ff116 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,13 @@
+github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
+github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
-golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c h1:9HhBz5L/UjnK9XLtiZhYAdue5BVKep3PMmS2LuPDt8k=
+golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201204225414-ed752295db88 h1:KmZPnMocC93w341XZp26yTJg8Za7lhb2KhkYmixoeso=
+golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM=
+golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/remote_test.go b/remote_test.go
index 4cc3298..b97d764 100644
--- a/remote_test.go
+++ b/remote_test.go
@@ -1,8 +1,21 @@
package git
import (
+ "bytes"
+ "crypto/rand"
+ "crypto/rsa"
"fmt"
+ "io"
+ "net"
+ "os"
+ "os/exec"
+ "strings"
+ "sync"
"testing"
+ "time"
+
+ "github.com/google/shlex"
+ "golang.org/x/crypto/ssh"
)
func TestListRemotes(t *testing.T) {
@@ -184,3 +197,266 @@ func TestRemotePrune(t *testing.T) {
t.Fatal("Expected error getting a pruned reference")
}
}
+
+func newChannelPipe(t *testing.T, w io.Writer, wg *sync.WaitGroup) (*os.File, error) {
+ pr, pw, err := os.Pipe()
+ if err != nil {
+ return nil, err
+ }
+
+ wg.Add(1)
+ go func() {
+ _, err := io.Copy(w, pr)
+ if err != nil && err != io.EOF {
+ t.Logf("Failed to copy: %v", err)
+ }
+ wg.Done()
+ }()
+
+ return pw, nil
+}
+
+func startSSHServer(t *testing.T, hostKey ssh.Signer, authorizedKeys []ssh.PublicKey) net.Listener {
+ t.Helper()
+
+ marshaledAuthorizedKeys := make([][]byte, len(authorizedKeys))
+ for i, authorizedKey := range authorizedKeys {
+ marshaledAuthorizedKeys[i] = authorizedKey.Marshal()
+ }
+
+ config := &ssh.ServerConfig{
+ PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
+ marshaledPubKey := pubKey.Marshal()
+ for _, marshaledAuthorizedKey := range marshaledAuthorizedKeys {
+ if bytes.Equal(marshaledPubKey, marshaledAuthorizedKey) {
+ return &ssh.Permissions{
+ // Record the public key used for authentication.
+ Extensions: map[string]string{
+ "pubkey-fp": ssh.FingerprintSHA256(pubKey),
+ },
+ }, nil
+ }
+ }
+ t.Logf("unknown public key for %q:\n\t%+v\n\t%+v\n", c.User(), pubKey.Marshal(), authorizedKeys)
+ return nil, fmt.Errorf("unknown public key for %q", c.User())
+ },
+ }
+ config.AddHostKey(hostKey)
+
+ listener, err := net.Listen("tcp", "localhost:0")
+ if err != nil {
+ t.Fatalf("Failed to listen for connection: %v", err)
+ }
+
+ go func() {
+ nConn, err := listener.Accept()
+ if err != nil {
+ if strings.Contains(err.Error(), "use of closed network connection") {
+ return
+ }
+ t.Logf("Failed to accept incoming connection: %v", err)
+ return
+ }
+ defer nConn.Close()
+
+ conn, chans, reqs, err := ssh.NewServerConn(nConn, config)
+ if err != nil {
+ t.Logf("failed to handshake: %+v, %+v", conn, err)
+ return
+ }
+
+ // The incoming Request channel must be serviced.
+ go func() {
+ for newRequest := range reqs {
+ t.Logf("new request %v", newRequest)
+ }
+ }()
+
+ // Service only the first channel request
+ newChannel := <-chans
+ defer func() {
+ for newChannel := range chans {
+ t.Logf("new channel %v", newChannel)
+ newChannel.Reject(ssh.UnknownChannelType, "server closing")
+ }
+ }()
+
+ // Channels have a type, depending on the application level
+ // protocol intended. In the case of a shell, the type is
+ // "session" and ServerShell may be used to present a simple
+ // terminal interface.
+ if newChannel.ChannelType() != "session" {
+ newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
+ return
+ }
+ channel, requests, err := newChannel.Accept()
+ if err != nil {
+ t.Logf("Could not accept channel: %v", err)
+ return
+ }
+ defer channel.Close()
+
+ // Sessions have out-of-band requests such as "shell",
+ // "pty-req" and "env". Here we handle only the
+ // "exec" request.
+ req := <-requests
+ if req.Type != "exec" {
+ req.Reply(false, nil)
+ return
+ }
+ // RFC 4254 Section 6.5.
+ var payload struct {
+ Command string
+ }
+ if err := ssh.Unmarshal(req.Payload, &payload); err != nil {
+ t.Logf("invalid payload on channel %v: %v", channel, err)
+ req.Reply(false, nil)
+ return
+ }
+ args, err := shlex.Split(payload.Command)
+ if err != nil {
+ t.Logf("invalid command on channel %v: %v", channel, err)
+ req.Reply(false, nil)
+ return
+ }
+ if len(args) < 2 || (args[0] != "git-upload-pack" && args[0] != "git-receive-pack") {
+ t.Logf("invalid command (%v) on channel %v: %v", args, channel, err)
+ req.Reply(false, nil)
+ return
+ }
+ req.Reply(true, nil)
+
+ go func(in <-chan *ssh.Request) {
+ for req := range in {
+ t.Logf("draining request %v", req)
+ }
+ }(requests)
+
+ // The first parameter is the (absolute) path of the repository.
+ args[1] = "./testdata" + args[1]
+
+ cmd := exec.Command(args[0], args[1:]...)
+ cmd.Stdin = channel
+ var wg sync.WaitGroup
+ stdoutPipe, err := newChannelPipe(t, channel, &wg)
+ if err != nil {
+ t.Logf("Failed to create stdout pipe: %v", err)
+ return
+ }
+ cmd.Stdout = stdoutPipe
+ stderrPipe, err := newChannelPipe(t, channel.Stderr(), &wg)
+ if err != nil {
+ t.Logf("Failed to create stderr pipe: %v", err)
+ return
+ }
+ cmd.Stderr = stderrPipe
+
+ go func() {
+ wg.Wait()
+ channel.CloseWrite()
+ }()
+
+ err = cmd.Start()
+ if err != nil {
+ t.Logf("Failed to start %v: %v", args, err)
+ return
+ }
+
+ // Once the process has started, we need to close the write end of the
+ // pipes from this process so that we can know when the child has done
+ // writing to it.
+ stdoutPipe.Close()
+ stderrPipe.Close()
+
+ timer := time.AfterFunc(5*time.Second, func() {
+ t.Log("process timed out, terminating")
+ cmd.Process.Kill()
+ })
+ defer timer.Stop()
+
+ err = cmd.Wait()
+ if err != nil {
+ t.Logf("Failed to run %v: %v", args, err)
+ return
+ }
+ }()
+ return listener
+}
+
+func TestRemoteSSH(t *testing.T) {
+ t.Parallel()
+ pubKeyUsername := "testuser"
+
+ hostPrivKey, err := rsa.GenerateKey(rand.Reader, 512)
+ if err != nil {
+ t.Fatalf("Failed to generate the host RSA private key: %v", err)
+ }
+ hostSigner, err := ssh.NewSignerFromKey(hostPrivKey)
+ if err != nil {
+ t.Fatalf("Failed to generate SSH hostSigner: %v", err)
+ }
+
+ privKey, err := rsa.GenerateKey(rand.Reader, 512)
+ if err != nil {
+ t.Fatalf("Failed to generate the user RSA private key: %v", err)
+ }
+ signer, err := ssh.NewSignerFromKey(privKey)
+ if err != nil {
+ t.Fatalf("Failed to generate SSH signer: %v", err)
+ }
+ // This is in the format "xx:xx:xx:...", so we remove the colons so that it
+ // matches the fmt.Sprintf() below.
+ // Note that not all libssh2 implementations support the SHA256 fingerprint,
+ // so we use MD5 here for testing.
+ publicKeyFingerprint := strings.Replace(ssh.FingerprintLegacyMD5(hostSigner.PublicKey()), ":", "", -1)
+
+ listener := startSSHServer(t, hostSigner, []ssh.PublicKey{signer.PublicKey()})
+ defer listener.Close()
+
+ repo := createTestRepo(t)
+ defer cleanupTestRepo(t, repo)
+
+ certificateCheckCallbackCalled := false
+ fetchOpts := FetchOptions{
+ RemoteCallbacks: RemoteCallbacks{
+ CertificateCheckCallback: func(cert *Certificate, valid bool, hostname string) ErrorCode {
+ hostkeyFingerprint := fmt.Sprintf("%x", cert.Hostkey.HashMD5[:])
+ if hostkeyFingerprint != publicKeyFingerprint {
+ t.Logf("server hostkey %q, want %q", hostkeyFingerprint, publicKeyFingerprint)
+ return ErrorCodeAuth
+ }
+ certificateCheckCallbackCalled = true
+ return ErrorCodeOK
+ },
+ CredentialsCallback: func(url, username string, allowedTypes CredentialType) (*Credential, error) {
+ if allowedTypes&(CredentialTypeSSHKey|CredentialTypeSSHCustom|CredentialTypeSSHMemory) != 0 {
+ return NewCredentialSSHKeyFromSigner(pubKeyUsername, signer)
+ }
+ if (allowedTypes & CredentialTypeUsername) != 0 {
+ return NewCredentialUsername(pubKeyUsername)
+ }
+ return nil, fmt.Errorf("unknown credential type %+v", allowedTypes)
+ },
+ },
+ }
+
+ remote, err := repo.Remotes.Create(
+ "origin",
+ fmt.Sprintf("ssh://%s/TestGitRepository", listener.Addr().String()),
+ )
+ checkFatal(t, err)
+ defer remote.Free()
+
+ err = remote.Fetch(nil, &fetchOpts, "")
+ checkFatal(t, err)
+ if !certificateCheckCallbackCalled {
+ t.Fatalf("CertificateCheckCallback was not called")
+ }
+
+ heads, err := remote.Ls()
+ checkFatal(t, err)
+
+ if len(heads) == 0 {
+ t.Error("Expected remote heads")
+ }
+}
diff --git a/script/build-libgit2.sh b/script/build-libgit2.sh
index e9c6c9c..a9ff2a9 100755
--- a/script/build-libgit2.sh
+++ b/script/build-libgit2.sh
@@ -69,11 +69,11 @@ cmake -DTHREADSAFE=ON \
-DCMAKE_INSTALL_PREFIX="${BUILD_INSTALL_PREFIX}" \
-DCMAKE_INSTALL_LIBDIR="lib" \
-DDEPRECATE_HARD=ON \
- "${VENDORED_PATH}" &&
+ "${VENDORED_PATH}"
-if which gmake nproc >/dev/null && [ -f Makefile ]; then
- # Make the build parallel if gmake is available and cmake used Makefiles.
- exec gmake "-j$(nproc --all)" install
+if which make nproc >/dev/null && [ -f Makefile ]; then
+ # Make the build parallel if make is available and cmake used Makefiles.
+ exec make "-j$(nproc --all)" install
+else
+ exec cmake --build . --target install
fi
-
-exec cmake --build . --target install
diff --git a/testdata/TestGitRepository.git/HEAD b/testdata/TestGitRepository.git/HEAD
new file mode 100644
index 0000000..cb089cd
--- /dev/null
+++ b/testdata/TestGitRepository.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/testdata/TestGitRepository.git/config b/testdata/TestGitRepository.git/config
new file mode 100644
index 0000000..44b9489
--- /dev/null
+++ b/testdata/TestGitRepository.git/config
@@ -0,0 +1,6 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = true
+[remote "origin"]
+ url = https://github.com/libgit2/TestGitRepository
diff --git a/testdata/TestGitRepository.git/description b/testdata/TestGitRepository.git/description
new file mode 100644
index 0000000..498b267
--- /dev/null
+++ b/testdata/TestGitRepository.git/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/testdata/TestGitRepository.git/info/exclude b/testdata/TestGitRepository.git/info/exclude
new file mode 100644
index 0000000..a5196d1
--- /dev/null
+++ b/testdata/TestGitRepository.git/info/exclude
@@ -0,0 +1,6 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
diff --git a/testdata/TestGitRepository.git/info/refs b/testdata/TestGitRepository.git/info/refs
new file mode 100644
index 0000000..e10a563
--- /dev/null
+++ b/testdata/TestGitRepository.git/info/refs
@@ -0,0 +1,8 @@
+0966a434eb1a025db6b71485ab63a3bfbea520b6 refs/heads/first-merge
+49322bb17d3acc9146f98c97d078513228bbf3c0 refs/heads/master
+42e4e7c5e507e113ebbb7801b16b52cf867b7ce1 refs/heads/no-parent
+d96c4e80345534eccee5ac7b07fc7603b56124cb refs/tags/annotated_tag
+c070ad8c08840c8116da865b2d65593a6bb9cd2a refs/tags/annotated_tag^{}
+55a1a760df4b86a02094a904dfa511deb5655905 refs/tags/blob
+8f50ba15d49353813cc6e20298002c0d17b0a9ee refs/tags/commit_tree
+6e0c7bdb9b4ed93212491ee778ca1c65047cab4e refs/tags/nearly-dangling
diff --git a/testdata/TestGitRepository.git/objects/info/commit-graph b/testdata/TestGitRepository.git/objects/info/commit-graph
new file mode 100644
index 0000000..013e2f0
--- /dev/null
+++ b/testdata/TestGitRepository.git/objects/info/commit-graph
Binary files differ
diff --git a/testdata/TestGitRepository.git/objects/info/packs b/testdata/TestGitRepository.git/objects/info/packs
new file mode 100644
index 0000000..d876b38
--- /dev/null
+++ b/testdata/TestGitRepository.git/objects/info/packs
@@ -0,0 +1,2 @@
+P pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.pack
+
diff --git a/testdata/TestGitRepository.git/objects/pack/pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.bitmap b/testdata/TestGitRepository.git/objects/pack/pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.bitmap
new file mode 100644
index 0000000..df3442c
--- /dev/null
+++ b/testdata/TestGitRepository.git/objects/pack/pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.bitmap
Binary files differ
diff --git a/testdata/TestGitRepository.git/objects/pack/pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.idx b/testdata/TestGitRepository.git/objects/pack/pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.idx
new file mode 100644
index 0000000..aff5c2e
--- /dev/null
+++ b/testdata/TestGitRepository.git/objects/pack/pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.idx
Binary files differ
diff --git a/testdata/TestGitRepository.git/objects/pack/pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.pack b/testdata/TestGitRepository.git/objects/pack/pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.pack
new file mode 100644
index 0000000..a3a1206
--- /dev/null
+++ b/testdata/TestGitRepository.git/objects/pack/pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.pack
Binary files differ
diff --git a/testdata/TestGitRepository.git/packed-refs b/testdata/TestGitRepository.git/packed-refs
new file mode 100644
index 0000000..ead149c
--- /dev/null
+++ b/testdata/TestGitRepository.git/packed-refs
@@ -0,0 +1,9 @@
+# pack-refs with: peeled fully-peeled sorted
+0966a434eb1a025db6b71485ab63a3bfbea520b6 refs/heads/first-merge
+49322bb17d3acc9146f98c97d078513228bbf3c0 refs/heads/master
+42e4e7c5e507e113ebbb7801b16b52cf867b7ce1 refs/heads/no-parent
+d96c4e80345534eccee5ac7b07fc7603b56124cb refs/tags/annotated_tag
+^c070ad8c08840c8116da865b2d65593a6bb9cd2a
+55a1a760df4b86a02094a904dfa511deb5655905 refs/tags/blob
+8f50ba15d49353813cc6e20298002c0d17b0a9ee refs/tags/commit_tree
+6e0c7bdb9b4ed93212491ee778ca1c65047cab4e refs/tags/nearly-dangling
diff --git a/testdata/TestGitRepository.git/refs/heads/master b/testdata/TestGitRepository.git/refs/heads/master
new file mode 100644
index 0000000..88fc4ea
--- /dev/null
+++ b/testdata/TestGitRepository.git/refs/heads/master
@@ -0,0 +1 @@
+49322bb17d3acc9146f98c97d078513228bbf3c0
diff --git a/wrapper.c b/wrapper.c
index cee8d00..f168425 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -437,6 +437,29 @@ git_credential_t _go_git_credential_credtype(git_credential *cred)
return cred->credtype;
}
+static int credential_ssh_sign_callback(
+ LIBSSH2_SESSION *session,
+ unsigned char **sig, size_t *sig_len,
+ const unsigned char *data, size_t data_len,
+ void **abstract)
+{
+ char *error_message = NULL;
+ const int ret = credentialSSHSignCallback(
+ &error_message,
+ sig,
+ sig_len,
+ (unsigned char *)data,
+ data_len,
+ (void *)*(uintptr_t *)abstract);
+ return set_callback_error(error_message, ret);
+}
+
+void _go_git_populate_credential_ssh_custom(git_credential_ssh_custom *cred)
+{
+ cred->parent.free = (void (*)(git_credential *))credentialSSHCustomFree;
+ cred->sign_callback = credential_ssh_sign_callback;
+}
+
int _go_git_odb_write_pack(git_odb_writepack **out, git_odb *db, void *progress_payload)
{
return git_odb_write_pack(out, db, transfer_progress_callback, progress_payload);