func()

in iterator/git.go [225:441]


func (obj *Git) Recurse(ctx context.Context, scan interfaces.ScanFunc) ([]interfaces.Iterator, error) {
	relDir := safepath.UnsafeParseIntoRelDir("git/")
	prefix := safepath.JoinToAbsDir(obj.Prefix, relDir)
	if err := os.MkdirAll(prefix.Path(), interfaces.Umask); err != nil {
		return nil, err
	}

	// make a unique ID for the directory
	// XXX: we can consider different algorithms or methods here later...
	sum := sha256.Sum256([]byte(obj.URL))
	hashRelDir, err := safepath.ParseIntoRelDir(fmt.Sprintf("%x", sum))
	if err != nil {
		return nil, err
	}
	repoAbsDir := safepath.JoinToAbsDir(prefix, hashRelDir)

	mapMutex.Lock()
	mu, exists := mutexes[obj.URL]
	if !exists {
		mu = &sync.Mutex{}
		mutexes[obj.URL] = mu
	}
	mapMutex.Unlock()

	if obj.Debug {
		obj.Logf("locking: %s", obj.String())
	}
	mu.Lock() // locking happens here (unlock on all errors/returns!)
	once := &sync.Once{}
	obj.unlock = func() {
		fn := func() {
			if obj.Debug {
				obj.Logf("unlocking: %s", obj.String())
			}
			mu.Unlock()
		}
		once.Do(fn)
	}

	// XXX: unlock when context closes?

	obj.Logf("cloning %s into %s", obj.String(), repoAbsDir)

	directory := repoAbsDir.Path()
	isBare := false
	repository, err := git.PlainCloneContext(ctx, directory, isBare, &git.CloneOptions{
		URL: obj.URL,
		// Don't recurse, we do it manually with the FsIterator, as this
		// way we'll get all the repositories cloned next to each other,
		// instead of in a big recursive filesystem tree.
		RecurseSubmodules: git.NoRecurseSubmodules,
		//Auth transport.AuthMethod
		//Progress: os.Stdout,

	})
	if err == git.ErrRepositoryAlreadyExists {
		obj.Logf("repo %s already exists", obj.String())
		repository, err = git.PlainOpenWithOptions(directory, &git.PlainOpenOptions{})
		if err != nil {
			obj.unlock()
			return nil, errwrap.Wrapf(err, "error opening repository")
		}

	} else if err != nil {
		obj.unlock()
		return nil, errwrap.Wrapf(err, "error cloning repository")
	}

	var hash plumbing.Hash

	if obj.Hash != "" {
		hash = plumbing.NewHash(obj.Hash)
	}

	if obj.Ref != "" {
		// TODO: update with https://github.com/go-git/go-git/issues/289
		var err error
		ref := plumbing.ReferenceName(obj.Ref)
		if hash, err = getCommitFromRef(repository, ref); err != nil {
			obj.unlock()
			return nil, err
		}
	}

	if obj.Rev != "" {
		// This API returns a *Hash. Everything else uses a Hash as-is.
		pHash, err := repository.ResolveRevision(plumbing.Revision(obj.Rev))
		if err != nil {
			obj.unlock()
			return nil, err
		}
		hash = *pHash
	}

	if hash.IsZero() { // desired state not specified, use HEAD branch...
		// XXX: I'm not sure this is the correct way to find the HEAD.
		// XXX: eg: https://github.com/ansible/ansible uses `devel`.
		// XXX: running: `git remote show origin`, shows: `HEAD branch: <branch>`
		// XXX: how do I extract that without looking in: ~/.git/refs/remotes/origin/*
		names := []string{"HEAD", "master", "main"}
		var err error
		var name string
		for _, name = range names {
			ref := plumbing.NewRemoteReferenceName("origin", name)
			// git symbolic-ref refs/remotes/origin/HEAD ?
			hash, err = getCommitFromRef(repository, ref)
			if err == plumbing.ErrReferenceNotFound {
				continue // this error means we continue
			}
			break // if a different error or a nil, we are done!
		}
		if err != nil { // check here if we found something!
			obj.unlock()
			return nil, errwrap.Wrapf(err, "could not find default HEAD in origin")
		}
		obj.Logf("default HEAD is at: %s", name)
	}

	head, err := repository.Head()
	if err != nil {
		obj.unlock()
		return nil, err
	}
	if obj.Debug {
		obj.Logf("HEAD is at: %s", head.Hash())
	}

	// If the desired state, is not equal to the actual state, set it!
	if hash.String() != head.Hash().String() {
		worktree, err := repository.Worktree()
		if err != nil {
			obj.unlock()
			return nil, err
		}

		checkoutOptions := &git.CheckoutOptions{}
		if obj.Hash != "" {
			checkoutOptions.Hash = hash
		}
		// We use the consistent hash approach to identify the repo so
		// that we have a unique identifier to use everywhere...
		//if obj.Ref != "" {
		//	checkoutOptions.Branch = plumbing.ReferenceName(obj.Ref)
		//}

		if err := worktree.Checkout(checkoutOptions); err != nil {
			obj.unlock()
			return nil, err
		}
	}

	obj.iterators = []interfaces.Iterator{}

	u, err := url.Parse(obj.URL) // build a url to modify
	if err != nil {
		obj.unlock()
		return nil, err
	}
	u.Scheme = GitSchemeRaw
	u.Opaque = ""                         // encoded opaque data
	if _, has := u.User.Password(); has { // redact password
		u.User = url.UserPassword(u.User.Username(), "")
	}
	//u.Host = ? // host or host:port

	// remove the .git suffix of old-style repos
	if obj.TrimGitSuffix && strings.HasSuffix(u.Path, ".git") {
		u.Path = strings.TrimSuffix(u.Path, ".git")
	}

	u.RawPath = ""       // encoded path hint (see EscapedPath method)
	u.ForceQuery = false // append a query ('?') even if RawQuery is empty
	v := url.Values{}
	v.Set("sha1", hash.String())
	u.RawQuery = v.Encode() // encoded query values, without '?'
	u.Fragment = ""         // fragment for references, without '#'
	u.RawFragment = ""      // encoded fragment hint (see EscapedFragment method)

	iterator := &Fs{
		Debug: obj.Debug,
		Logf: func(format string, v ...interface{}) {
			obj.Logf(format, v...) // TODO: add a prefix?
		},
		Prefix: obj.Prefix,

		Iterator: obj,

		Path: repoAbsDir,

		GenUID: func(safePath safepath.Path) (string, error) {
			if !safepath.HasPrefix(safePath, repoAbsDir) {
				// programming error
				return "", fmt.Errorf("path doesn't have prefix")
			}

			p := ""
			// remove repoAbsDir prefix from safePath to get a relPath
			relPath, err := safepath.StripPrefix(safePath, repoAbsDir)
			if err == nil {
				p = relPath.String()
			} else if err != nil && safePath.String() != repoAbsDir.String() {
				// programming error
				return "", errwrap.Wrapf(err, "problem stripping prefix")
			}

			x := *u // copy
			x.Path += "/" + p

			return x.String(), nil
		},

		//Unlock: unlock,
	}
	obj.iterators = append(obj.iterators, iterator)

	return obj.iterators, nil
}