libgit2 / git2go

Git to Go; bindings for libgit2. Like McDonald's but tastier.
MIT License
1.92k stars 316 forks source link

Push equivalent to clone mirror? #927

Open Marakai opened 1 year ago

Marakai commented 1 year ago

Despite looking at the godocs, the tests and the source, I can't seem to figure out the correction PushOptions for the opposite of a mirror clone, i.e. push with the mirror options. Ideally, for my special use case with force.

I'm trying to clone from one server and push to another, using the mirror option. So, I:

This is using https, not ssh, if it matters.

On that notice, more examples would be very nice (looking at unit tests isn't quite the sames thing).

Marakai commented 1 year ago

At this time, my somewhat naive first implementation, after poring through the source looks like this. However there are a number of issues that I've listed with inline comments.

Note: this is built on MacOS. libgit2, libssh2 and openssl are installed via Homebrew. SSL certificates and CA are all installed as part of the corporate build. HTTPS pushes into Git are authenticated via access token.

package migrate

import (
    "XXXXXX.ghe.com/gh-platform/gh-management-services/service/api"
    gitwrap "XXXXXXX.ghe.com/gh-platform/gh-management-services/service/utils/git"
    "context"
    "errors"
    libgit "github.com/libgit2/git2go/v33"
)

const GHAE = "XXXXXXX.ghe.com"

func MirrorPush(ctx context.Context, github api.Github, org string, repoSlug string, repo *libgit.Repository) error {
    if ghUrl, err := github.GetRepoUrl(org, repoSlug); err != nil {
        return err
    } else {
        rcb := libgit.RemoteCallbacks{
// This will not be called if I inject the token directly below. However, when called
// I don't get a valid Credential and receive an error 
// "could not find appropriate mechanism for credentials" 
// when Push() is called below.
            CredentialsCallback: func(url, username string, allowedTypes libgit.CredentialType) (*libgit.Credential, error) {
                c, err := libgit.NewCredentialUsername(github.Token())
                if err != nil {
                    return nil, err
                }
                return c, nil
            },
            CertificateCheckCallback: func(cert *libgit.Certificate, valid bool, hostname string) error {
                if hostname != GHAE {
                    return errors.New("hostname does not match")
                }
                return nil
            },
        }
        proxyopt := libgit.ProxyOptions{
            Type: libgit.ProxyTypeAuto,
            // Url: utils.GetProxyUrl(),
        }
        pushopts := libgit.PushOptions{
            RemoteCallbacks: rcb,
            PbParallelism:   0,
            Headers:         nil,
            ProxyOptions:    proxyopt,
        }

        logger.Notice(ctx, "starting push of %s into %s", repoSlug, ghUrl)
        remote, err := repo.Remotes.CreateWithFetchspec("ghorigin", ghUrl, "+refs/*:refs/*")
        if err != nil {
            return err
        }
// A helper method to inject the access token during debugging and testing
// It retrieves the token from a secure store
// When injecting the token into the URL, the connection succeeds, but this is highly
// undesirable due to risk of exposure
        ghUrl = gitwrap.InjectToken(ghUrl, github.Token())

        cfg, err := repo.Config()
        if err != nil {
            return err
        }

        if err = cfg.SetBool("remote.ghorigin.mirror", true); err != nil {
            return err
        }

// When using the injected URL and able to connect, this call returns without error
// But does NOT actually push anything!
        if err = remote.Push(nil, &pushopts); err != nil {
            return err
        }
        logger.Notice(ctx, "finished mirror-push of %s into %s", repoSlug, repo.Path())
    }
    return nil
}