8000 git: repository, add empty dir check to clone by CLBRITTON2 · Pull Request #1594 · go-git/go-git · GitHub
[go: up one dir, main page]

Skip to content

git: repository, add empty dir check to clone #1594

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 22 additions & 49 deletions repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/go-git/go-billy/v6"
"github.com/go-git/go-billy/v6/osfs"
"github.com/go-git/go-billy/v6/util"
"github.com/go-git/go-git/v6/config"
"github.com/go-git/go-git/v6/internal/path_util"
"github.com/go-git/go-git/v6/internal/revision"
Expand Down Expand Up @@ -53,7 +52,6 @@ var (
ErrInvalidReference = errors.New("invalid reference, should be a tag or a branch")
ErrRepositoryNotExists = errors.New("repository does not exist")
ErrRepositoryIncomplete = errors.New("repository's commondir path does not exist")
ErrRepositoryAlreadyExists = errors.New("repository already exists")
ErrRemoteNotFound = errors.New("remote not found")
ErrRemoteExists = errors.New("remote already exists")
ErrAnonymousRemoteName = errors.New("anonymous remote name must be 'anonymous'")
Expand All @@ -65,6 +63,7 @@ var (
ErrAlternatePathNotSupported = errors.New("alternate path must use the file scheme")
ErrUnsupportedMergeStrategy = errors.New("unsupported merge strategy")
ErrFastForwardMergeNotPossible = errors.New("not possible to fast-forward merge changes")
ErrTargetDirNotEmpty = errors.New("destination path already exists and is not empty")
)

// Repository represents a git repository
Expand Down Expand Up @@ -115,7 +114,7 @@ func WithObjectFormat(of formatcfg.ObjectFormat) InitOption {

// Init creates an empty git repository, based on the given Storer and worktree.
// The worktree Filesystem is optional, if nil a bare repository is created. If
// the given storer is not empty ErrRepositoryAlreadyExists is returned
// the given storer is not empty ErrTargetDirNotEmpty is returned
func Init(s storage.Storer, opts ...InitOption) (*Repository, error) {
options := newInitOptions()
for _, oFn := range opts {
Expand All @@ -137,7 +136,7 @@ func Init(s storage.Storer, opts ...InitOption) (*Repository, error) {
switch err {
case plumbing.ErrReferenceNotFound:
case nil:
return nil, ErrRepositoryAlreadyExists
return nil, ErrTargetDirNotEmpty
default:
return nil, err
}
Expand Down Expand Up @@ -244,14 +243,14 @@ func Open(s storage.Storer, worktree billy.Filesystem) (*Repository, error) {

// Clone a repository into the given Storer and worktree Filesystem with the
// given options, if worktree is nil a bare repository is created. If the given
// storer is not empty ErrRepositoryAlreadyExists is returned.
// storer is not empty ErrTargetDirNotEmpty is returned.
func Clone(s storage.Storer, worktree billy.Filesystem, o *CloneOptions) (*Repository, error) {
return CloneContext(context.Background(), s, worktree, o)
}

// CloneContext a repository into the given Storer and worktree Filesystem with
// the given options, if worktree is nil a bare repository is created. If the
// given storer is not empty ErrRepositoryAlreadyExists is returned.
// given storer is not empty ErrTargetDirNotEmpty is returned.
//
// The provided Context must be non-nil. If the context expires before the
// operation is complete, an error is returned. The context only affects the
Expand Down Expand Up @@ -280,7 +279,7 @@ func CloneContext(

// PlainInit create an empty git repository at the given path. isBare defines
// if the repository will have worktree (non-bare) or not (bare), if the path
// is not empty ErrRepositoryAlreadyExists is returned.
// is not empty ErrTargetDirNotEmpty is returned.
func PlainInit(path string, isBare bool, options ...InitOption) (*Repository, error) {
var wt, dot billy.Filesystem
var initFn func(s *filesystem.Storage) (*Repository, error)
Expand All @@ -306,7 +305,6 @@ func PlainInit(path string, isBare bool, options ...InitOption) (*Repository, er
}
s := filesystem.NewStorage(dot, cache.NewObjectLRUDefault())
r, err := initFn(s)

if err != nil {
return nil, err
}
Expand Down Expand Up @@ -491,21 +489,26 @@ func dotGitCommonDirectory(fs billy.Filesystem) (commonDir billy.Filesystem, err

// PlainClone a repository into the path with the given options, isBare defines
// if the new repository will be bare or normal. If the path is not empty
// ErrRepositoryAlreadyExists is returned.
// ErrTargetDirNotEmpty is returned.
func PlainClone(path string, o *CloneOptions) (*Repository, error) {
return PlainCloneContext(context.Background(), path, o)
}

// PlainCloneContext a repository into the path with the given options, isBare
// defines if the new repository will be bare or normal. If the path is not empty
// ErrRepositoryAlreadyExists is returned.
// ErrTargetDirNotEmpty is returned.
//
// The provided Context must be non-nil. If the context expires before the
// operation is complete, an error is returned. The context only affects the
// transport operations.
//
// TODO(smola): refuse upfront to clone on a non-empty directory in v5, see #1027
func PlainCloneContext(ctx context.Context, path string, o *CloneOptions) (*Repository, error) {
empty, err := checkTargetDirIsEmpty(path)
if err != nil {
return nil, err
}
if !empty {
return nil, fmt.Errorf("%w %s", ErrTargetDirNotEmpty, path)
}
start := time.Now()
defer func() {
url := ""
Expand All @@ -515,11 +518,6 @@ func PlainCloneContext(ctx context.Context, path string, o *CloneOptions) (*Repo
trace.Performance.Printf("performance: %.9f s: git command: git clone %s", time.Since(start).Seconds(), url)
}()

cleanup, cleanupParent, err := checkIfCleanupIsNeeded(path)
if err != nil {
return nil, err
}

isBare := o.Bare
if o.Mirror {
isBare = true
Expand All @@ -530,12 +528,6 @@ func PlainCloneContext(ctx context.Context, path string, o *CloneOptions) (*Repo
}

err = r.clone(ctx, o)
if err != nil && err != ErrRepositoryAlreadyExists {
if cleanup {
_ = cleanUpDir(path, cleanupParent)
}
}

return r, err
}

Expand All @@ -547,49 +539,30 @@ func newRepository(s storage.Storer, worktree billy.Filesystem) *Repository {
}
}

func checkIfCleanupIsNeeded(path string) (cleanup bool, cleanParent bool, err error) {
func checkTargetDirIsEmpty(path string) (empty bool, err error) {
fi, err := osfs.Default.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return true, true, nil
return true, nil
}

return false, false, err
return false, err
}

if !fi.IsDir() {
return false, false, fmt.Errorf("path is not a directory: %s", path)
return false, fmt.Errorf("path is not a directory: %s", path)
}

files, err := osfs.Default.ReadDir(path)
if err != nil {
return false, false, err
return false, err
}

if len(files) == 0 {
return true, false, nil
}

return false, false, nil
}

func cleanUpDir(path string, all bool) error {
if all {
return util.RemoveAll(osfs.Default, path)
}

files, err := osfs.Default.ReadDir(path)
if err != nil {
return err
}

for _, fi := range files {
if err := util.RemoveAll(osfs.Default, osfs.Default.Join(path, fi.Name())); err != nil {
return err
}
return true, nil
}

return err
return false, nil
}

// Config return the repository config. In a filesystem backed repository this
Expand Down
18 changes: 9 additions & 9 deletions repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func (s *RepositorySuite) TestInitAlreadyExists() {
s.NotNil(r)

r, err = Init(st)
s.ErrorIs(err, ErrRepositoryAlreadyExists)
s.ErrorIs(err, ErrTargetDirNotEmpty)
s.Nil(r)
}

Expand Down Expand Up @@ -684,7 +684,7 @@ func (s *RepositorySuite) TestPlainInitAlreadyExists() {
s.NotNil(r)

r, err = PlainInit(dir, true)
s.ErrorIs(err, ErrRepositoryAlreadyExists)
s.ErrorIs(err, ErrTargetDirNotEmpty)
s.Nil(r)
}

Expand Down Expand Up @@ -1044,7 +1044,7 @@ func (s *RepositorySuite) TestPlainCloneOverExistingGitDirectory() {
URL: s.GetBasicLocalRepositoryURL(),
})
s.Nil(r)
s.ErrorIs(err, ErrRepositoryAlreadyExists)
s.ErrorIs(err, ErrTargetDirNotEmpty)
}

func (s *RepositorySuite) TestPlainCloneContextCancel() {
Expand Down Expand Up @@ -1132,7 +1132,7 @@ func (s *RepositorySuite) TestPlainCloneContextNonExistentWithNotDir() {
s.False(fi.IsDir())
}

func (s *RepositorySuite) TestPlainCloneContextNonExistentWithNotEmptyDir() {
func (s *RepositorySuite) TestPlainCloneContextFailedClonePreservesExistingDir() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

Expand All @@ -1152,8 +1152,8 @@ func (s *RepositorySuite) TestPlainCloneContextNonExistentWithNotEmptyDir() {
r, err := PlainCloneContext(ctx, fs.Join(fs.Root(), repoDir), &CloneOptions{
URL: "incorrectOnPurpose",
})
s.NotNil(r)
s.ErrorIs(err, transport.ErrRepositoryNotFound)
s.Nil(r)
s.ErrorIs(err, ErrTargetDirNotEmpty)

_, err = fs.Stat(dummyFile)
s.NoError(err)
Expand All @@ -1174,7 +1174,7 @@ func (s *RepositorySuite) TestPlainCloneContextNonExistingOverExistingGitDirecto
URL: "incorrectOnPurpose",
})
s.Nil(r)
s.ErrorIs(err, ErrRepositoryAlreadyExists)
s.ErrorIs(err, ErrTargetDirNotEmpty)
}

func (s *RepositorySuite) TestPlainCloneWithRecurseSubmodules() {
Expand Down Expand Up @@ -1301,8 +1301,8 @@ func (s *RepositorySuite) TestFetchWithFilters() {
Filter: packp.FilterBlobNone(),
})
s.ErrorIs(err, transport.ErrFilterNotSupported)

}

func (s *RepositorySuite) TestFetchWithFiltersReal() {
r, _ := Init(memory.NewStorage())
_, err := r.CreateRemote(&config.RemoteConfig{
Expand All @@ -1317,8 +1317,8 @@ func (s *RepositorySuite) TestFetchWithFiltersReal() {
blob, err := r.BlobObject(plumbing.NewHash("9a48f23120e880dfbe41f7c9b7b708e9ee62a492"))
s.NotNil(err)
s.Nil(blob)

}

func (s *RepositorySuite) TestCloneWithProgress() {
s.T().Skip("Currently, go-git server-side implementation does not support writing" +
"progress and sideband messages to the client. This means any tests that" +
Expand Down
Loading
0