Skip to content

Instantly share code, notes, and snippets.

@bruschill
Created May 17, 2016 01:26
Show Gist options
  • Save bruschill/ec9ded1954fb387e391630ca8a3ca660 to your computer and use it in GitHub Desktop.
Save bruschill/ec9ded1954fb387e391630ca8a3ca660 to your computer and use it in GitHub Desktop.
multithreaded repo updating
package main
import "os/exec"
//GitCommand is the base structure for an executable git command
type gitCommand struct {
//name of bin
binName string
//additional args for call, appended to reqArgs
addlArgs []string
//path to exec command in
execPath string
}
//NewGitCommand returns a new instance of GitCommand
func NewGitCommand(execPath string, addlArgs []string) *gitCommand {
return &gitCommand{
binName: "git",
addlArgs: addlArgs,
execPath: execPath + "/.git",
}
}
func (gc *gitCommand) reqArgs() []string {
return []string{"-C", gc.execPath}
}
//Run executes GitCommand based on its members
func (gc *gitCommand) Run() error {
args := append(gc.reqArgs(), gc.addlArgs...)
return exec.Command(gc.binName, args...).Run()
}
//Output executes GitCommand based on its members
func (gc *gitCommand) Output() (string, error) {
args := append(gc.reqArgs(), gc.addlArgs...)
out, err := exec.Command(gc.binName, args...).Output()
if err != nil {
return "", err
}
return string(out), nil
}
package main
import (
"fmt"
"log"
"os"
"sync"
)
func updateRepos() {
//get $REPO_ROOT env var
rootPath := os.ExpandEnv("$REPO_ROOT")
if rootPath == "" {
log.Fatalln("$REPO_ROOT must be defined")
}
//open dir defined by rootPath
rootDir, err := os.Open(rootPath)
defer rootDir.Close()
if err != nil {
log.Fatal(err)
}
//get slice of all dir names from rootDir
repoDirs, err := rootDir.Readdirnames(0)
if err != nil {
log.Fatal(err)
}
//initialize WaitGroup
var wg sync.WaitGroup
//iterate through all repo dirs and update them
for _, repoDir := range repoDirs {
wg.Add(1)
repo := NewRepo(repoDir, rootPath)
go func(repo *Repo) {
updateStatus := repo.Update()
wg.Done()
fmt.Printf("%s: %s\n", repo.Name, updateStatus.Message)
}(repo)
}
wg.Wait()
}
func main() {
updateRepos()
}
package main
import (
"log"
"path/filepath"
)
//repo represents a git repository
type Repo struct {
//name of the repository
Name string
//full path to repository
fullPath string
//name of branch initially selected
origBranch string
//tracks whether or not unstaged changes were stashed
hasStashedChanges bool
}
type updateStatus struct {
Success bool
Message string
}
//NewRepo returns a new instance of Repo
func NewRepo(dirName string, rootPath string) *Repo {
repo := &Repo{
Name: dirName,
fullPath: filepath.Join(rootPath, dirName),
}
branch, err := repo.currentBranch()
if err != nil {
log.Fatal(err)
}
repo.origBranch = branch
return repo
}
//hasUnstagedChanges returns true if the repo has unstaged changes, false otherwise
func (r *Repo) hasUnstagedChanges() bool {
addlArgs := []string{"diff", "--exit-code"}
gc := NewGitCommand(r.fullPath, addlArgs)
err := gc.Run()
if err != nil {
return true
}
return false
}
//stashChanges stashes changes for current branch
func (r *Repo) stashChanges() {
if r.hasUnstagedChanges() {
r.hasStashedChanges = true
addlArgs := []string{"stash"}
gc := NewGitCommand(r.fullPath, addlArgs)
gc.Run()
}
}
//retrieveChanges retrieves changes that were previously stashed
func (r *Repo) retrieveChanges() {
if r.hasStashedChanges {
addlArgs := []string{"stash", "pop"}
gc := NewGitCommand(r.fullPath, addlArgs)
gc.Run()
}
}
//currentBranch returns name of current branch as string
func (r *Repo) currentBranch() (string, error) {
addlArgs := []string{"rev-parse", "--abbrev-ref", "HEAD"}
gc := NewGitCommand(r.fullPath, addlArgs)
branchName, err := gc.Output()
if err != nil {
return "", err
}
return branchName, nil
}
//checkoutBranch checks out branch specified by branchName
func (r *Repo) checkoutBranch(branchName string) {
curBranch, _ := r.currentBranch()
if branchName != curBranch {
addlArgs := []string{"checkout", branchName}
gc := NewGitCommand(r.fullPath, addlArgs)
gc.Run()
}
}
//Update updates repo's master branch and returns repo back to original state before the update
func (r *Repo) Update() *updateStatus {
var statusMsg = "Updated."
r.stashChanges()
r.checkoutBranch("master")
addlArgs := []string{"pull"}
gc := NewGitCommand(r.fullPath, addlArgs)
err := gc.Run()
// if err != nil, there were no changes
if err != nil {
statusMsg = "No changes."
}
r.checkoutBranch(r.origBranch)
r.retrieveChanges()
return &updateStatus{Success: true, Message: statusMsg}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment