package main import ( "bufio" "crypto/tls" "flag" "fmt" "io" "log" "net/http" "os" "path" "path/filepath" "sync" "time" "errors" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" githttp "github.com/go-git/go-git/v5/plumbing/transport/http" ) const ( defaultRemoteName = "pad-archiver" ) var ( nothingToDo = errors.New("Nothing to do for unmodified file") ) var cm sync.Mutex func commit( tree *git.Worktree, padfile string, url string, ) (plumbing.Hash, error) { cm.Lock() defer cm.Unlock() if _, err := tree.Add(padfile); err != nil { return plumbing.ZeroHash, fmt.Errorf("Failed to stage %s: %w", padfile, err) } status, err := tree.Status() if err != nil { return plumbing.ZeroHash, fmt.Errorf("Failed to get status of %s", padfile) } fileStatus := status.File(padfile) if fileStatus.Staging != git.Added && fileStatus.Staging != git.Modified { return plumbing.ZeroHash, nothingToDo } commit, err := tree.Commit( fmt.Sprintf("Updated %s from %s", padfile, url), &git.CommitOptions{ All: false, Author: &object.Signature { Name: "Pad Archiver", Email: "pad-archiver@dadada.li", When: time.Now(), }, }, ) if err != nil { return plumbing.ZeroHash, fmt.Errorf("Failed to commit %s: %w", padfile, err) } return commit, nil } func download( gitdir string, url string, ) (string, error) { res, err := http.Get(url + "/export/txt") if err != nil { return "", fmt.Errorf("Failed to get pad at %s: %w", url, err) } defer res.Body.Close() padfile := path.Base(url) + ".txt" padpath := filepath.Join(gitdir, padfile) out, err := os.Create(padpath) written, err := io.Copy(out, res.Body) if err != nil { return "", fmt.Errorf("Failed to write pad to file at %s: %w", padfile, err) } if written < 100 { return "", fmt.Errorf("Skipping update of %s, because pad has likely been removed from %s", padfile, url) } return padfile, nil } func createRemote( repo *git.Repository, remote string, url string, ) (*git.Remote, error) { newRemote, err := repo.Remote(remote) if err != nil { log.Printf("Creating new git remote %s with URL %s", remote, url) return repo.CreateRemote(&config.RemoteConfig{ Name: remote, URLs: []string{url}, }) } else { log.Printf("Using remote %s with URL %s", remote, url) } return newRemote, nil } func push( auth *githttp.BasicAuth, r *git.Repository, remote string, ) error { return r.Push(&git.PushOptions{ RemoteName: remote, Auth: auth, }) } func main() { http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} cwd, err := os.Getwd() if err != nil { log.Fatalf("Failed to get working directory %s", err) } gitdir := flag.String( "C", cwd, "git directory", ) doPush := flag.Bool( "push", false, "push repository to remote", ) username := flag.String( "username", "", "username", ) password := flag.String( "password", "", "password", ) remoteUrl := flag.String( "url", "", "url of remote", ) flag.Parse() repo, err := git.PlainOpen(*gitdir) if err != nil { log.Fatalf("Failed to open git repo %s: %s", *gitdir, err) } tree, err := repo.Worktree() if err != nil { log.Fatalf("Failed to open git worktree %s", err) } defer tree.Clean(&git.CleanOptions{Dir: true}) filesystemRoot := tree.Filesystem.Root() scanner := bufio.NewScanner(os.Stdin) var wg sync.WaitGroup for scanner.Scan() { wg.Add(1) padurl := scanner.Text() go func() { defer wg.Done() padfile, err := download(filesystemRoot, padurl) if err != nil { log.Printf("%s", err) return } log.Printf("Downloaded %s", padurl) if _, err := commit(tree, padfile, padurl); err != nil { if err == nothingToDo { log.Printf("Nothing to do for %s", padfile) } else { log.Printf("%s", err) } } else { log.Printf("Committed %s", padfile) } }() } wg.Wait() auth := &githttp.BasicAuth{ Username: *username, Password: *password, } if *doPush == true { if _, err := createRemote(repo, defaultRemoteName, *remoteUrl); err != nil { log.Fatalf("%s", err) } if err := push(auth, repo, defaultRemoteName); err != nil { if err == git.NoErrAlreadyUpToDate { log.Println("Already up-to-date") } else { log.Fatalf("%s", err) } } else { log.Println("Pushed changes to remote") } } }