-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor, and add framework that will allow me to add BitBucket and G…
…itLab support easily
- Loading branch information
Showing
6 changed files
with
192 additions
and
151 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,3 +28,4 @@ gobackup | |
main | ||
~./.vimdid/ | ||
~. | ||
tempor1s/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,190 +1,72 @@ | ||
package backup | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io/ioutil" | ||
"log" | ||
"os" | ||
"path" | ||
"sync" | ||
|
||
"github.com/google/go-github/github" | ||
"github.com/schollz/progressbar/v2" | ||
"golang.org/x/oauth2" | ||
"gopkg.in/src-d/go-git.v4" | ||
"gopkg.in/src-d/go-git.v4/plumbing/transport/http" | ||
) | ||
|
||
// GitHub will clone all users github repos to your local machine. Puts them in a <github_username>/ folder. | ||
func GitHub(token string, args []string) { | ||
// Start will start the command, handle arguments, and dispatch the correct handler | ||
func Start(token string, args []string) { | ||
if len(args) == 0 { | ||
fmt.Println("Please enter a URL for a GitHub user. Example: `gobackup backup github.com/tempor1s`") | ||
fmt.Println("Please pass the provider that you want to clone from. (github/gitlab/bitbucket) Example: `backup github`") | ||
return | ||
} else if len(args) == 1 { | ||
fmt.Println("Please pass in the username that you would like to clone from. Example: `backup github tempor1s`") | ||
return | ||
} | ||
|
||
if token == "" { | ||
// TODO: Support non-access token based request | ||
fmt.Println("WARNING: Personal token was not passed in. Please pass in a token using --token - Support for NON-token download coming soon.") | ||
fmt.Println("WARNING: Personal token was not passed in, only cloning public repos. If you want to clone private repos, please supply a token using --token") | ||
} | ||
|
||
fmt.Printf("Backing up your repos... Please wait - Don't worry if the bar freezes, this could take a few minutes :)\n\n") | ||
|
||
// Get the URL to clone | ||
userURL := args[0] | ||
|
||
// Get the users github name for the directory | ||
dirName := path.Base(userURL) | ||
// Check the service to backup and then dispatch to the correct handler with token and username | ||
switch args[0] { | ||
case "github": | ||
gitHub(token, args[1]) | ||
case "gitlab": | ||
gitLab(token, args[1]) | ||
case "bitbucket": | ||
bitBucket(token, args[1]) | ||
} | ||
} | ||
|
||
// gitHub will clone all of a users github repos to your local machine. Puts them in a <github_username>/ folder. | ||
func gitHub(token string, username string) { | ||
// Start timer, create wait group so we dont exit early, and create channel for URL's | ||
repos := make(chan string) | ||
repoChan := make(chan int) | ||
var wg sync.WaitGroup | ||
|
||
// Get all repos for the user | ||
go getRepos(token, dirName, repos, repoChan, &wg) | ||
go getGithubRepos(token, username, repos, repoChan, &wg) | ||
|
||
// Get length of repos for the max of our progress bar | ||
repoCount := <-repoChan | ||
bar := progressbar.NewOptions(repoCount, progressbar.OptionSetRenderBlankState(true)) | ||
|
||
// Clone all repos | ||
cloneRepos(repos, bar, dirName, token, &wg) | ||
cloneRepos(repos, bar, username, token, &wg) | ||
// Wait until all repos have been cloned before printing time and exiting | ||
wg.Wait() | ||
|
||
// TODO: Refactor this out | ||
size := dirSize(dirName) | ||
sizeStr := fmt.Sprintf("%.2f MB", size) | ||
|
||
if size > 1000 { | ||
size = size / 1000 | ||
sizeStr = fmt.Sprintf("%.2f GB", size) | ||
} | ||
// Get the total size of all the cloned directories and print information | ||
size := getDirSizeStr(username) | ||
|
||
fmt.Printf("\n\nCloning repos complete. Cloned %d repos with a total size of %s\n", repoCount+1, sizeStr) | ||
fmt.Printf("\n\nCloning repos complete. Cloned %d repos with a total size of %s\n", repoCount+1, size) | ||
} | ||
|
||
// getRepos will get all the repos for a user | ||
func getRepos(token, userName string, c chan string, count chan int, wg *sync.WaitGroup) { | ||
// Set up OAuth token stuff | ||
ctx := context.Background() | ||
ts := oauth2.StaticTokenSource( | ||
&oauth2.Token{AccessToken: token}, | ||
) | ||
|
||
tc := oauth2.NewClient(ctx, ts) | ||
|
||
// Create a new github client using the OAuth2 token or no token | ||
var client *github.Client | ||
if token != "" { | ||
client = github.NewClient(tc) | ||
} else { | ||
client = github.NewClient(nil) | ||
} | ||
|
||
// Options for our request to GitHub. This will have the API only return repos that we own, and with no pagination | ||
opt := &github.RepositoryListOptions{ | ||
Affiliation: "owner", | ||
ListOptions: github.ListOptions{ | ||
PerPage: 100000, | ||
}, | ||
} | ||
|
||
// Get all repos that the user owns | ||
var repos []*github.Repository | ||
var err error | ||
|
||
if token != "" { | ||
// Get private repos if we have a token | ||
repos, _, err = client.Repositories.List(ctx, "", opt) | ||
} else { | ||
// Get only public repos if we have no token, allowing us to clone other peoples repos as well | ||
repos, _, err = client.Repositories.List(ctx, userName, opt) | ||
} | ||
|
||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
// Send length of bar to channel to use for ProgressBar | ||
count <- len(repos) | ||
|
||
// Add all repos to the channel | ||
for _, repo := range repos { | ||
c <- *repo.HTMLURL | ||
wg.Add(1) | ||
} | ||
|
||
// Close the channel, our other function will still be able to receive what is already inside of it, and this prevents a deadlock | ||
close(c) | ||
// gitLab will clone all of a users gitlab repos to your local machine. Puts them in a <gitlab_username>/ folder. | ||
func gitLab(token string, username string) { | ||
// TODO | ||
fmt.Println("Coming soon!") | ||
} | ||
|
||
// cloneRepos will clone all the repos in a given string slice of repo URLS | ||
func cloneRepos(repos chan string, bar *progressbar.ProgressBar, dirName, token string, wg *sync.WaitGroup) { | ||
// Create username dir to put all cloned repos in. | ||
err := os.MkdirAll(dirName, os.ModePerm) | ||
|
||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
// Clone each repo in the channel | ||
for repo := range repos { | ||
go cloneWorker(repo, dirName, token, wg, bar) | ||
} | ||
} | ||
|
||
// cloneWorker will clone the given repository | ||
func cloneWorker(repo, dirName, token string, wg *sync.WaitGroup, bar *progressbar.ProgressBar) { | ||
// fmt.Printf("[gobackup] cloning %s\n", repo) | ||
|
||
// Decrement the waitgroup count when we are finished cloning the repository | ||
defer bar.Add(1) | ||
defer wg.Done() | ||
// Get the name of the repo we are cloning | ||
repoName := path.Base(repo) | ||
// Dirname which will be <github_username>/<repo_name> | ||
dirName = dirName + "/" + repoName | ||
|
||
// Setup auth | ||
var auth *http.BasicAuth | ||
if token != "" { | ||
// If we have a token | ||
auth = &http.BasicAuth{ | ||
Username: "gobackup", | ||
Password: token, | ||
} | ||
} else { | ||
// If we have no token, we dont want to use any auth | ||
auth = nil | ||
} | ||
// Clone the repository | ||
_, err := git.PlainClone(dirName, false, &git.CloneOptions{ | ||
Auth: auth, | ||
URL: repo, | ||
}) | ||
|
||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
} | ||
|
||
var fileSize float64 | ||
|
||
func dirSize(dirName string) float64 { | ||
allFiles, err := ioutil.ReadDir(dirName) | ||
|
||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
for _, file := range allFiles { | ||
if file.IsDir() { | ||
dirSize(dirName + "/" + file.Name()) | ||
} | ||
fileSize += float64(file.Size()) / 1000000.0 | ||
} | ||
|
||
return fileSize | ||
// bitBucket will clone all of a users bitbucket repos to your local machine. Puts them in a <bitbucket_username>/ folder. | ||
func bitBucket(token string, username string) { | ||
// TODO | ||
fmt.Println("Coming soon!") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
package backup | ||
|
||
import ( | ||
"context" | ||
"os" | ||
"path" | ||
"sync" | ||
|
||
"github.com/google/go-github/github" | ||
"github.com/schollz/progressbar/v2" | ||
"golang.org/x/oauth2" | ||
"gopkg.in/src-d/go-git.v4" | ||
"gopkg.in/src-d/go-git.v4/plumbing/transport/http" | ||
) | ||
|
||
// getGithubRepos will get all a users github repos | ||
func getGithubRepos(token, username string, repoChan chan string, totalRepos chan int, wg *sync.WaitGroup) { | ||
// Set up OAuth token stuff | ||
ctx := context.Background() | ||
ts := oauth2.StaticTokenSource( | ||
&oauth2.Token{AccessToken: token}, | ||
) | ||
|
||
tc := oauth2.NewClient(ctx, ts) | ||
|
||
// Create a new github client using the OAuth2 token or no token | ||
var client *github.Client | ||
if token != "" { | ||
client = github.NewClient(tc) | ||
} else { | ||
client = github.NewClient(nil) | ||
} | ||
|
||
// Options for our request to GitHub. This will have the API only return repos that we own, and with no pagination | ||
opt := &github.RepositoryListOptions{ | ||
Affiliation: "owner", | ||
ListOptions: github.ListOptions{ | ||
PerPage: 100000, | ||
}, | ||
} | ||
|
||
// Get all repos that the user owns | ||
var repos []*github.Repository | ||
var err error | ||
|
||
if token != "" { | ||
// Get private repos if we have a token | ||
repos, _, err = client.Repositories.List(ctx, "", opt) | ||
} else { | ||
// Get only public repos if we have no token, allowing us to clone other peoples repos as well | ||
repos, _, err = client.Repositories.List(ctx, username, opt) | ||
} | ||
checkIfError(err) | ||
|
||
// Send length of bar to channel to use for ProgressBar | ||
totalRepos <- len(repos) | ||
|
||
// Add all repos to the channel | ||
for _, repo := range repos { | ||
repoChan <- *repo.HTMLURL | ||
wg.Add(1) | ||
} | ||
|
||
// Close repo list chanel, our other function will still be able to receive what is already inside of it, and this prevents a deadlock or infinite loop | ||
close(repoChan) | ||
} | ||
|
||
// cloneRepos will clone all the repos in a given string slice of repo URLS | ||
func cloneRepos(repos chan string, bar *progressbar.ProgressBar, dirName, token string, wg *sync.WaitGroup) { | ||
// Create username dir to put all cloned repos in. | ||
err := os.MkdirAll(dirName, os.ModePerm) | ||
checkIfError(err) | ||
|
||
// Clone each repo in the channel | ||
for repo := range repos { | ||
go cloneWorker(repo, dirName, token, wg, bar) | ||
} | ||
} | ||
|
||
// cloneWorker will clone the given repository | ||
func cloneWorker(repo, dirName, token string, wg *sync.WaitGroup, bar *progressbar.ProgressBar) { | ||
// fmt.Printf("[gobackup] cloning %s\n", repo) | ||
|
||
// Decrement the waitgroup count when we are finished cloning the repository and increment our progress bar | ||
defer bar.Add(1) | ||
defer wg.Done() | ||
// Get the name of the repo we are cloning | ||
repoName := path.Base(repo) | ||
// Dirname which will be <github_username>/<repo_name> | ||
dirName = dirName + "/" + repoName | ||
|
||
// Setup auth if we have a token | ||
var auth *http.BasicAuth | ||
if token != "" { | ||
// If we have a token | ||
auth = &http.BasicAuth{ | ||
Username: "gobackup", | ||
Password: token, | ||
} | ||
} else { | ||
// If we have no token, we dont want to use any auth | ||
auth = nil | ||
} | ||
// Clone the repository | ||
_, err := git.PlainClone(dirName, false, &git.CloneOptions{ | ||
Auth: auth, | ||
URL: repo, | ||
}) | ||
|
||
checkIfError(err) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package backup | ||
|
||
import ( | ||
"fmt" | ||
"io/ioutil" | ||
"log" | ||
) | ||
|
||
// Get the size of a directory in the format of a string. | ||
func getDirSizeStr(dirName string) string { | ||
size := dirSize(dirName) | ||
sizeStr := fmt.Sprintf("%.2f MB", size) | ||
|
||
if size > 1000 { | ||
size = size / 1000 | ||
sizeStr = fmt.Sprintf("%.2f GB", size) | ||
} | ||
|
||
return sizeStr | ||
} | ||
|
||
// fileSize is a var we keep because our other method is recursive, and it just makes life easier | ||
var fileSize float64 | ||
|
||
// Get the size of a directory in MB, returns a float | ||
func dirSize(dirName string) float64 { | ||
allFiles, err := ioutil.ReadDir(dirName) | ||
checkIfError(err) | ||
|
||
for _, file := range allFiles { | ||
if file.IsDir() { | ||
dirSize(dirName + "/" + file.Name()) | ||
} | ||
fileSize += float64(file.Size()) / 1000000.0 | ||
} | ||
|
||
return fileSize | ||
} | ||
|
||
// Just a simple check to see if there is an error, because I was re-writing this code too much | ||
func checkIfError(e error) { | ||
if e != nil { | ||
log.Fatal(e) | ||
} | ||
} |
Oops, something went wrong.