Repository: libgit2/git2go
Pull request #856 (2021-11-07 20:21:16)

Make ssh commands used in the git smart transport compatible with libgit2 (#852)
* Fix ssh commands used in go SmartSubtransport

Before the fix, the commands sent were of the form:

```
git-upload-pack "/bar/test-reponame"
```

This resulted in the git server returning error:
`error parsing command: invalid git command`

This change replaces the double quotes with single quotes:

```
git-upload-pack '/bar/test-reponame'
```

* Update ssh.go

Co-authored-by: lhchavez <lhchavez@lhchavez.com>
(cherry picked from commit 6cea7a7a59f44e0e72ca577fbea65a042b3fb26b)


Patch:
diff --git a/remote.go b/remote.go
index 7abc461..f4424fc 100644
--- a/remote.go
+++ b/remote.go
@@ -182,6 +182,9 @@ type Remote struct {
 	ptr       *C.git_remote
 	callbacks RemoteCallbacks
 	repo      *Repository
+	// weak indicates that a remote is a weak pointer and should not be
+	// freed.
+	weak bool
 }
 
 type remotePointerList struct {
@@ -602,6 +605,9 @@ func (r *Remote) free() {
 // Free releases the resources of the Remote.
 func (r *Remote) Free() {
 	r.repo.Remotes.untrackRemote(r)
+	if r.weak {
+		return
+	}
 	r.free()
 }
 
@@ -1231,3 +1237,12 @@ func freeRemoteCreateOptions(ptr *C.git_remote_create_options) {
 	C.free(unsafe.Pointer(ptr.name))
 	C.free(unsafe.Pointer(ptr.fetchspec))
 }
+
+// createNewEmptyRemote used to get a new empty object of *Remote
+func createNewEmptyRemote() *Remote {
+	return &Remote{
+		callbacks: RemoteCallbacks{},
+		repo:      nil,
+		weak:      false,
+	}
+}
diff --git a/ssh.go b/ssh.go
index dd2725e..0581ca0 100644
--- a/ssh.go
+++ b/ssh.go
@@ -17,6 +17,7 @@ import (
 	"net"
 	"net/url"
 	"runtime"
+	"strings"
 	"unsafe"
 
 	"golang.org/x/crypto/ssh"
@@ -74,6 +75,13 @@ func (t *sshSmartSubtransport) Action(urlString string, action SmartServiceActio
 		return nil, err
 	}
 
+	// Escape \ and '.
+	uPath := strings.Replace(u.Path, `\`, `\\`, -1)
+	uPath = strings.Replace(uPath, `'`, `\'`, -1)
+
+	// TODO: Add percentage decode similar to libgit2.
+	// Refer: https://github.com/libgit2/libgit2/blob/358a60e1b46000ea99ef10b4dd709e92f75ff74b/src/str.c#L455-L481
+
 	var cmd string
 	switch action {
 	case SmartServiceActionUploadpackLs, SmartServiceActionUploadpack:
@@ -83,7 +91,7 @@ func (t *sshSmartSubtransport) Action(urlString string, action SmartServiceActio
 			}
 			t.Close()
 		}
-		cmd = fmt.Sprintf("git-upload-pack %q", u.Path)
+		cmd = fmt.Sprintf("git-upload-pack '%s'", uPath)
 
 	case SmartServiceActionReceivepackLs, SmartServiceActionReceivepack:
 		if t.currentStream != nil {
@@ -92,7 +100,7 @@ func (t *sshSmartSubtransport) Action(urlString string, action SmartServiceActio
 			}
 			t.Close()
 		}
-		cmd = fmt.Sprintf("git-receive-pack %q", u.Path)
+		cmd = fmt.Sprintf("git-receive-pack '%s'", uPath)
 
 	default:
 		return nil, fmt.Errorf("unexpected action: %v", action)
diff --git a/transport.go b/transport.go
index 1afa6f4..23514b4 100644
--- a/transport.go
+++ b/transport.go
@@ -22,7 +22,6 @@ void _go_git_setup_smart_subtransport_stream(_go_managed_smart_subtransport_stre
 */
 import "C"
 import (
-	"errors"
 	"fmt"
 	"io"
 	"reflect"
@@ -306,8 +305,10 @@ func smartTransportCallback(
 	registeredSmartTransport := pointerHandles.Get(handle).(*RegisteredSmartTransport)
 	remote, ok := remotePointers.get(owner)
 	if !ok {
-		err := errors.New("remote pointer not found")
-		return setCallbackError(errorMessage, err)
+		// create a new empty remote and set it
+		// as a weak pointer, so that control stays in golang
+		remote = createNewEmptyRemote()
+		remote.weak = true
 	}
 
 	managed := &managedSmartSubtransport{


Test patch:
diff --git a/clone_test.go b/clone_test.go
index 8814dd0..9ff03e6 100644
--- a/clone_test.go
+++ b/clone_test.go
@@ -2,6 +2,7 @@ package git
 
 import (
 	"io/ioutil"
+	"os"
 	"testing"
 )
 
@@ -70,3 +71,17 @@ func TestCloneWithCallback(t *testing.T) {
 	}
 	defer remote.Free()
 }
+
+// TestCloneWithExternalHTTPUrl
+func TestCloneWithExternalHTTPUrl(t *testing.T) {
+
+	path, err := ioutil.TempDir("", "git2go")
+	defer os.RemoveAll(path)
+
+	// clone the repo
+	url := "https://github.com/libgit2/TestGitRepository"
+	_, err = Clone(url, path, &CloneOptions{})
+	if err != nil {
+		t.Fatal("cannot clone remote repo via https, error: ", err)
+	}
+}