Repository: go-git/go-git

Issue:
PlainClone results in an error, due to Rename clashing with Chroot
Calling a basic PlainClone results in an error.

In the example below, the PlainClone() call uses an osfs.BoundOS filesystem with its baseDir set to "rbuf/.git". 

Then it tries to Rename() the bundle file from 

oldpath='rbuf/.git/objects/pack/tmp_pack_3521308057'; 

to

newpath='objects/pack/pack-d13fafeca6b25414f9da5e45d3cb981063cdb655.pack'

And you can see that it has mistakenly included the same prefix that it already set as the baseDir. This results in the Rename failing because the baseDir is pre-pended by the BoundOS, and thus shows up twice.

Thus the Rename, at https://github.com/go-git/go-git/blob/main/storage/filesystem/dotgit/writers.go#L142 

in dotgit.PackWriter.save(), fails with

"lstat rbuf/.git/rbuf/.git/objects/pack/tmp_pack_522850955: no such file or directory"

See verify_test.go:19 in the reproducer repo https://github.com/glycerine/bundle

~~~
    repo, err := git.PlainClone("rbuf", &git.CloneOptions{
        URL:      "https://github.com/glycerine/rbuf",
        Progress: os.Stdout,
    })
~~~

version: github.com/go-git/go-git/v6
At go-git latest 1f24ae85fe166e795a9822d2e91da43f4f86ac74 from 2025 July 11.
Using go-billy latest c1f149aaab071551ea7acd2663314449bf983738 from 2025 July 11.

Reproducer run output:
~~~
go test -v
=== RUN   TestVerify
Enumerating objects: 130, done.
Total 130 (delta 0), reused 0 (delta 0), pack-reused 130 (from 1)
--- FAIL: TestVerify (0.28s)
panic: lstat rbuf/.git/rbuf/.git/objects/pack/tmp_pack_522850955: no such file or directory [recovered, repanicked]
~~~

debugging variable dumps, mostly for my for reference:
~~~
go-git/plumbing/format/packfile/parser.go: Parser.Parse() end err0='<nil>'
PackWriter.Close() returning w.save()
base = 'objects/pack/pack-d13fafeca6b25414f9da5e45d3cb981063cdb655'
w.fs = '&osfs.BoundOS{baseDir:"rbuf/.git", deduplicatePath:true}';
w.fs.Rename err0 = 'lstat rbuf/.git/rbuf/.git/objects/pack/tmp_pack_3521308057: no \
such file or directory';
 oldpath='rbuf/.git/objects/pack/tmp_pack_3521308057'; 
newpath='objects/pack/pack-d13fafeca6b25414f9da5e45d3cb981063cdb655.pack'
PackWriter.save() returning err0 = 'lstat rbuf/.git/rbuf/.git/objects/pack/tmp_pack\
_3521308057: no such file or directory'
ioutil.CheckClose sees cerr = 'lstat rbuf/.git/rbuf/.git/objects/pack/tmp_pack_3521\
308057: no such file or directory'
~~~

Edit: I can fix this in a couple of ways, so I'd appreciate advice on which is the right approach. There are no docs on the func saying what the intended design is, so I'm left to guess. I'll take a stab at a PR, but it will be based on a guess...

full stack trace showing where the error originates:

~~~
--- FAIL: TestVerify (1.43s)
panic: where? [recovered, repanicked]

goroutine 6 [running]:
testing.tRunner.func1.2({0x45ca3a0, 0x467eb00})
        /usr/local/go1.25rc2/src/testing/testing.go:1872 +0x237
testing.tRunner.func1()
        /usr/local/go1.25rc2/src/testing/testing.go:1875 +0x35b
panic({0x45ca3a0?, 0x467eb00?})
        /usr/local/go1.25rc2/src/runtime/panic.go:783 +0x132
github.com/go-git/go-git/v6/storage/filesystem/dotgit.(*PackWriter).save(0xc00009e100)
        /Users/jaten/go/src/github.com/go-git/go-git/storage/filesystem/dotgit/writers.go:165 +0x6cb
github.com/go-git/go-git/v6/storage/filesystem/dotgit.(*PackWriter).Close(0xc00009e100)
        /Users/jaten/go/src/github.com/go-git/go-git/storage/filesystem/dotgit/writers.go:126 +0x3a5
github.com/go-git/go-git/v6/utils/ioutil.CheckClose({0x4b713948?, 0xc00009e100?}, 0xc00003b4e8)
        /Users/jaten/go/src/github.com/go-git/go-git/utils/ioutil/common.go:135 +0x2a
github.com/go-git/go-git/v6/plumbing/format/packfile.WritePackfileToObjectStorage({0x4b713660, 0xc0002662d0}, {0x4681020, 0xc0000dc280})
        /Users/jaten/go/src/github.com/go-git/go-git/plumbing/format/packfile/common.go:79 +0x2f7
github.com/go-git/go-git/v6/plumbing/format/packfile.UpdateObjectStorage({0x4b6f7460, 0xc0002662d0}, {0x4681020, 0xc0000dc280})
        /Users/jaten/go/src/github.com/go-git/go-git/plumbing/format/packfile/common.go:37 +0x10a
github.com/go-git/go-git/v6/plumbing/transport.FetchPack({0x4684cb8, 0x49b11a0}, {0x4688c88, 0xc0002662d0}, {0x4686b40, 0xc00023fe30}, {0x4683080, 0xc0000ce3a0}, 0x0, 0xc0000e80\
00)
        /Users/jaten/go/src/github.com/go-git/go-git/plumbing/transport/fetch.go:46 +0x398
github.com/go-git/go-git/v6/plumbing/transport/http.(*HTTPSession).Fetch(0xc00023fe30, {0x4684cb8, 0x49b11a0}, 0xc0000e8000)
        /Users/jaten/go/src/github.com/go-git/go-git/plumbing/transport/http/common.go:479 +0x316
github.com/go-git/go-git/v6.(*Remote).fetch(0xc00000ea50, {0x4684cb8, 0x49b11a0}, 0xc00003bc30)
        /Users/jaten/go/src/github.com/go-git/go-git/remote.go:455 +0xaa5
github.com/go-git/go-git/v6.(*Repository).fetchAndUpdateReferences(0xc00026e4e0, {0x4684cb8, 0x49b11a0}, 0xc000117c30, {0x446e9cc, 0x4})
        /Users/jaten/go/src/github.com/go-git/go-git/repository.go:1097 +0x185
github.com/go-git/go-git/v6.(*Repository).clone(0xc00026e4e0, {0x4684cb8, 0x49b11a0}, 0xc00003be50)
        /Users/jaten/go/src/github.com/go-git/go-git/repository.go:948 +0x665
github.com/go-git/go-git/v6.PlainCloneContext({0x4684cb8, 0x49b11a0}, {0x446e8b0, 0x4}, 0xc000104e50)
        /Users/jaten/go/src/github.com/go-git/go-git/repository.go:530 +0x236
github.com/go-git/go-git/v6.PlainClone(...)
        /Users/jaten/go/src/github.com/go-git/go-git/repository.go:494
github.com/glycerine/bundle.TestVerify(0xc000003880?)
        /Users/jaten/glycerine/bundle/verify_test.go:19 +0x10a
testing.tRunner(0xc000003880, 0x467c660)
        /usr/local/go1.25rc2/src/testing/testing.go:1934 +0xea
created by testing.(*T).Run in goroutine 1
        /usr/local/go1.25rc2/src/testing/testing.go:1997 +0x465
exit status 2
FAIL    github.com/glycerine/bundle     1.599s
~~~

Discussion:
@cooperspencer: I am currently facing the same issue.@glycerine: @cooperspencer I don't know if its the right fix, but #1611 offers a simple way forward if you are stuck right now on relative paths not working.@cooperspencer: @glycerine thanks a lot. For now I downgraded to v5 again, because it works there, but I'll upgrade to v6 as soon as the PR is merged.@pjbgf: @glycerine thanks for filing this bug, I can confim I can reproduce this using the clone in the examples:
```
go run _examples/clone/main.go https://github.com/glycerine/rbuf buf3
git clone https://github.com/glycerine/rbuf buf3 --recursive
Enumerating objects: 130, done.
Total 130 (delta 0), reused 0 (delta 0), pack-reused 130 (from 1)
error: lstat buf3/.git/buf3/.git/objects/pack/tmp_pack_397782543: no such file or directory
exit status 1
```

I'll take a look at your PR shortly.

Solution patch:
diff --git a/repository.go b/repository.go
index d584fcda..c2e361cd 100644
--- a/repository.go
+++ b/repository.go
@@ -289,6 +289,15 @@ func PlainInit(path string, isBare bool, options ...InitOption) (*Repository, er
 		oFn(&o)
 	}
 
+	var err error
+	// go-git/go-git#1610
+	if !filepath.IsAbs(path) {
+		path, err = filepath.Abs(path)
+		if err != nil {
+			return nil, err
+		}
+	}
+
 	if isBare {
 		dot = osfs.New(path, osfs.WithBoundOS())
 		initFn = func(s *filesystem.Storage) (*Repository, error) {



Test patch:
diff --git a/repository_test.go b/repository_test.go
index 8d1b137e..5e365eec 100644
--- a/repository_test.go
+++ b/repository_test.go
@@ -886,8 +886,13 @@ func (s *RepositorySuite) TestPlainOpenNotExistsDetectDotGit() {
 }
 
 func (s *RepositorySuite) TestPlainClone() {
-	dir, err := os.MkdirTemp("", "")
-	s.NoError(err)
+	dir := "rel-dir"
+	err := os.Mkdir(dir, 0o755)
+	s.Require().NoError(err)
+
+	s.T().Cleanup(func() {
+		os.RemoveAll(dir)
+	})
 
 	r, err := PlainClone(dir, &CloneOptions{
 		URL: s.GetBasicLocalRepositoryURL(),