Skip to content

Commit 5c5f0d2

Browse files
committed
Abstract git logic
1 parent da0611a commit 5c5f0d2

File tree

4 files changed

+133
-14
lines changed

4 files changed

+133
-14
lines changed

cmd/git-backup/main.go

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,27 @@ import (
99

1010
func main() {
1111
config := loadConfig()
12-
for _, githubConfig := range config.Github {
13-
me, err := githubConfig.GetMe()
14-
if err != nil {
15-
log.Printf("Communication Error: %s", err)
16-
os.Exit(100)
12+
for _, source := range config.GetSources() {
13+
sourceName := source.GetName()
14+
log.Printf("=== %s ===", sourceName)
15+
if err := source.Test(); err != nil {
16+
log.Printf("Failed to verify connection to job [%s]: %s", sourceName, err)
17+
os.Exit(110)
1718
}
18-
log.Printf("=== %s ===", githubConfig.JobName)
19-
log.Printf("Authenticated as: %s", *me.Login)
20-
repos, err := githubConfig.GetRepos()
19+
repos, err := source.ListRepositories()
2120
if err != nil {
2221
log.Printf("Communication Error: %s", err)
2322
os.Exit(100)
2423
}
2524
for _, repo := range repos {
26-
log.Println(*repo.FullName)
27-
targetPath := filepath.Join("backup", githubConfig.JobName, *repo.FullName)
25+
log.Printf("Discovered %s", repo.FullName)
26+
targetPath := filepath.Join("backup", sourceName, repo.FullName)
2827
err := os.MkdirAll(targetPath, os.ModePerm)
2928
if err != nil {
3029
log.Printf("Failed to create directory: %s", err)
3130
os.Exit(100)
3231
}
33-
err = githubConfig.CloneInto(repo, targetPath)
32+
err = repo.CloneInto(targetPath)
3433
if err != nil {
3534
log.Printf("Failed to clone: %s", err)
3635
os.Exit(100)

config.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@ type Config struct {
1010
Github []*GithubConfig `yaml:"github"`
1111
}
1212

13+
func (c *Config) GetSources() []RepositorySource {
14+
sources := make([]RepositorySource, len(c.Github))
15+
16+
offset := 0;
17+
for i := 0; i < len(c.Github); i++ {
18+
sources[i + offset] = c.Github[i]
19+
offset++
20+
}
21+
22+
return sources
23+
}
24+
1325
func (c *Config) setDefaults() {
1426
if c.Github != nil {
1527
for _, config := range c.Github {
@@ -37,4 +49,4 @@ func LoadReader(reader io.Reader) (out Config, err error) {
3749
err = dec.Decode(&out)
3850
out.setDefaults()
3951
return
40-
}
52+
}

github.go

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/go-git/go-git/v5/plumbing/transport/http"
99
"github.com/google/go-github/v43/github"
1010
"golang.org/x/oauth2"
11+
"net/url"
1112
"os"
1213
)
1314

@@ -18,6 +19,36 @@ type GithubConfig struct {
1819
client *github.Client
1920
}
2021

22+
func (c *GithubConfig) Test() error {
23+
me, err := c.getMe()
24+
if err != nil {
25+
return err
26+
}
27+
fmt.Printf("Authenticated with github as: %s", me.Login)
28+
return nil
29+
}
30+
31+
func (c *GithubConfig) GetName() string {
32+
return c.JobName
33+
}
34+
35+
func (c *GithubConfig) ListRepositories() ([]*Repository, error) {
36+
repos, err := c.getAllRepos()
37+
if err != nil {
38+
return nil, err
39+
}
40+
out := make([]*Repository, len(repos))
41+
for i, repo := range repos {
42+
gitUrl, err := url.Parse(*repo.CloneURL)
43+
if err != nil {
44+
return out, err
45+
}
46+
gitUrl.User = url.UserPassword("github", c.AccessToken)
47+
out[i] = &Repository{GitURL: *gitUrl}
48+
}
49+
return out, nil
50+
}
51+
2152
func (c *GithubConfig) setDefaults() {
2253
if c.JobName == "" {
2354
c.JobName = "GitHub"
@@ -33,12 +64,13 @@ func (c *GithubConfig) setDefaults() {
3364
}
3465
}
3566
}
36-
func (c *GithubConfig) GetMe() (*github.User, error) {
67+
68+
func (c *GithubConfig) getMe() (*github.User, error) {
3769
response, _, err := c.client.Users.Get(context.Background(), "")
3870
return response, err
3971
}
4072

41-
func (c *GithubConfig) GetRepos() ([]*github.Repository, error) {
73+
func (c *GithubConfig) getAllRepos() ([]*github.Repository, error) {
4274
all := make([]*github.Repository, 0)
4375
var err error
4476

repository.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package git_backup
2+
3+
import (
4+
"github.com/go-git/go-git/v5"
5+
"github.com/go-git/go-git/v5/plumbing/transport"
6+
"github.com/go-git/go-git/v5/plumbing/transport/http"
7+
"net/url"
8+
"os"
9+
)
10+
11+
type RepositorySource interface {
12+
GetName() string
13+
Test() error
14+
ListRepositories() ([]*Repository, error)
15+
}
16+
17+
type Repository struct {
18+
GitURL url.URL
19+
FullName string
20+
}
21+
22+
func (r *Repository) CloneInto(path string) error {
23+
var auth http.AuthMethod
24+
if r.GitURL.User != nil {
25+
password, _ := r.GitURL.User.Password()
26+
auth = &http.BasicAuth{
27+
Username: r.GitURL.User.Username(),
28+
Password: password,
29+
}
30+
}
31+
gitRepo, err := git.PlainClone(path, false, &git.CloneOptions{
32+
URL: r.GitURL.String(),
33+
Auth: auth,
34+
Progress: os.Stdout,
35+
})
36+
37+
if err == git.ErrRepositoryAlreadyExists {
38+
// Pull instead of clone
39+
if gitRepo, err = git.PlainOpen(path); err == nil {
40+
if w, wErr := gitRepo.Worktree(); wErr != nil {
41+
err = wErr
42+
} else {
43+
err = w.Pull(&git.PullOptions{
44+
Auth: auth,
45+
Progress: os.Stdout,
46+
})
47+
}
48+
}
49+
}
50+
51+
switch err {
52+
case transport.ErrEmptyRemoteRepository:
53+
// Empty repo does not need backup
54+
return nil
55+
default:
56+
return err
57+
case git.NoErrAlreadyUpToDate:
58+
// Already up to date on current branch, still need to refresh other branches
59+
fallthrough
60+
case nil:
61+
// No errors, continue
62+
err = gitRepo.Fetch(&git.FetchOptions{
63+
Auth: auth,
64+
Progress: os.Stdout,
65+
Tags: git.AllTags,
66+
Force: true,
67+
})
68+
}
69+
70+
switch err {
71+
case git.NoErrAlreadyUpToDate:
72+
return nil
73+
default:
74+
return err
75+
}
76+
}

0 commit comments

Comments
 (0)