go-git icon indicating copy to clipboard operation
go-git copied to clipboard

Unable to clone from local repository

Open alethenorio opened this issue 7 years ago • 5 comments

Host: Windows 10 Go Version: 1.10.1 windows/amd64

I am trying a simple flow

  • Init a new repository in a local folder
  • Clone that local repository to another location

However I seem to be unable to do so

Here's a reproducer

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/url"
	"os"

	git "gopkg.in/src-d/go-git.v4"
)

func main() {

	srcPathName := "srcRepo"
	dstPathName := "dstRepo"

	// Create a local folder to store a temporary repository
	srcPath, err := ioutil.TempDir("", srcPathName)
	if err != nil {
		log.Fatalf("Unable to create source folder: %s", err)
	}

	defer os.RemoveAll(srcPath)

	// Init a new repo in the src folder
	_, err = git.PlainInit(srcPath, false)
	if err != nil {
		log.Fatalf("Unable to init source repository: %s\n", err)
	}

	// Create destination clone directory
	dstPath, err := ioutil.TempDir("", dstPathName)
	if err != nil {
		log.Fatalf("Unable to create destination repository: %s\n", err)
	}

	defer os.RemoveAll(dstPath)

	// Get srcPath as a file: url to clone it
	srcURL, _ := url.Parse(srcPath)
	srcURL.Scheme = "file"

	if err != nil {
		log.Fatalf("Unable to parse source repo path: %s\n", err)
	}

	fmt.Printf("Attempting to clone from %s\n", srcURL.String())

	// Attempt to clone local repo
	_, err = git.PlainClone(dstPath, false, &git.CloneOptions{
		URL:               srcURL.String(),
		RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
	})

	if err != nil {
		log.Fatalf("Unable to clone repo: %s", err)
	}
}

When I run this I get

Attempting to clone from file:\Users\USERNA~1\AppData\Local\Temp\dstRepo768183330
2018/05/17 23:33:39 Unable to clone repo: repository not found

I have also tried the following variations of the code to make sure I am not doing something wrong

  • Init as bare
  • Making the url in the format of file:///C:\Users\USERNA~1\AppData\Local\Temp\dstRepo768183330

It is my understanding from looking at the compatibility matrix that the file: protocol is supported.

Am I doing something wrong here, is this a bug or just not supported?

alethenorio avatar May 17 '18 21:05 alethenorio

I've had the same problem, If you dive into the created repo, does git see it as a repository?

jw-s avatar May 18 '18 08:05 jw-s

It seems so. If I jump inside the folder and do a git status

PS C:\Users\Username\AppData\Local\Temp\dstRepo005704362> git status
On branch master

No commits yet

nothing to commit (create/copy files and use "git add" to track)

I am also able to commit to it with git.

I also attempted cloning a repository created with Git and get the same error

I found a small bug in my reproducer code above where I was attempting to clone from destination rather than source, I fixed it but it did not change the outcome

alethenorio avatar May 18 '18 09:05 alethenorio

After some more digging I found the issue

Turns out go-git does not like the file:\some\path format returned by url.Parse when used with the path returned by ioutil.TempDir directly so we need to make the source path into a more friendly format for go-git. This can be done using filepath.ToSlash on the path value returned by ioutil.TempDir

Also go-git complains about not being able to clone an empty repository so we need to commit a new file to it for the use case above to work.

Here follows the full solution

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"path/filepath"
	"time"

	git "gopkg.in/src-d/go-git.v4"
	"gopkg.in/src-d/go-git.v4/plumbing/object"
)

func main() {

	srcPathName := "srcRepo"
	dstPathName := "dstRepo"

	// Create a local folder to store a temporary repository
	srcPath, err := ioutil.TempDir("", srcPathName)
	if err != nil {
		log.Fatalf("Unable to create source folder: %s", err)
	}
	defer os.RemoveAll(srcPath)

	// Windows hosts return a path using \ so we need to make this
	// into a path format go-git can understand with /
	srcPath = filepath.ToSlash(srcPath)

	// Create a dummy file to commit into the dummy repo
	tf, err := ioutil.TempFile(srcPath, "test")
	if err != nil {
		log.Fatalf("Unable to create test file: %s\n", err)
	}

	// Init a new repo in the src folder
	repo, err := git.PlainInit(srcPath, false)
	if err != nil {
		log.Fatalf("Unable to init source repository: %s\n", err)
	}

	wt, err := repo.Worktree()
	if err != nil {
		log.Fatalf("Unable to retrieve source repo working tree: %s\n", err)
	}

	// Stage our test file
	_, err = wt.Add(filepath.Base(tf.Name()))
	if err != nil {
		log.Fatalf("Unable to stage path: %s\n", err)
	}

	// Commit the file
	_, err = wt.Commit("Initial commit", &git.CommitOptions{
		Author: &object.Signature{
			Name:  "John Doe",
			Email: "[email protected]",
			When:  time.Now(),
		},
	})
	if err != nil {
		log.Fatalf("Unable to commit: %s\n", err)
	}

	// Create destination clone directory
	dstPath, err := ioutil.TempDir("", dstPathName)
	if err != nil {
		log.Fatalf("Unable to create test repository: %s\n", err)
	}

	defer os.RemoveAll(dstPath)

	// Get srcPath as a file: url to clone it
	srcURL, _ := url.Parse(srcPath)
	srcURL.Scheme = "file"

	if err != nil {
		log.Fatalf("Unable to unescape path: %s\n", err)
	}

	fmt.Printf("Attempting to clone from %s\n", srcURL.String())

	// Attempt to clone local repo
	_, err = git.PlainClone(dstPath, false, &git.CloneOptions{
		URL:               srcURL.String(),
		RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
	})

	if err != nil {
		log.Fatalf("Unable to clone repo: %s", err)
	}

	fmt.Printf("Cloned repository into %s\n", dstPath)
}

Can this be considered a bug in go-git or maybe an enhancement in go-git? Or is this just by design?

alethenorio avatar May 18 '18 10:05 alethenorio

@ByteFlinger I think it can be considered a bug.

smola avatar Sep 25 '18 15:09 smola

I have found that incorrect file url's seem to work. If you try file://C:/Users/USERNA~1/AppData/Local/Temp/dstRepo768183330 it would probably work as expected. That said, it seems to be a url parsing problem. Not exactly sure where...

Traced this into go net/url Parse itself... Then checked the reported issues, and they explicitly chose this behavior. So, even though windows expects urls of the form file:///C:/foo, golang decided to only parse file://C:/foo... For better or worse, this seems to be behaving as designed.

lucastheisen avatar Nov 11 '19 21:11 lucastheisen