This document covers all operations for interacting with remote repositories, including fetching, pushing, pulling, and authentication.
go-git provides comprehensive support for remote repository operations:
type Remote struct {
// contains filtered or unexported fields
}
func (r *Remote) Config() *config.RemoteConfig
func (r *Remote) Push(o *PushOptions) error
func (r *Remote) PushContext(ctx context.Context, o *PushOptions) error
func (r *Remote) Fetch(o *FetchOptions) error
func (r *Remote) FetchContext(ctx context.Context, o *FetchOptions) error
func (r *Remote) List(o *ListOptions) ([]*plumbing.Reference, error)
func (r *Remote) String() stringfunc (r *Repository) Remote(name string) (*Remote, error)Example:
package main
import (
"fmt"
"github.com/go-git/go-git/v5"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
// Get origin remote
remote, err := r.Remote("origin")
if err != nil {
panic(err)
}
fmt.Println("Remote name:", remote.Config().Name)
fmt.Println("URLs:", remote.Config().URLs)
}func (r *Repository) Remotes() ([]*Remote, error)Example:
package main
import (
"fmt"
"github.com/go-git/go-git/v5"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
// List all remotes
remotes, err := r.Remotes()
if err != nil {
panic(err)
}
for _, remote := range remotes {
fmt.Printf("%s: %v\n", remote.Config().Name, remote.Config().URLs)
}
}func (r *Repository) CreateRemote(c *config.RemoteConfig) (*Remote, error)Example:
package main
import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
// Create new remote
remote, err := r.CreateRemote(&config.RemoteConfig{
Name: "upstream",
URLs: []string{"https://github.com/upstream/repo"},
})
if err != nil {
panic(err)
}
println("Created remote:", remote.Config().Name)
}func (r *Repository) DeleteRemote(name string) errorExample:
package main
import (
"github.com/go-git/go-git/v5"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
// Delete remote
err = r.DeleteRemote("old-remote")
if err != nil {
panic(err)
}
}func (r *Repository) Fetch(o *FetchOptions) error
func (r *Repository) FetchContext(ctx context.Context, o *FetchOptions) errorExample:
package main
import (
"os"
"github.com/go-git/go-git/v5"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
// Fetch from default remote
err = r.Fetch(&git.FetchOptions{
RemoteName: "origin",
Progress: os.Stdout,
})
if err != nil && err != git.NoErrAlreadyUpToDate {
panic(err)
}
if err == git.NoErrAlreadyUpToDate {
println("Already up to date")
}
}func (remote *Remote) Fetch(o *FetchOptions) error
func (remote *Remote) FetchContext(ctx context.Context, o *FetchOptions) errorExample:
package main
import (
"os"
"github.com/go-git/go-git/v5"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
remote, err := r.Remote("origin")
if err != nil {
panic(err)
}
// Fetch from specific remote
err = remote.Fetch(&git.FetchOptions{
Progress: os.Stdout,
})
if err != nil && err != git.NoErrAlreadyUpToDate {
panic(err)
}
}type FetchOptions struct {
RemoteName string
RemoteURL string
RefSpecs []config.RefSpec
Depth int
Auth transport.AuthMethod
Progress sideband.Progress
Tags TagMode
Force bool
InsecureSkipTLS bool
ClientCert []byte
ClientKey []byte
CABundle []byte
ProxyOptions transport.ProxyOptions
Prune bool
}Key Fields:
Example with Options:
package main
import (
"os"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing/transport/http"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
err = r.Fetch(&git.FetchOptions{
RemoteName: "origin",
RefSpecs: []config.RefSpec{
config.RefSpec("+refs/heads/main:refs/remotes/origin/main"),
},
Depth: 50,
Auth: &http.BasicAuth{
Username: "user",
Password: "token",
},
Progress: os.Stdout,
Prune: true,
Tags: git.AllTags,
})
if err != nil && err != git.NoErrAlreadyUpToDate {
panic(err)
}
}func (r *Repository) Push(o *PushOptions) error
func (r *Repository) PushContext(ctx context.Context, o *PushOptions) errorExample:
package main
import (
"os"
"github.com/go-git/go-git/v5"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
// Push to default remote
err = r.Push(&git.PushOptions{
RemoteName: "origin",
Progress: os.Stdout,
})
if err != nil {
panic(err)
}
}func (remote *Remote) Push(o *PushOptions) error
func (remote *Remote) PushContext(ctx context.Context, o *PushOptions) errorExample:
package main
import (
"github.com/go-git/go-git/v5"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
remote, err := r.Remote("origin")
if err != nil {
panic(err)
}
// Push to specific remote
err = remote.Push(&git.PushOptions{})
if err != nil {
panic(err)
}
}type PushOptions struct {
RemoteName string
RemoteURL string
RefSpecs []config.RefSpec
Auth transport.AuthMethod
Progress sideband.Progress
Prune bool
Force bool
InsecureSkipTLS bool
ClientCert []byte
ClientKey []byte
CABundle []byte
RequireRemoteRefs []config.RefSpec
FollowTags bool
ForceWithLease *ForceWithLease
Options map[string]string
Atomic bool
ProxyOptions transport.ProxyOptions
}Key Fields:
Example - Push Specific Branch:
package main
import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
// Push specific branch
err = r.Push(&git.PushOptions{
RemoteName: "origin",
RefSpecs: []config.RefSpec{
config.RefSpec("refs/heads/feature:refs/heads/feature"),
},
})
if err != nil {
panic(err)
}
}Example - Force Push (Carefully!):
package main
import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
// Force push - USE WITH CAUTION
err = r.Push(&git.PushOptions{
RemoteName: "origin",
RefSpecs: []config.RefSpec{
config.RefSpec("+refs/heads/branch:refs/heads/branch"),
},
Force: true,
})
if err != nil {
panic(err)
}
}Example - Force with Lease (Safe Force Push):
package main
import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
// Get current remote branch state
ref, err := r.Reference(plumbing.ReferenceName("refs/remotes/origin/main"), true)
if err != nil {
panic(err)
}
// Force push with lease - only succeeds if remote hasn't changed
err = r.Push(&git.PushOptions{
RemoteName: "origin",
ForceWithLease: &git.ForceWithLease{
RefName: plumbing.ReferenceName("refs/heads/main"),
Hash: ref.Hash(),
},
})
if err != nil {
panic(err)
}
}Example - Atomic Push:
package main
import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
// Atomic push - all refs succeed or all fail
err = r.Push(&git.PushOptions{
RemoteName: "origin",
RefSpecs: []config.RefSpec{
config.RefSpec("refs/heads/main:refs/heads/main"),
config.RefSpec("refs/heads/develop:refs/heads/develop"),
},
Atomic: true,
})
if err != nil {
panic(err)
}
}func (w *Worktree) Pull(o *PullOptions) error
func (w *Worktree) PullContext(ctx context.Context, o *PullOptions) errorExample:
package main
import (
"fmt"
"github.com/go-git/go-git/v5"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
w, err := r.Worktree()
if err != nil {
panic(err)
}
// Pull from origin
err = w.Pull(&git.PullOptions{RemoteName: "origin"})
if err == git.NoErrAlreadyUpToDate {
fmt.Println("Already up to date")
} else if err != nil {
panic(err)
}
// Print HEAD after pull
ref, err := r.Head()
if err != nil {
panic(err)
}
fmt.Println("HEAD at:", ref.Hash())
}type PullOptions struct {
RemoteName string
RemoteURL string
ReferenceName plumbing.ReferenceName
SingleBranch bool
Depth int
Auth transport.AuthMethod
RecurseSubmodules SubmoduleRescursivity
Progress sideband.Progress
Force bool
InsecureSkipTLS bool
ClientCert []byte
ClientKey []byte
CABundle []byte
ProxyOptions transport.ProxyOptions
}Example with Options:
package main
import (
"os"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/transport/http"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
w, err := r.Worktree()
if err != nil {
panic(err)
}
err = w.Pull(&git.PullOptions{
RemoteName: "origin",
ReferenceName: plumbing.ReferenceName("refs/heads/develop"),
SingleBranch: true,
Depth: 10,
Auth: &http.BasicAuth{
Username: "user",
Password: "token",
},
Progress: os.Stdout,
})
if err != nil && err != git.NoErrAlreadyUpToDate {
panic(err)
}
}func (remote *Remote) List(o *ListOptions) ([]*plumbing.Reference, error)List references available on the remote without fetching.
Example:
package main
import (
"fmt"
"github.com/go-git/go-git/v5"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
remote, err := r.Remote("origin")
if err != nil {
panic(err)
}
// List remote references
refs, err := remote.List(&git.ListOptions{})
if err != nil {
panic(err)
}
fmt.Println("Remote references:")
for _, ref := range refs {
fmt.Printf(" %s -> %s\n", ref.Name().Short(), ref.Hash())
}
}import "github.com/go-git/go-git/v5/plumbing/transport/http"
type BasicAuth struct {
Username string
Password string
}Example - Username and Password:
package main
import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/transport/http"
)
func main() {
r, err := git.PlainClone("/tmp/repo", false, &git.CloneOptions{
URL: "https://github.com/private/repo",
Auth: &http.BasicAuth{
Username: "username",
Password: "password",
},
})
if err != nil {
panic(err)
}
_ = r
}Example - Personal Access Token:
package main
import (
"os"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/transport/http"
)
func main() {
token := os.Getenv("GITHUB_TOKEN")
r, err := git.PlainClone("/tmp/repo", false, &git.CloneOptions{
URL: "https://github.com/private/repo",
Auth: &http.BasicAuth{
Username: "abc123", // Can be anything except empty
Password: token,
},
})
if err != nil {
panic(err)
}
_ = r
}import "github.com/go-git/go-git/v5/plumbing/transport/http"
type TokenAuth struct {
Token string
}Example:
package main
import (
"os"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/transport/http"
)
func main() {
token := os.Getenv("GITHUB_TOKEN")
r, err := git.PlainClone("/tmp/repo", false, &git.CloneOptions{
URL: "https://github.com/private/repo",
Auth: &http.TokenAuth{
Token: token,
},
})
if err != nil {
panic(err)
}
_ = r
}import "github.com/go-git/go-git/v5/plumbing/transport/ssh"
type PublicKeys struct {
User string
Signer ssh.Signer
// contains filtered or unexported fields
}
func NewPublicKeys(user string, pemBytes []byte, password string) (*PublicKeys, error)
func NewPublicKeysFromFile(user, pemFile, password string) (*PublicKeys, error)Example - SSH Key from File:
package main
import (
"os"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
)
func main() {
// Load SSH key from file
sshKey := os.Getenv("HOME") + "/.ssh/id_rsa"
auth, err := ssh.NewPublicKeysFromFile("git", sshKey, "")
if err != nil {
panic(err)
}
r, err := git.PlainClone("/tmp/repo", false, &git.CloneOptions{
URL: "git@github.com:private/repo.git",
Auth: auth,
})
if err != nil {
panic(err)
}
_ = r
}Example - SSH Key from Memory:
package main
import (
"os"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
)
func main() {
// Read SSH key
keyBytes, err := os.ReadFile(os.Getenv("HOME") + "/.ssh/id_rsa")
if err != nil {
panic(err)
}
// Create auth from key bytes
auth, err := ssh.NewPublicKeys("git", keyBytes, "")
if err != nil {
panic(err)
}
r, err := git.PlainClone("/tmp/repo", false, &git.CloneOptions{
URL: "git@github.com:private/repo.git",
Auth: auth,
})
if err != nil {
panic(err)
}
_ = r
}Example - SSH Key with Passphrase:
package main
import (
"os"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
)
func main() {
sshKey := os.Getenv("HOME") + "/.ssh/id_rsa"
passphrase := os.Getenv("SSH_PASSPHRASE")
auth, err := ssh.NewPublicKeysFromFile("git", sshKey, passphrase)
if err != nil {
panic(err)
}
r, err := git.PlainClone("/tmp/repo", false, &git.CloneOptions{
URL: "git@github.com:private/repo.git",
Auth: auth,
})
if err != nil {
panic(err)
}
_ = r
}import "github.com/go-git/go-git/v5/plumbing/transport/ssh"
type Password struct {
User string
Password string
}Example:
package main
import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
)
func main() {
auth := &ssh.Password{
User: "git",
Password: "password",
}
r, err := git.PlainClone("/tmp/repo", false, &git.CloneOptions{
URL: "ssh://git@example.com/repo.git",
Auth: auth,
})
if err != nil {
panic(err)
}
_ = r
}Use with caution - only for testing or self-signed certificates:
package main
import (
"github.com/go-git/go-git/v5"
)
func main() {
r, err := git.PlainClone("/tmp/repo", false, &git.CloneOptions{
URL: "https://self-signed.example.com/repo.git",
InsecureSkipTLS: true,
})
if err != nil {
panic(err)
}
_ = r
}package main
import (
"os"
"github.com/go-git/go-git/v5"
)
func main() {
// Load custom CA certificate
caBundle, err := os.ReadFile("/path/to/ca-cert.pem")
if err != nil {
panic(err)
}
r, err := git.PlainClone("/tmp/repo", false, &git.CloneOptions{
URL: "https://example.com/repo.git",
CABundle: caBundle,
})
if err != nil {
panic(err)
}
_ = r
}package main
import (
"os"
"github.com/go-git/go-git/v5"
)
func main() {
// Load client certificate and key
clientCert, err := os.ReadFile("/path/to/client-cert.pem")
if err != nil {
panic(err)
}
clientKey, err := os.ReadFile("/path/to/client-key.pem")
if err != nil {
panic(err)
}
r, err := git.PlainClone("/tmp/repo", false, &git.CloneOptions{
URL: "https://example.com/repo.git",
ClientCert: clientCert,
ClientKey: clientKey,
})
if err != nil {
panic(err)
}
_ = r
}type ProxyOptions struct {
URL string
Username string
Password string
}Example:
package main
import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/transport"
)
func main() {
r, err := git.PlainClone("/tmp/repo", false, &git.CloneOptions{
URL: "https://github.com/repo/repo",
ProxyOptions: transport.ProxyOptions{
URL: "http://proxy.example.com:8080",
Username: "proxy-user",
Password: "proxy-pass",
},
})
if err != nil {
panic(err)
}
_ = r
}package main
import (
"os"
"github.com/go-git/go-git/v5"
)
func main() {
r, err := git.PlainClone("/tmp/repo", false, &git.CloneOptions{
URL: "https://github.com/large/repo",
Progress: os.Stdout,
})
if err != nil {
panic(err)
}
_ = r
}package main
import (
"fmt"
"io"
"github.com/go-git/go-git/v5"
)
type progressWriter struct{}
func (pw *progressWriter) Write(p []byte) (n int, err error) {
fmt.Print(string(p))
return len(p), nil
}
func main() {
r, err := git.PlainClone("/tmp/repo", false, &git.CloneOptions{
URL: "https://github.com/repo/repo",
Progress: &progressWriter{},
})
if err != nil {
panic(err)
}
_ = r
}var (
ErrRemoteNotFound = errors.New("remote not found")
ErrRemoteExists = errors.New("remote already exists")
ErrAnonymousRemoteName = errors.New("anonymous remote name not allowed")
ErrFetching = errors.New("error fetching")
ErrNonFastForwardUpdate = errors.New("non-fast-forward update")
NoErrAlreadyUpToDate = errors.New("already up-to-date")
)From transport package:
var (
ErrRepositoryNotFound = errors.New("repository not found")
ErrEmptyRemoteRepository = errors.New("remote repository is empty")
ErrAuthenticationRequired = errors.New("authentication required")
ErrAuthorizationFailed = errors.New("authorization failed")
ErrInvalidAuthMethod = errors.New("invalid auth method")
)package main
import (
"github.com/go-git/go-git/v5"
)
func main() {
r, _ := git.PlainOpen("/tmp/repo")
err := r.Fetch(&git.FetchOptions{RemoteName: "origin"})
if err == git.NoErrAlreadyUpToDate {
println("Repository is up to date")
} else if err != nil {
panic(err)
} else {
println("Fetched new changes")
}
}package main
import (
"context"
"time"
"github.com/go-git/go-git/v5"
)
func main() {
r, _ := git.PlainOpen("/tmp/repo")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
err := r.FetchContext(ctx, &git.FetchOptions{
RemoteName: "origin",
})
if err != nil && err != git.NoErrAlreadyUpToDate {
panic(err)
}
}package main
import (
"os"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/transport/http"
)
func main() {
// Load from environment variable, not hardcoded
token := os.Getenv("GITHUB_TOKEN")
if token == "" {
panic("GITHUB_TOKEN not set")
}
r, err := git.PlainClone("/tmp/repo", false, &git.CloneOptions{
URL: "https://github.com/private/repo",
Auth: &http.BasicAuth{
Username: "token",
Password: token,
},
})
if err != nil {
panic(err)
}
_ = r
}package main
import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
)
func main() {
r, _ := git.PlainOpen("/tmp/repo")
// Get current remote state
ref, _ := r.Reference(plumbing.ReferenceName("refs/remotes/origin/main"), true)
// Force with lease - safer than force push
err := r.Push(&git.PushOptions{
RemoteName: "origin",
ForceWithLease: &git.ForceWithLease{
RefName: plumbing.ReferenceName("refs/heads/main"),
Hash: ref.Hash(),
},
})
if err != nil {
panic(err)
}
}package main
import (
"errors"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/http"
)
func main() {
r, _ := git.PlainOpen("/tmp/repo")
err := r.Push(&git.PushOptions{
RemoteName: "origin",
Auth: &http.BasicAuth{
Username: "user",
Password: "wrong-password",
},
})
if err != nil {
if errors.Is(err, transport.ErrAuthenticationRequired) {
println("Authentication required")
} else if errors.Is(err, transport.ErrAuthorizationFailed) {
println("Authorization failed - check credentials")
} else {
panic(err)
}
}
}