Search code examples
gogo-git

Pushing to a remote with basic authentication


I'm currently struggling to get go-git working with private repositories on GitHub. Although working with the git command line works as expected (git push origin), the code snippet below does not work. The last log command returns the following result:

 Error during push     error=repository not found

The repository itself does exist, otherwise the push from the command line would not work. First, I thought, that it might be related that the remote repository is empty. But even if that's not the case, I've got the same error. The credentials are valid, I've double-checked them.

So, there must be something I am missing. But since I am new to Go, my expertise is too low for this to be figured out.

package git

import (
    "io/ioutil"
    "path/filepath"
    "time"

    "github.com/apex/log"
    "github.com/go-git/go-git/v5"
    "github.com/go-git/go-git/v5/config"
    "github.com/go-git/go-git/v5/plumbing/object"
    "github.com/go-git/go-git/v5/plumbing/transport/http"
    "github.com/spf13/viper"
)

const gitDataDirectory = "./data/"
const defaultRemoteName = "origin"

// Commit creates a commit in the current repository
func Commit() {
    repo, err := git.PlainOpen(gitDataDirectory)

    if err != nil {
        // Repository does not exist yet, create it
        log.Info("The Git repository does not exist yet and will be created.")

        repo, err = git.PlainInit(gitDataDirectory, false)
    }

    if err != nil {
        log.Warn("The data folder could not be converted into a Git repository. Therefore, the versioning does not work as expected.")
        return
    }

    w, _ := repo.Worktree()


    log.Info("Committing new changes...")
    w.Add(".gitignore")
    w.Add("info.json")
    w.Commit("test", &git.CommitOptions{
        Author: &object.Signature{
            Name:  "test",
            Email: "test@localhost",
            When:  time.Now(),
        },
    })

    _, err = repo.Remote(defaultRemoteName)
    if err != nil {
        log.Info("Creating new Git remote named " + defaultRemoteName)
        _, err = repo.CreateRemote(&config.RemoteConfig{
            Name: defaultRemoteName,
            URLs: []string{"https://github.com/jlnostr/blub.git"},
        })

        if err != nil {
            log.WithError(err).Warn("Error creating remote")
        }
    }

    auth := &http.BasicAuth{
        Username: "jlnostr",
        Password: "[git_basic_auth_token]",
    }
    log.Info("Pushing changes to remote")
    err = repo.Push(&git.PushOptions{
        RemoteName: defaultRemoteName,
        Auth:       auth,
    })

    if err != nil {
        log.WithError(err).Warn("Error during push")
    }
}


Solution

  • There were several issues:

    1. The GitHub token only had access to public repositories, but I've tried to push to a private repository. Using the repo permissions (instead of just repo:public) helped.

    2. The repo was not yet "really" initialized. Pulling before pushing helped in this case, as @peakle mentioned.

    So, below is a complete working example for initializing a private repository with go-git v5.

    package git
    
    import (
        "io/ioutil"
        "path/filepath"
        "time"
    
        "github.com/go-git/go-git/v5"
        "github.com/go-git/go-git/v5/config"
        "github.com/go-git/go-git/v5/plumbing/object"
        "github.com/go-git/go-git/v5/plumbing/transport/http"
    )
    
    const gitDataDirectory = "./data/"
    const defaultRemoteName = "origin"
    
    var auth = &http.BasicAuth{
        Username: "<username>",
        Password: "<git_basic_auth_token>",
    }
    
    func createCommitOptions() *git.CommitOptions {
        return &git.CommitOptions{
            Author: &object.Signature{
                Name:  "Rick Astley",
                Email: "never.gonna.give.you.up@localhost",
                When:  time.Now(),
            },
        }
    }
    
    // Commit creates a commit in the current repository
    func Commit() {
        err := initializeGitRepository()
        if err != nil {
            // logging: The folder could not be converted into a Git repository.
            return
        }
    
        // Open after initialization
        repo, _ := git.PlainOpen(gitDataDirectory)
        w, _ := repo.Worktree()
    
        status, _ := w.Status()
        if status.IsClean() {
            return
        }
    
        // Committing new changes
        w.Add("<your_file>.txt")
        w.Commit("test", createCommitOptions())
    
        // Pushing to remote
        err = repo.Push(&git.PushOptions{
            RemoteName: defaultRemoteName,
            Auth:       auth,
        })
    }
    
    func initializeGitRepository() error {
        _, err := git.PlainOpen(gitDataDirectory)
        if err == nil {
            return nil
        }
    
        // The Git repository does not exist yet and will be created.
    
        repo, err := git.PlainInit(gitDataDirectory, false)
    
        if err != nil {
            return err
        }
    
        // Writing default .gitignore with "media/" as first line
        filename := filepath.Join(gitDataDirectory, ".gitignore")
        err = ioutil.WriteFile(filename, []byte("media/"), 0644)
    
        if err != nil {
            return err
        }
    
        w, _ := repo.Worktree()
        w.Add(".gitignore")
        w.Commit("Initial commit", createCommitOptions())
    
        return initializeRemote()
    }
    
    func initializeRemote() error {
        repo, err := git.PlainOpen(gitDataDirectory)
        if err != nil {
            return err
        }
    
        _, err = repo.Remote(defaultRemoteName)
        if err == nil {
            // Repo already exists, skipping
            return nil
        }
    
        w, err := repo.Worktree()
        if err != nil {
            return err
        }
    
        refspec := config.RefSpec("+refs/heads/*:refs/remotes/origin/*")
    
        // Creating default remote
        _, err = repo.CreateRemote(&config.RemoteConfig{
            Name:  defaultRemoteName,
            URLs:  []string{"https://github.com/<user>/<repo>.git"},
            Fetch: []config.RefSpec{refspec},
        })
    
        if err != nil {
            // TODO
        }
    
        // Pulling from remote
        w.Pull(&git.PullOptions{
            RemoteName: defaultRemoteName,
            Auth:       auth,
        })
    
        return err
    }